feat(photos): добавить эффекты и настройки панели редактора
- текстура, чёткость, дымка, виньетка и зерно в неразрушающем редакторе - живой WebGL2-предпросмотр эффектов и CPU-фолбэк - секции редактора свёрнуты по умолчанию, состояние в localStorage - вкладка «Редактирование» доступна только из просмотрщика
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Состояние сворачиваемых секций редактора фото.
|
||||
*
|
||||
* Все секции по умолчанию свёрнуты, а выбор пользователя хранится в браузере
|
||||
* (localStorage), поэтому панель открывается в том же виде после перезагрузки
|
||||
* и переключения фото. Серверные настройки для этого не нужны.
|
||||
*/
|
||||
|
||||
export const EDITOR_SECTIONS = [
|
||||
'light',
|
||||
'curve',
|
||||
'color',
|
||||
'mixer',
|
||||
'pointColor',
|
||||
'correction',
|
||||
'effects'
|
||||
] as const;
|
||||
|
||||
export type EditorSection = (typeof EDITOR_SECTIONS)[number];
|
||||
|
||||
/** true — секция свёрнута. */
|
||||
export type SectionCollapse = Record<EditorSection, boolean>;
|
||||
|
||||
export const EDITOR_SECTIONS_KEY = 'galo.edit.sections';
|
||||
|
||||
/** По умолчанию все секции свёрнуты. */
|
||||
export function defaultSectionCollapse(): SectionCollapse {
|
||||
return {
|
||||
light: true,
|
||||
curve: true,
|
||||
color: true,
|
||||
mixer: true,
|
||||
pointColor: true,
|
||||
correction: true,
|
||||
effects: true
|
||||
};
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Приводит сохранённое значение к полному набору секций: неизвестные ключи
|
||||
* отбрасываются, небулевы значения и отсутствующие ключи получают дефолт.
|
||||
*/
|
||||
export function normalizeSectionCollapse(raw: unknown): SectionCollapse {
|
||||
const defaults = defaultSectionCollapse();
|
||||
if (!isRecord(raw)) return defaults;
|
||||
const out = defaults;
|
||||
for (const section of EDITOR_SECTIONS) {
|
||||
const value = raw[section];
|
||||
if (typeof value === 'boolean') out[section] = value;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
type ReadableStorage = Pick<Storage, 'getItem'>;
|
||||
type WritableStorage = Pick<Storage, 'setItem'>;
|
||||
|
||||
/** Читает состояние секций из браузера; без хранилища — дефолт. */
|
||||
export function loadSectionCollapse(
|
||||
storage?: ReadableStorage | null
|
||||
): SectionCollapse {
|
||||
const target = storage === undefined ? browserStorage() : storage;
|
||||
if (!target) return defaultSectionCollapse();
|
||||
try {
|
||||
const raw = target.getItem(EDITOR_SECTIONS_KEY);
|
||||
if (!raw) return defaultSectionCollapse();
|
||||
return normalizeSectionCollapse(JSON.parse(raw));
|
||||
} catch {
|
||||
return defaultSectionCollapse();
|
||||
}
|
||||
}
|
||||
|
||||
/** Сохраняет состояние секций в браузере; ошибки хранилища игнорируются. */
|
||||
export function saveSectionCollapse(
|
||||
state: SectionCollapse,
|
||||
storage?: WritableStorage | null
|
||||
): void {
|
||||
const target = storage === undefined ? browserStorage() : storage;
|
||||
if (!target) return;
|
||||
try {
|
||||
target.setItem(EDITOR_SECTIONS_KEY, JSON.stringify(state));
|
||||
} catch {
|
||||
// Приватный режим или переполненное хранилище не должны ломать панель.
|
||||
}
|
||||
}
|
||||
|
||||
function browserStorage(): Storage | null {
|
||||
return typeof localStorage === 'undefined' ? null : localStorage;
|
||||
}
|
||||
Reference in New Issue
Block a user