diff --git a/packages/nc-gui/composables/useBetaFeatureToggle.ts b/packages/nc-gui/composables/useBetaFeatureToggle.ts index d6c061b951..25be37c102 100644 --- a/packages/nc-gui/composables/useBetaFeatureToggle.ts +++ b/packages/nc-gui/composables/useBetaFeatureToggle.ts @@ -1,44 +1,51 @@ import { onMounted, ref } from 'vue' import { createSharedComposable } from '@vueuse/core' +import rfdc from 'rfdc' + +const deepClone = rfdc() const FEATURES = [ { id: 'infinite_scrolling', title: 'Infinite scrolling', description: 'Effortlessly browse large datasets with infinite scrolling.', enabled: true, + version: 1, }, { id: 'canvas_grid_view', title: 'Improved Grid View', description: 'High-performance grid view with enhanced scrolling and rendering capabilities.', enabled: !ncIsPlaywright(), + version: 1, }, { id: 'canvas_group_grid_view', title: 'Improved Group By', description: 'New and Improved groupby in grid view with enhanced scrolling and rendering capabilities.', enabled: !ncIsPlaywright(), + version: 1, }, { id: 'improved_sidebar_ui', title: 'Improved Sidebar', description: 'New and Improved sidebar for better UI experience', enabled: !ncIsPlaywright(), - isEngineering: false, + version: 2, }, { id: 'link_to_another_record', title: 'Link To Another Record', description: 'Show linked record display value in Link fields.', enabled: false, + version: 1, }, - { id: 'model_context_protocol', title: 'Model Context Protocol', description: 'Connect NocoDB base to Claude AI, Windsurf AI, and more.', enabled: false, + version: 1, isEngineering: true, }, { @@ -46,6 +53,7 @@ const FEATURES = [ title: 'Payment Flows', description: 'Enable NocoDB Payment Flows.', enabled: false, + version: 1, isEngineering: true, isEE: true, }, @@ -54,6 +62,7 @@ const FEATURES = [ title: 'AI features', description: 'Unlock AI features to enhance your NocoDB experience.', enabled: false, + version: 1, isEngineering: true, isEE: true, }, @@ -62,6 +71,7 @@ const FEATURES = [ title: 'NocoDB Scripts (Beta)', description: 'Enable NocoDB Scripts to automate repetitive workflow', enabled: false, + version: 1, isEngineering: true, isEE: true, }, @@ -70,6 +80,7 @@ const FEATURES = [ title: 'Integrations', description: 'Enable dynamic integrations.', enabled: false, + version: 1, isEngineering: true, }, { @@ -77,6 +88,7 @@ const FEATURES = [ title: 'Data reflection', description: 'Enable data reflection.', enabled: false, + version: 1, isEngineering: true, isEE: true, }, @@ -85,6 +97,7 @@ const FEATURES = [ title: 'OSS to Enterprise migration', description: 'Enable import from NocoDB OSS instance to Enterprise Edition.', enabled: true, + version: 1, isEE: true, }, { @@ -92,6 +105,7 @@ const FEATURES = [ title: 'Sync', description: 'Enable sync feature.', enabled: false, + version: 1, isEngineering: true, isEE: true, }, @@ -100,6 +114,7 @@ const FEATURES = [ title: 'Geodata column', description: 'Enable the geodata column.', enabled: false, + version: 1, isEngineering: true, }, { @@ -107,6 +122,7 @@ const FEATURES = [ title: 'Scanner for filling data in forms', description: 'Enable scanner to fill data in forms.', enabled: false, + version: 1, isEngineering: true, }, { @@ -114,6 +130,7 @@ const FEATURES = [ title: 'Extensions', description: 'Extensions allows you to add new features or functionalities to the NocoDB platform.', enabled: ncIsPlaywright(), + version: 1, isEngineering: true, }, { @@ -121,6 +138,7 @@ const FEATURES = [ title: 'Comments in attachment carousel', description: 'Enable comments in attachment carousel.', enabled: false, + version: 1, isEngineering: true, }, { @@ -128,6 +146,7 @@ const FEATURES = [ title: 'Expanded form file preview mode', description: 'Preview mode allows you to see attachments inline', enabled: true, + version: 2, isEE: true, }, { @@ -135,6 +154,7 @@ const FEATURES = [ title: 'Expanded form discussion mode', description: 'Discussion mode allows you to see the comments and records audits combined in one place', enabled: true, + version: 2, isEE: true, }, { @@ -142,6 +162,7 @@ const FEATURES = [ title: 'Language', description: 'Community/AI Translated', enabled: false, + version: 1, isEngineering: true, isEE: true, }, @@ -150,6 +171,7 @@ const FEATURES = [ title: 'Cross Base Link', description: 'Enables link creation between tables in different bases.', enabled: false, + version: 1, isEE: true, }, { @@ -157,6 +179,7 @@ const FEATURES = [ title: 'Custom Link', description: 'Allows user to create custom links using existing fields.', enabled: false, + version: 1, isEE: true, }, ] as const @@ -172,7 +195,7 @@ export type BetaFeatureType = (typeof FEATURES)[number] const STORAGE_KEY = 'featureToggleStates' export const useBetaFeatureToggle = createSharedComposable(() => { - const features = ref(structuredClone(FEATURES)) + const features = ref(deepClone(FEATURES)) const featureStates = computed(() => { return features.value.reduce((acc, feature) => { @@ -187,7 +210,13 @@ export const useBetaFeatureToggle = createSharedComposable(() => { const saveFeatures = () => { try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(features.value)) + const featuresToSave = features.value.map((feature) => ({ + id: feature.id, + enabled: feature.enabled, + version: feature.version, + })) + + localStorage.setItem(STORAGE_KEY, JSON.stringify(featuresToSave)) window.dispatchEvent(new StorageEvent('storage', { key: STORAGE_KEY })) } catch (error) { console.error('Failed to save features:', error) @@ -217,15 +246,41 @@ export const useBetaFeatureToggle = createSharedComposable(() => { try { const stored = localStorage.getItem(STORAGE_KEY) if (stored) { - const parsedFeatures = JSON.parse(stored) as Partial[] - features.value = FEATURES.map((defaultFeature) => ({ - ...defaultFeature, - enabled: parsedFeatures.find((f) => f.id === defaultFeature.id)?.enabled ?? defaultFeature.enabled, - })) + const parsedFeatures = JSON.parse(stored) as Array<{ + id: string + enabled: boolean + version?: number + }> + + features.value = FEATURES.map((defaultFeature) => { + const storedFeature = parsedFeatures.find((f) => f.id === defaultFeature.id) + + if (!storedFeature) { + return { ...defaultFeature } + } + + const storedVersion = storedFeature.version || 1 + const currentVersion = defaultFeature.version || 1 + + if (storedVersion < currentVersion) { + console.log(`Feature ${defaultFeature.id} updated from v${storedVersion} to v${currentVersion}`) + return { + ...defaultFeature, + } + } + return { + ...defaultFeature, + enabled: storedFeature.enabled, + } + }) + } else { + features.value = deepClone(FEATURES) } } catch (error) { console.error('Failed to initialize features:', error) + features.value = deepClone(FEATURES) } + saveFeatures() }