Skip to content

Commit 4506aa0

Browse files
committed
feat: implement scoped localStorage keys using lsKey function for brand customization
1 parent e3e03c5 commit 4506aa0

10 files changed

Lines changed: 40 additions & 36 deletions

File tree

‎adminforth/modules/codeInjector.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ class CodeInjector implements ICodeInjector {
404404

405405
const env = {
406406
VITE_ADMINFORTH_PUBLIC_PATH: this.adminforth.config.baseUrl,
407+
VITE_ADMINFORTH_BRAND_SLUG: this.adminforth.config.customization.brandNameSlug,
407408
FORCE_COLOR: '1',
408409
...process.env,
409410
...envOverrides,
@@ -1538,6 +1539,7 @@ class CodeInjector implements ICodeInjector {
15381539
}
15391540
const env = {
15401541
VITE_ADMINFORTH_PUBLIC_PATH: this.adminforth.config.baseUrl,
1542+
VITE_ADMINFORTH_BRAND_SLUG: this.adminforth.config.customization.brandNameSlug,
15411543
FORCE_COLOR: '1',
15421544
...process.env,
15431545
};

‎adminforth/servers/startingPages.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ const tokenVars = (colors: Record<string, string>, theme: 0 | 1) =>
2929

3030
function renderPage(adminforth: IAdminForth, title: string, body: string) {
3131
const { colors, fontFamily } = new StylesGenerator(adminforth.config.customization.styles).mergeStyles();
32+
// same key as lsKey('theme') in the SPA
33+
const themeLsKey = `af_${adminforth.config.customization.brandNameSlug}_theme`;
3234
return `<!DOCTYPE html>
3335
<html lang="en">
3436
<head>
3537
<meta charset="UTF-8">
3638
<meta name="viewport" content="width=device-width, initial-scale=1.0">
3739
<title>${title}</title>
3840
<script>
39-
document.documentElement.classList.toggle('dark', (${JSON.stringify(adminforth.config.customization.singleTheme ?? null)} ?? localStorage.getItem('af__theme')) === 'dark');
41+
document.documentElement.classList.toggle('dark', (${JSON.stringify(adminforth.config.customization.singleTheme ?? null)} ?? localStorage.getItem(${JSON.stringify(themeLsKey)})) === 'dark');
4042
</script>
4143
<style>
4244
:root { ${tokenVars(colors, 0)} color-scheme: light; }

‎adminforth/spa/src/App.vue‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ import AcceptModal from './components/AcceptModal.vue';
184184
import Sidebar from './components/Sidebar.vue';
185185
import { useRoute, useRouter } from 'vue-router';
186186
import { createHead } from 'unhead'
187-
import { getCustomComponent, handleNotAuthorized } from '@/utils';
187+
import { getCustomComponent, handleNotAuthorized, lsKey } from '@/utils';
188188
import ToastStack from './components/ToastStack.vue';
189189
import adminforth, { initFrontedAPI, useAdminforth } from '@/adminforth';
190190
import UserMenuSettingsButton from './components/UserMenuSettingsButton.vue';
@@ -214,7 +214,7 @@ adminforth.closeUserMenuDropdown = () => {
214214
const routerIsReady = ref(false);
215215
const loginRedirectCheckIsReady = ref(false);
216216
217-
const isSidebarIconOnly = ref(localStorage.getItem('afIconOnlySidebar') === 'true');
217+
const isSidebarIconOnly = ref(localStorage.getItem(lsKey('iconOnlySidebar')) === 'true');
218218
219219
const loggedIn = computed(() => !!coreStore?.adminUser);
220220
@@ -370,14 +370,14 @@ onUnmounted(() => {
370370
})
371371
372372
onBeforeMount(()=>{
373-
theme.value = window.localStorage.getItem('af__theme') || 'light';
373+
theme.value = window.localStorage.getItem(lsKey('theme')) || 'light';
374374
document.documentElement.classList.toggle('dark', theme.value === 'dark');
375375
})
376376
377377
watch(() => coreStore.config?.singleTheme, (singleTheme) => {
378378
if (singleTheme) {
379379
theme.value = singleTheme;
380-
window.localStorage.setItem('af__theme', singleTheme);
380+
window.localStorage.setItem(lsKey('theme'), singleTheme);
381381
document.documentElement.classList.toggle('dark', theme.value === 'dark');
382382
}
383383
}, { immediate: true })

‎adminforth/spa/src/components/ResourceListTable.vue‎

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,13 @@
364364
{{ $t('Rows per page') }}
365365
</span>
366366
<Select
367-
v-model="pageSizeInternal"
367+
:modelValue="pageSize"
368+
@update:modelValue="selectPageSize"
368369
:options="pageSizeOptionsComputed"
369370
:searchDisabled="true"
370371
:disableTogleOfSelectedItem="true"
371372
:style="{ width: selectDynamicWidth }"
372-
:placeholder="pageSizeInternal?.toString()"
373+
:placeholder="pageSize.toString()"
373374
class="text-sm min-w-20 af-page-size-button"
374375
classesForInput="
375376
af-page-size-btn h-7 min-h-0 py-1 pl-2.5 pr-7 text-left text-sm font-medium transition-all
@@ -394,7 +395,7 @@ import { getCustomComponent, formatComponent } from '@/utils';
394395
import { useCoreStore } from '@/stores/core';
395396
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
396397
import SkeleteLoader from '@/components/SkeleteLoader.vue';
397-
import { getIcon } from '@/utils';
398+
import { getIcon, lsKey } from '@/utils';
398399
import {
399400
IconEye,
400401
IconPencil,
@@ -478,24 +479,17 @@ const pageSizeOptionsComputed = computed(() => {
478479
}));
479480
});
480481
481-
const pageSizeInternal = ref<number | null>(null);
482+
const selectDynamicWidth = computed(() => `${props.pageSize.toString().length + 5}ch`);
482483
483-
const selectDynamicWidth = computed(() => {
484-
const length = pageSizeInternal.value?.toString().length || 2;
485-
return `${length + 5}ch`;
486-
})
487-
488-
watch(() => props.pageSize, (newVal) => {
489-
pageSizeInternal.value = newVal;
490-
});
491-
492-
watch(() => pageSizeInternal.value, (newSize) => {
493-
if (newSize) {
494-
localStorage.setItem(`pageSize_${props.resource?.resourceId}`, newSize.toString());
495-
emits('update:pageSize', newSize);
496-
page.value = 1;
484+
function selectPageSize(newSize: number) {
485+
// Select emits again when the already selected option is clicked
486+
if (newSize === props.pageSize) {
487+
return;
497488
}
498-
});
489+
localStorage.setItem(lsKey(`pageSize_${props.resource?.resourceId}`), newSize.toString());
490+
emits('update:pageSize', newSize);
491+
page.value = 1;
492+
}
499493
500494
501495
const checkboxesInternal: Ref<any[]> = ref([]);

‎adminforth/spa/src/components/Sidebar.vue‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ import { computed, ref, watch, nextTick, onMounted, onUnmounted, type Ref } from
312312
import { useCoreStore } from '@/stores/core';
313313
import MenuLink from './MenuLink.vue';
314314
import { IconPanelLeftClose, IconPanelLeftOpen, IconChevronDown, IconX, IconCircleAlert } from '@iconify-prerendered/vue-lucide';
315-
import { getIcon, verySimpleHash, loadFile, getCustomComponent } from '@/utils';
315+
import { getIcon, verySimpleHash, loadFile, getCustomComponent, lsKey } from '@/utils';
316316
import { Tooltip } from '@/afcl';
317317
import type { AnnouncementBadgeResponse, AdminForthConfigMenuItem } from '@/types/Common';
318318
import { useAdminforth } from '@/adminforth';
@@ -338,7 +338,7 @@ const emit = defineEmits<{
338338
339339
const coreStore = useCoreStore();
340340
341-
const OPENED_GROUPS_LS_KEY = 'afSidebarOpenedGroups';
341+
const OPENED_GROUPS_LS_KEY = lsKey('sidebarOpenedGroups');
342342
// expanded/collapsed state of groups toggled by user, by itemId, untouched groups fall back to item.open
343343
const openedGroups = ref<Record<string, boolean>>(JSON.parse(localStorage.getItem(OPENED_GROUPS_LS_KEY) || '{}'));
344344
const sidebarAside = ref(null);
@@ -357,7 +357,7 @@ function handleBreakpointChange(e: MediaQueryListEvent) {
357357
} else {
358358
if (props.forceIconOnly === true) {
359359
isSidebarIconOnly.value = true;
360-
} else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
360+
} else if (iconOnlySidebarEnabled.value && localStorage.getItem(lsKey('iconOnlySidebar')) === 'true') {
361361
isSidebarIconOnly.value = true;
362362
} else {
363363
isSidebarIconOnly.value = false;
@@ -404,7 +404,7 @@ function clickOnMenuItem(item: AdminForthConfigMenuItem) {
404404
405405
watch(isSidebarIconOnly, (isIconOnly) => {
406406
if (!isMobile.value && iconOnlySidebarEnabled.value && !props.forceIconOnly) {
407-
localStorage.setItem('afIconOnlySidebar', isIconOnly.toString());
407+
localStorage.setItem(lsKey('iconOnlySidebar'), isIconOnly.toString());
408408
}
409409
emit('sidebarStateChange', { isSidebarIconOnly: isIconOnly, isSidebarHovering: isSidebarHovering.value });
410410
})
@@ -425,7 +425,7 @@ const ctaBadge: Ref<(AnnouncementBadgeResponse & { hash: string; }) | null> = co
425425
return null;
426426
}
427427
const hash = badge.closable ? verySimpleHash(JSON.stringify(badge)) : '';
428-
if (badge.closable && window.localStorage.getItem(`ctaBadge-${hash}`)) {
428+
if (badge.closable && window.localStorage.getItem(lsKey(`ctaBadge-${hash}`))) {
429429
return null;
430430
}
431431
return {...badge, hash};
@@ -436,7 +436,7 @@ function closeCTA() {
436436
return;
437437
}
438438
const hash = ctaBadge.value.hash;
439-
window.localStorage.setItem(`ctaBadge-${hash}`, '1');
439+
window.localStorage.setItem(lsKey(`ctaBadge-${hash}`), '1');
440440
nextTick( async() => {
441441
emit('loadMenu');
442442
await coreStore.fetchMenuBadges();
@@ -469,7 +469,7 @@ watch(() => props.forceIconOnly, (force) => {
469469
}
470470
if (props.forceIconOnly === true) {
471471
isSidebarIconOnly.value = true;
472-
} else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
472+
} else if (iconOnlySidebarEnabled.value && localStorage.getItem(lsKey('iconOnlySidebar')) === 'true') {
473473
isSidebarIconOnly.value = true;
474474
} else {
475475
isSidebarIconOnly.value = false;

‎adminforth/spa/src/stores/core.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ref, computed, onMounted, onUnmounted } from 'vue'
22
import { defineStore } from 'pinia'
3-
import { callAdminForthApi, handleNotAuthorized } from '@/utils';
3+
import { callAdminForthApi, handleNotAuthorized, lsKey } from '@/utils';
44
import { isCompositePrimaryKey, recordIdFilters } from '@/utils/recordId';
55
import websocket from '@/websocket';
66
import { useAdminforth } from '@/adminforth';
@@ -13,7 +13,7 @@ import type { Ref } from 'vue'
1313
export const useCoreStore = defineStore('core', () => {
1414
const { alert } = useAdminforth();
1515
const resourceById: Ref<Record<string, ResourceVeryShort>> = ref({});
16-
const theme: Ref<'light'| 'dark'> = ref(window.localStorage.getItem('af__theme') as ('light'|'dark') || 'light');
16+
const theme: Ref<'light'| 'dark'> = ref(window.localStorage.getItem(lsKey('theme')) as ('light'|'dark') || 'light');
1717

1818
const menu: Ref<AdminForthConfigMenuItem[]> = ref([]);
1919
const config: Ref<AdminForthConfigForFrontend | null> = ref(null);
@@ -76,7 +76,7 @@ export const useCoreStore = defineStore('core', () => {
7676

7777
document.documentElement.setAttribute('data-theme', theme.value);
7878
theme.value = theme.value;
79-
window.localStorage.setItem('af__theme', theme.value);
79+
window.localStorage.setItem(lsKey('theme'), theme.value);
8080
}
8181

8282

‎adminforth/spa/src/utils.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './utils/utils';
22
export * from './utils/listUtils';
33
export * from './utils/createEditUtils';
44
export * from './utils/dateUtils';
5+
export * from './utils/storageKey';

‎adminforth/spa/src/utils/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './utils';
22
export * from './listUtils';
33
export * from './createEditUtils';
44
export * from './dateUtils';
5+
export * from './storageKey';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// localStorage is shared by every app on the same origin, so keys are scoped by brand like the auth cookie
2+
export function lsKey(name: string): string {
3+
return `af_${import.meta.env.VITE_ADMINFORTH_BRAND_SLUG}_${name}`;
4+
}

‎adminforth/spa/src/views/ListView.vue‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ import { useFiltersStore } from '@/stores/filters';
205205
import { callAdminForthApi, currentQuery, getIcon, setQuery, formatComponent, executeCustomBulkAction } from '@/utils';
206206
import { computed, onMounted, onUnmounted, ref, watch, type Ref } from 'vue';
207207
import { useRoute } from 'vue-router';
208-
import { getCustomComponent, getList, startBulkAction } from '@/utils';
208+
import { getCustomComponent, getList, startBulkAction, lsKey } from '@/utils';
209209
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
210210
import { Tooltip, Spinner, Button, LinkButton } from '@/afcl'
211211
import type { AdminForthComponentDeclaration, AdminForthComponentDeclarationFull, AdminForthFilterOperators, AdminForthResourceColumnCommon } from '@/types/Common';
@@ -255,7 +255,7 @@ const pageSize = ref(DEFAULT_PAGE_SIZE);
255255
256256
const syncPageSize = () => {
257257
const resourceId = route.params.resourceId;
258-
const savedSize = localStorage.getItem(`pageSize_${resourceId}`);
258+
const savedSize = localStorage.getItem(lsKey(`pageSize_${resourceId}`));
259259
260260
if (savedSize) {
261261
pageSize.value = parseInt(savedSize);

0 commit comments

Comments
 (0)