Skip to content

Commit

Permalink
AG-9300 Prompt to avoid loss of saved work in Theme Builder
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieSumption committed Apr 19, 2024
1 parent c58bc5f commit ee5e5d3
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 128 deletions.
@@ -1,18 +1,42 @@
import { Provider, createStore } from 'jotai';
import { useMemo } from 'react';
import { useLayoutEffect, useMemo, useState } from 'react';

import { RootContainer } from './components/general/RootContainer';
import { applyPreset, darkModePreset, lightModePreset } from './components/presets/presets';
import { allParamModels } from './model/ParamModel';
import { allPartModels } from './model/PartModel';
import { addChangedModelItem, getChangedModelItemCount } from './model/changed-model-items';
import { rerenderTheme } from './model/rendered-theme';

export const ThemeBuilder = () => {
const store = useMemo(createStore, []);
const store = useMemo(createStore, []);

(window as any).handlePartsCssChange = () => {
rerenderTheme(store);
};

const [initialised, setInitialised] = useState(false);

useLayoutEffect(() => {
const hasChanges = getChangedModelItemCount(store) !== 0;
if (!hasChanges) {
const isDarkMode = document.documentElement.dataset.darkMode === 'true';
applyPreset(store, isDarkMode ? darkModePreset : lightModePreset);
}

const detectChange = (name: string) => {
addChangedModelItem(store, name);
};
const listeners = [
...allParamModels().map((param) => store.sub(param.valueAtom, () => detectChange(param.property))),
...allPartModels().map((part) => store.sub(part.variantAtom, () => detectChange(part.partId))),
];

(window as any).handlePartsCssChange = () => {
rerenderTheme(store);
};
if (!initialised) {
setInitialised(true);
}
return () => listeners.forEach((listener) => listener());
}, []);

return (
<Provider store={store}>
<RootContainer />
</Provider>
);
return <Provider store={store}>{initialised && <RootContainer />}</Provider>;
};
Expand Up @@ -19,8 +19,8 @@ import root from 'react-shadow';

import { useSetPreviewGridApi, useSetPreviewGridContainer } from '../../model/rendered-theme';
import { useGridOptions } from '../grid-config/grid-config-atom';
import { allPresets } from '../presets/PresetSelector';
import { useSetGridDom } from '../presets/grid-dom';
import { allPresets } from '../presets/presets';
import { withErrorBoundary } from './ErrorBoundary';

ModuleRegistry.registerModules([
Expand Down
@@ -1,97 +1,11 @@
import {
type CoreParam,
type PartId,
corePart,
opaqueForeground,
paramValueToCss,
ref,
} from '@ag-grid-community/theming';
import { allPartModels } from '@components/theme-builder/model/PartModel';
import { getApplicationConfigAtom } from '@components/theme-builder/model/application-config';
import { corePart, paramValueToCss } from '@ag-grid-community/theming';
import { getChangedModelItemCount } from '@components/theme-builder/model/changed-model-items';
import styled from '@emotion/styled';
import { useStore } from 'jotai';
import { memo, useEffect, useRef } from 'react';

import { allParamModels } from '../../model/ParamModel';
import { PresetRender } from './PresetRender';

type Preset = {
pageBackgroundColor: string;
params?: Partial<Record<CoreParam, string>>;
parts?: Partial<Record<PartId, string>>;
};

export const allPresets: Preset[] = [
{
pageBackgroundColor: '#FAFAFA',
},
{
pageBackgroundColor: '#1D2634',
params: {
backgroundColor: '#1f2836',
foregroundColor: '#FFF',
chromeBackgroundColor: opaqueForeground(0.07),
},
},
{
pageBackgroundColor: 'rgb(75, 153, 154)',
params: {
backgroundColor: 'rgb(241, 237, 225)',
foregroundColor: 'rgb(46, 55, 66)',
chromeBackgroundColor: ref('backgroundColor'),
fontFamily: 'google:Press Start 2P',
gridSize: '4px',
},
},
{
pageBackgroundColor: '#948B8E',
params: {
backgroundColor: '#E4E0E2',
headerBackgroundColor: '#807078',
headerTextColor: '#EEECED',
// headerBackgroundColor: '#807078',
foregroundColor: 'rgb(46, 55, 66)',
chromeBackgroundColor: ref('backgroundColor'),
fontFamily: 'google:Jacquard 24',
gridSize: '8px',
wrapperBorderRadius: '0px',
headerFontWeight: '600',
},
},
{
pageBackgroundColor: '#212124',
params: {
backgroundColor: '#252A33',
headerBackgroundColor: '#8AB4F9',
headerTextColor: '#252A33',
// headerBackgroundColor: '#807078',
foregroundColor: '#BDC2C7',
chromeBackgroundColor: ref('backgroundColor'),
fontFamily: 'google:Plus Jakarta Sans',
gridSize: '8px',
wrapperBorderRadius: '12px',
headerFontWeight: '600',
accentColor: '#8AB4F9',
rowVerticalPaddingScale: '0.6',
},
},
{
pageBackgroundColor: '#ffffff',
params: {
backgroundColor: '#ffffff',
headerBackgroundColor: '#F9FAFB',
headerTextColor: '#919191',
foregroundColor: 'rgb(46, 55, 66)',
fontFamily: 'Arial',
gridSize: '8px',
wrapperBorderRadius: '0px',
headerFontWeight: '600',
oddRowBackgroundColor: '#F9FAFB',
rowBorder: 'none',
wrapperBorder: 'none',
},
},
];
import { type Preset, allPresets, applyPreset } from './presets';

export const PresetSelector = memo(() => {
// find and load any google fonts that might be used by presets
Expand Down Expand Up @@ -144,35 +58,18 @@ const SelectButton = ({ preset }: SelectButtonProps) => {

const store = useStore();

const applyPreset = () => {
const presetParams: any = preset.params || {};
for (const { property, valueAtom } of allParamModels()) {
if (store.get(valueAtom) != null || presetParams[property] != null) {
store.set(valueAtom, presetParams[property] || null);
}
}

const presetParts = preset.parts || {};
for (const part of allPartModels()) {
const newVariantId = presetParts[part.partId];
if (store.get(part.variantAtom) != null || newVariantId != null) {
const newVariant =
newVariantId == null
? part.defaultVariant
: part.variants.find((v) => v.variantId === newVariantId);
if (!newVariant) {
throw new Error(
`Invalid variant ${newVariantId} for part ${part.partId}, use one of: ${part.variants.map((v) => v.variantId).join(', ')}`
);
}
store.set(part.variantAtom, newVariant);
}
}
store.set(getApplicationConfigAtom('previewPaneBackgroundColor'), preset.pageBackgroundColor || null);
};

return (
<SelectButtonWrapper ref={wrapperRef} onClick={applyPreset}>
<SelectButtonWrapper
ref={wrapperRef}
onClick={() => {
if (getChangedModelItemCount(store) > 1) {
if (!confirm('Applying a preset will reset your changes, are you sure?')) {
return;
}
}
applyPreset(store, preset);
}}
>
<PresetRender />
</SelectButtonWrapper>
);
Expand Down
@@ -0,0 +1,114 @@
import { type CoreParam, type PartId, opaqueForeground, ref } from '@ag-grid-community/theming';
import { allParamModels } from '@components/theme-builder/model/ParamModel';
import { allPartModels } from '@components/theme-builder/model/PartModel';
import { getApplicationConfigAtom } from '@components/theme-builder/model/application-config';
import { resetChangedModelItems } from '@components/theme-builder/model/changed-model-items';

import type { Store } from '../../model/store';

export type Preset = {
pageBackgroundColor: string;
params?: Partial<Record<CoreParam, string>>;
parts?: Partial<Record<PartId, string>>;
};

export const lightModePreset: Preset = {
pageBackgroundColor: '#FAFAFA',
};
export const darkModePreset: Preset = {
pageBackgroundColor: '#1D2634',
params: {
backgroundColor: '#1f2836',
foregroundColor: '#FFF',
chromeBackgroundColor: opaqueForeground(0.07),
},
};

export const allPresets: Preset[] = [
lightModePreset,
darkModePreset,
{
pageBackgroundColor: 'rgb(75, 153, 154)',
params: {
backgroundColor: 'rgb(241, 237, 225)',
foregroundColor: 'rgb(46, 55, 66)',
chromeBackgroundColor: ref('backgroundColor'),
fontFamily: 'google:Press Start 2P',
gridSize: '4px',
},
},
{
pageBackgroundColor: '#948B8E',
params: {
backgroundColor: '#E4E0E2',
headerBackgroundColor: '#807078',
headerTextColor: '#EEECED',
// headerBackgroundColor: '#807078',
foregroundColor: 'rgb(46, 55, 66)',
chromeBackgroundColor: ref('backgroundColor'),
fontFamily: 'google:Jacquard 24',
gridSize: '8px',
wrapperBorderRadius: '0px',
headerFontWeight: '600',
},
},
{
pageBackgroundColor: '#212124',
params: {
backgroundColor: '#252A33',
headerBackgroundColor: '#8AB4F9',
headerTextColor: '#252A33',
// headerBackgroundColor: '#807078',
foregroundColor: '#BDC2C7',
chromeBackgroundColor: ref('backgroundColor'),
fontFamily: 'google:Plus Jakarta Sans',
gridSize: '8px',
wrapperBorderRadius: '12px',
headerFontWeight: '600',
accentColor: '#8AB4F9',
rowVerticalPaddingScale: '0.6',
},
},
{
pageBackgroundColor: '#ffffff',
params: {
backgroundColor: '#ffffff',
headerBackgroundColor: '#F9FAFB',
headerTextColor: '#919191',
foregroundColor: 'rgb(46, 55, 66)',
fontFamily: 'Arial',
gridSize: '8px',
wrapperBorderRadius: '0px',
headerFontWeight: '600',
oddRowBackgroundColor: '#F9FAFB',
rowBorder: 'none',
wrapperBorder: 'none',
},
},
];

export const applyPreset = (store: Store, preset: Preset) => {
const presetParams: any = preset.params || {};
for (const { property, valueAtom } of allParamModels()) {
if (store.get(valueAtom) != null || presetParams[property] != null) {
store.set(valueAtom, presetParams[property] || null);
}
}

const presetParts = preset.parts || {};
for (const part of allPartModels()) {
const newVariantId = presetParts[part.partId];
if (store.get(part.variantAtom) != null || newVariantId != null) {
const newVariant =
newVariantId == null ? part.defaultVariant : part.variants.find((v) => v.variantId === newVariantId);
if (!newVariant) {
throw new Error(
`Invalid variant ${newVariantId} for part ${part.partId}, use one of: ${part.variants.map((v) => v.variantId).join(', ')}`
);
}
store.set(part.variantAtom, newVariant);
}
}
store.set(getApplicationConfigAtom('previewPaneBackgroundColor'), preset.pageBackgroundColor || null);
resetChangedModelItems(store);
};
@@ -0,0 +1,23 @@
import { atomWithJSONStorage } from './JSONStorage';
import type { Store } from './store';

const changedParamsAtom = atomWithJSONStorage<string[] | null>('changedParams', null);

export const addChangedModelItem = (store: Store, name: string) => {
let existing = store.get(changedParamsAtom);
if (!Array.isArray(existing)) {
existing = [];
}
if (!existing.includes(name)) {
store.set(changedParamsAtom, [...existing, name]);
}
};

export const resetChangedModelItems = (store: Store) => {
store.set(changedParamsAtom, null);
};

export const getChangedModelItemCount = (store: Store) => {
const changes = store.get(changedParamsAtom);
return Array.isArray(changes) ? changes.length : 0;
};

0 comments on commit ee5e5d3

Please sign in to comment.