135 lines
4.0 KiB
TypeScript
Executable File
135 lines
4.0 KiB
TypeScript
Executable File
import {create} from 'zustand';
|
|
import {subscribeWithSelector} from 'zustand/middleware';
|
|
import {immer} from 'zustand/middleware/immer';
|
|
import {Settings} from '../../core/settings/settings';
|
|
import type {IAppearanceConfig} from './types/appearance-editor-config';
|
|
import {AllCommands} from './commands/commands';
|
|
import mergedAppearanceConfig from './config/merged-appearance-config';
|
|
import {BootstrapData} from '../../core/bootstrap-data/bootstrap-data';
|
|
import {FontConfig} from '@common/http/value-lists';
|
|
|
|
export interface AppearanceValues {
|
|
appearance: {
|
|
env: {app_name: string; app_url: string};
|
|
seo: {
|
|
key: string;
|
|
name: string;
|
|
value: string;
|
|
defaultValue: string;
|
|
}[];
|
|
themes: BootstrapData['themes'];
|
|
custom_code: {
|
|
css?: string;
|
|
html?: string;
|
|
};
|
|
};
|
|
settings: Settings;
|
|
}
|
|
|
|
export interface AppearanceDefaults {
|
|
appearance: {
|
|
themes: {
|
|
light: Record<string, string>;
|
|
dark: Record<string, string>;
|
|
};
|
|
};
|
|
settings: Settings;
|
|
}
|
|
|
|
interface AppearanceStore {
|
|
defaults: AppearanceDefaults | null;
|
|
iframeWindow: Window | null;
|
|
config: IAppearanceConfig | null;
|
|
setDefaults: (value: AppearanceDefaults) => void;
|
|
setIframeWindow: (value: Window) => void;
|
|
preview: {
|
|
navigate: (sectionName: string) => void;
|
|
setValues: (settings: AppearanceValues) => void;
|
|
setThemeFont: (font: FontConfig | null) => void;
|
|
setThemeValue: (name: string, value: string) => void;
|
|
setActiveTheme: (themeId: number | string) => void;
|
|
setHighlight: (selector: string | null | undefined) => void;
|
|
setCustomCode: (mode: 'css' | 'html', value?: string) => void;
|
|
};
|
|
}
|
|
|
|
export const useAppearanceStore = create<AppearanceStore>()(
|
|
subscribeWithSelector(
|
|
immer((set, get) => ({
|
|
defaults: null,
|
|
iframeWindow: null,
|
|
config: mergedAppearanceConfig,
|
|
setDefaults: value => {
|
|
set(state => {
|
|
state.defaults = {...value};
|
|
});
|
|
},
|
|
setIframeWindow: value => {
|
|
set(() => {
|
|
return {iframeWindow: value};
|
|
});
|
|
},
|
|
|
|
preview: {
|
|
navigate: sectionName => {
|
|
const section = get().config?.sections[sectionName];
|
|
const route = section?.previewRoute || '/';
|
|
const preview = get().iframeWindow;
|
|
if (route) {
|
|
postMessage(preview, {type: 'navigate', to: route});
|
|
}
|
|
},
|
|
setValues: values => {
|
|
const preview = get().iframeWindow;
|
|
postMessage(preview, {type: 'setValues', values});
|
|
},
|
|
setThemeFont: font => {
|
|
const preview = get().iframeWindow;
|
|
postMessage(preview, {type: 'setThemeFont', value: font});
|
|
},
|
|
setThemeValue: (name, value) => {
|
|
const preview = get().iframeWindow;
|
|
postMessage(preview, {type: 'setThemeValue', name, value});
|
|
},
|
|
setActiveTheme: themeId => {
|
|
const preview = get().iframeWindow;
|
|
postMessage(preview, {type: 'setActiveTheme', themeId});
|
|
},
|
|
setCustomCode: (mode, value) => {
|
|
const preview = get().iframeWindow;
|
|
postMessage(preview, {type: 'setCustomCode', mode, value});
|
|
},
|
|
setHighlight: selector => {
|
|
set(() => {
|
|
let node: HTMLElement | null = null;
|
|
const document = get().iframeWindow?.document;
|
|
if (document && selector) {
|
|
node = document.querySelector(selector);
|
|
}
|
|
if (node) {
|
|
requestAnimationFrame(() => {
|
|
if (!node) return;
|
|
node.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'center',
|
|
inline: 'center',
|
|
});
|
|
});
|
|
}
|
|
});
|
|
},
|
|
},
|
|
})),
|
|
),
|
|
);
|
|
|
|
function postMessage(window: Window | null, command: AllCommands) {
|
|
if (window) {
|
|
window.postMessage({source: 'be-appearance-editor', ...command}, '*');
|
|
}
|
|
}
|
|
|
|
export function appearanceState() {
|
|
return useAppearanceStore.getState();
|
|
}
|