Radius/add network structure

This commit is contained in:
Luca Haid
2025-12-09 05:34:24 +00:00
parent 60556e5d63
commit 167b038c20
37 changed files with 6833 additions and 2920 deletions
@@ -0,0 +1,157 @@
/**
* TT-Core Async Data Composable (Vue 3)
* Provides async data fetching with loading states
*/
/**
* Create an async data composable
* @returns {Object} - Composable with state and methods
*/
export function useAsyncData() {
const { ref } = Vue;
const isLoading = ref(false);
const hasError = ref(false);
const errorMessage = ref(null);
const data = ref(null);
/**
* Execute async operation with loading state
* @param {Function} asyncFn - Async function to execute
* @param {Object} options - Options
* @returns {Promise<any>} - Result
*/
const executeAsync = async (asyncFn, options = {}) => {
isLoading.value = true;
hasError.value = false;
errorMessage.value = null;
try {
const result = await asyncFn();
data.value = result;
return result;
} catch (error) {
hasError.value = true;
errorMessage.value = error.message || 'Ein Fehler ist aufgetreten';
if (options.onError) {
options.onError(error);
}
throw error;
} finally {
isLoading.value = false;
}
};
/**
* Fetch data from API
* @param {string} url - API URL
* @param {Object} options - Fetch options
* @returns {Promise<any>} - Response data
*/
const fetchData = async (url, options = {}) => {
return executeAsync(async () => {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
});
};
/**
* Reset state
*/
const reset = () => {
isLoading.value = false;
hasError.value = false;
errorMessage.value = null;
data.value = null;
};
return {
isLoading,
hasError,
errorMessage,
data,
executeAsync,
fetchData,
reset
};
}
/**
* Create an async data mixin (backward compatibility)
* @returns {Object} - Vue mixin
*/
export function createAsyncDataMixin() {
return {
data() {
return {
isLoading: false,
hasError: false,
errorMessage: null
};
},
methods: {
/**
* Execute async operation with loading state
* @param {Function} asyncFn - Async function to execute
* @param {Object} options - Options
* @returns {Promise<any>} - Result
*/
async executeAsync(asyncFn, options = {}) {
this.isLoading = true;
this.hasError = false;
this.errorMessage = null;
try {
const result = await asyncFn();
return result;
} catch (error) {
this.hasError = true;
this.errorMessage = error.message || 'Ein Fehler ist aufgetreten';
if (options.onError) {
options.onError(error);
}
throw error;
} finally {
this.isLoading = false;
}
},
/**
* Fetch data from API
* @param {string} url - API URL
* @param {Object} options - Fetch options
* @returns {Promise<any>} - Response data
*/
async fetchData(url, options = {}) {
return this.executeAsync(async () => {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
});
}
}
};
}
@@ -0,0 +1,150 @@
/**
* TT-Core Infinite Scroll Composable (Vue 3)
* Provides infinite scrolling functionality
*/
/**
* Create an infinite scroll composable
* @param {Ref} items - Reactive reference to items array
* @param {Object} options - Scroll options
* @returns {Object} - Composable with visible items and methods
*/
export function useInfiniteScroll(items, options = {}) {
const { ref, computed, onMounted, onBeforeUnmount, onUpdated } = Vue;
const visibleCount = ref(options.initialCount || 50);
const incrementBy = options.incrementBy || 50;
const sentinelRef = ref(null);
let scrollObserver = null;
const visibleItems = computed(() => {
return items.value.slice(0, visibleCount.value);
});
const hasMore = computed(() => {
return visibleCount.value < items.value.length;
});
const loadMore = () => {
if (hasMore.value) {
visibleCount.value += incrementBy;
}
};
const resetVisibleCount = () => {
visibleCount.value = options.initialCount || 50;
};
const setupScrollObserver = () => {
scrollObserver = new IntersectionObserver(
([entry]) => {
if (entry && entry.isIntersecting) {
loadMore();
}
},
{
root: options.root || null,
threshold: 0.1
}
);
if (sentinelRef.value) {
scrollObserver.observe(sentinelRef.value);
}
};
onMounted(() => {
setupScrollObserver();
});
onBeforeUnmount(() => {
if (scrollObserver) {
scrollObserver.disconnect();
scrollObserver = null;
}
});
onUpdated(() => {
// Reconnect observer when DOM updates
if (scrollObserver && sentinelRef.value) {
scrollObserver.disconnect();
scrollObserver.observe(sentinelRef.value);
}
});
return {
sentinelRef,
visibleItems,
visibleCount,
hasMore,
loadMore,
resetVisibleCount
};
}
/**
* Create an infinite scroll mixin (backward compatibility)
* @param {Object} options - Scroll options
* @returns {Object} - Vue mixin
*/
export function createInfiniteScrollMixin(options = {}) {
return {
data() {
return {
visibleCount: options.initialCount || 50,
incrementBy: options.incrementBy || 50,
scrollObserver: null
};
},
computed: {
visibleItems() {
const items = this[options.itemsKey || 'items'] || [];
return items.slice(0, this.visibleCount);
}
},
mounted() {
this.setupScrollObserver();
},
beforeUnmount() {
if (this.scrollObserver) {
this.scrollObserver.disconnect();
this.scrollObserver = null;
}
},
updated() {
// Reconnect observer when DOM updates
if (this.scrollObserver && this.$refs.sentinel) {
this.scrollObserver.disconnect();
this.scrollObserver.observe(this.$refs.sentinel);
}
},
methods: {
setupScrollObserver() {
this.scrollObserver = new IntersectionObserver(
([entry]) => {
if (entry && entry.isIntersecting) {
this.loadMore();
}
},
{
root: this.$refs.tableWrap || null,
threshold: 0.1
}
);
if (this.$refs.sentinel) {
this.scrollObserver.observe(this.$refs.sentinel);
}
},
loadMore() {
const items = this[options.itemsKey || 'items'] || [];
if (this.visibleCount < items.length) {
this.visibleCount += this.incrementBy;
}
},
resetVisibleCount() {
this.visibleCount = options.initialCount || 50;
}
}
};
}
@@ -0,0 +1,100 @@
/**
* TT-Core Intersection Observer Composable (Vue 3)
* Provides lazy-loading and visibility detection
*/
/**
* Create an intersection observer composable
* @param {Function} callback - Callback when element becomes visible
* @param {Object} options - Observer options
* @returns {Object} - Composable with ref and cleanup
*/
export function useIntersectionObserver(callback, options = {}) {
const { ref, onMounted, onBeforeUnmount } = Vue;
const targetRef = ref(null);
let observer = null;
onMounted(() => {
const threshold = options.threshold || 0.1;
const rootMargin = options.rootMargin || '0px';
const once = options.once !== undefined ? options.once : true;
observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
callback(entry);
if (once && observer) {
observer.disconnect();
observer = null;
}
}
},
{ threshold, rootMargin, root: options.root || null }
);
if (targetRef.value) {
observer.observe(targetRef.value);
}
});
onBeforeUnmount(() => {
if (observer) {
observer.disconnect();
observer = null;
}
});
return {
targetRef
};
}
/**
* Create an intersection observer mixin (backward compatibility)
* @param {Object} options - Observer options
* @returns {Object} - Vue mixin
*/
export function createIntersectionObserverMixin(options = {}) {
return {
data() {
return {
isVisible: false,
hasBeenVisible: false,
observer: null
};
},
mounted() {
this.setupObserver();
},
beforeUnmount() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
},
methods: {
setupObserver() {
const threshold = options.threshold || 0.1;
const rootMargin = options.rootMargin || '0px';
this.observer = new IntersectionObserver(
([entry]) => {
this.isVisible = entry.isIntersecting;
if (entry.isIntersecting && !this.hasBeenVisible) {
this.hasBeenVisible = true;
if (this.onFirstVisible) {
this.onFirstVisible();
}
}
},
{ threshold, rootMargin }
);
if (this.$refs.root) {
this.observer.observe(this.$refs.root);
}
}
}
};
}