import { SocialLink } from '$lib/models/socials'; import type Sortable from 'sortablejs'; import { writable } from 'svelte/store'; interface EditorData { loadTime: number; editMode: boolean; draggingWidget: boolean; edits: EditorPublish; previews: EmbedCacheFetched['widgets']; widget?: BioSiteWidget | null; widgetPreview?: WidgetPreview | null; avatar?: string | null; banner?: string | null; sortables?: { left?: Sortable; right?: Sortable; }; socialSortables?: { links?: Sortable; texts?: Sortable; }; updatedSortable?: boolean; } interface EditorStore extends EditorData { toggleEditMode: () => void; setDraggingWidget: (draggingWidget: boolean) => void; setActive: (widget?: BioSiteWidget) => void; setPreview: (preview?: WidgetPreview) => void; editWidget: (widget: BioSiteWidget) => void; deleteWidget: (id: string | number) => void; applyIndex: () => void; setSortables: (left?: Sortable, right?: Sortable) => void; setDisplayName: (displayName: string) => void; setUsername: (username: string) => void; setDescription: (description: string) => void; setLocation: (location: string) => void; setSchool: (school: string) => void; setWorkplace: (workplace: string) => void; setAvatar: (file: File) => void; deleteAvatar: () => void; setBanner: (file: File) => void; deleteBanner: () => void; setSocials: ( createSocialLinks: BioSiteSocialLink[], createSocialTexts: BioSiteSocialText[], indexSocialLinks: IndexSocialLink[], indexSocialTexts: IndexSocialText[], deleteSocialLinks: number[], deleteSocialTexts: number[], ) => void; toggleUid: (uid: boolean) => void; reset: () => void; } export const generateResetState = (): EditorData => { return { loadTime: Date.now(), editMode: false, widget: null, widgetPreview: null, avatar: null, banner: null, edits: { edits: { createWidgets: [], updateWidgets: [], deleteWidgets: [], indexWidgets: [], alignWidgets: [], imageWidgets: [], updateDisplayName: null, updateUsername: null, updateDescription: null, updateLocation: null, updateSchool: null, updateWorkplace: null, deleteBanner: null, deleteAvatar: null, toggleUid: null, createSocialLinks: [], indexSocialLinks: [], deleteSocialLinks: [], createSocialTexts: [], indexSocialTexts: [], deleteSocialTexts: [], }, avatar: null, banner: null, }, previews: {}, updatedSortable: false, draggingWidget: false, }; }; export const editorStore = writable({ ...generateResetState(), toggleEditMode: () => { editorStore.update((store) => ({ ...store, editMode: !store.editMode })); }, setDraggingWidget: (draggingWidget) => { editorStore.update((store) => ({ ...store, draggingWidget, updatedSortable: true })); }, setActive: (widget) => { editorStore.update((store) => ({ ...store, widget })); }, setPreview(preview) { editorStore.update((store) => ({ ...store, widgetPreview: preview })); }, editWidget: (widget) => { editorStore.update((store) => { const edits = store.edits; if (widget.id.toString().startsWith('new+')) { const index = edits.edits.createWidgets.findIndex((edit) => edit.widgetId === widget.id); if (index !== -1) { edits.edits.createWidgets[index] = { ...edits.edits.createWidgets[index], ...widget, align: edits.edits.createWidgets[index].align, }; } else { edits.edits.createWidgets.push({ widgetId: widget.id, type: widget.type, visible: widget.visible, align: 2, index: -1, data: widget.data, }); } } else { const index = edits.edits.updateWidgets.findIndex((edit) => edit.widgetId === widget.id); if (index !== -1) { edits.edits.updateWidgets[index] = { ...edits.edits.updateWidgets[index], ...widget }; } else { edits.edits.updateWidgets.push({ widgetId: widget.id, align: 2, data: widget.data, }); } } if (!store.widgetPreview || !store.widget) return { ...store }; const previews = store.previews; previews[store.widget.id] = store.widgetPreview; return { ...store }; }); }, deleteWidget: (id) => { editorStore.update((store) => { const edits = store.edits; if (!id.toString().startsWith('new+') && typeof id === 'number') edits.edits.deleteWidgets.push({ widgetId: id }); return { ...store, edits: { ...edits, edits: { ...edits.edits, createWidgets: edits.edits.createWidgets.filter(function (edit) { return edit.widgetId !== id; }), updateWidgets: edits.edits.updateWidgets.filter(function (edit) { return edit.widgetId !== id; }), }, }, }; }); }, applyIndex: () => { editorStore.update((store) => { if (!store.sortables?.left || !store.sortables?.right) return store; const sortables = { left: store.sortables.left.toArray(), right: store.sortables.right.toArray(), }; const map = (id: string, index: number) => { if (id.toString().startsWith('new+')) { const edits = store.edits; const createIndex = edits.edits.createWidgets.findIndex((edit) => edit.widgetId === id); if (createIndex !== -1) { const align = sortables.right.findIndex((sortableId) => sortableId === id) !== -1 ? 2 : 0; edits.edits.createWidgets[createIndex] = { ...edits.edits.createWidgets[createIndex], index, align }; } return null; } return { widgetId: Number.parseInt(id), index, } as IndexWidget; }; const sorted = { left: sortables.left.map(map).filter(Boolean) as IndexWidget[], right: sortables.right.map(map).filter(Boolean) as IndexWidget[], }; const aligned = { left: sorted.left.map((widget) => ({ ...widget, align: 0 } as AlignWidget)) as AlignWidget[], right: sorted.right.map((widget) => ({ ...widget, align: 2 } as AlignWidget)) as AlignWidget[], }; return { ...store, edits: { ...store.edits, edits: { ...store.edits.edits, indexWidgets: [...sorted.left, ...sorted.right], alignWidgets: [...aligned.left, ...aligned.right], }, } as EditorPublish, }; }); }, setSortables: (left, right) => { editorStore.update((store) => ({ ...store, sortables: { left: left ? left : store.sortables?.left, right: right ? right : store.sortables?.right }, })); }, setDisplayName: (displayName) => { editorStore.update((store) => ({ ...store, edits: { ...store.edits, edits: { ...store.edits.edits, updateDisplayName: { newDisplayName: displayName }, }, }, })); }, setUsername: (username) => { editorStore.update((store) => ({ ...store, edits: { ...store.edits, edits: { ...store.edits.edits, updateUsername: { newUsername: username }, }, }, })); }, setDescription: (description) => { editorStore.update((store) => ({ ...store, edits: { ...store.edits, edits: { ...store.edits.edits, updateDescription: { newDescription: description }, }, }, })); }, setLocation: (location) => { editorStore.update((store) => ({ ...store, edits: { ...store.edits, edits: { ...store.edits.edits, updateLocation: { newLocation: location }, }, }, })); }, setSchool: (school) => { editorStore.update((store) => ({ ...store, edits: { ...store.edits, edits: { ...store.edits.edits, updateSchool: { newSchool: school }, }, }, })); }, setWorkplace: (workplace) => { editorStore.update((store) => ({ ...store, edits: { ...store.edits, edits: { ...store.edits.edits, updateWorkplace: { newWorkplace: workplace }, }, }, })); }, setAvatar: (file) => { const url = URL.createObjectURL(file); editorStore.update((store) => ({ ...store, avatar: url, edits: { ...store.edits, edits: { ...store.edits.edits, deleteAvatar: null, }, avatar: file, }, })); }, deleteAvatar: () => { editorStore.update((store) => ({ ...store, avatar: null, edits: { ...store.edits, edits: { ...store.edits.edits, deleteAvatar: {}, }, avatar: null, }, })); }, setBanner: (file) => { const url = URL.createObjectURL(file); editorStore.update((store) => ({ ...store, banner: url, edits: { ...store.edits, edits: { ...store.edits.edits, deleteBanner: null, }, banner: file, }, })); }, deleteBanner: () => { editorStore.update((store) => ({ ...store, banner: null, edits: { ...store.edits, edits: { ...store.edits.edits, deleteBanner: {}, }, banner: null, }, })); }, setSocials: ( createSocialLinks, createSocialTexts, indexSocialLinks, indexSocialTexts, deleteSocialLinks, deleteSocialTexts, ) => { editorStore.update((store) => { return { ...store, edits: { ...store.edits, edits: { ...store.edits.edits, createSocialLinks: createSocialLinks .filter((x) => x.type != 'UNKNOWN' && x.value) .map( (x) => ({ id: x.id, index: x.index, type: Object.values(SocialLink).indexOf(x.type), value: x.value, } as CreateSocialLink), ), createSocialTexts: createSocialTexts .filter((x) => x.value) .map((x) => ({ id: x.id, index: x.index, title: x.title, value: x.value })), indexSocialLinks, indexSocialTexts, deleteSocialLinks: deleteSocialLinks.map((x) => ({ id: x })), deleteSocialTexts: deleteSocialTexts.map((x) => ({ id: x })), }, }, }; }); }, toggleUid: (visible) => { editorStore.update((store) => ({ ...store, edits: { ...store.edits, edits: { ...store.edits.edits, toggleUid: { visible }, }, }, })); }, reset: () => { editorStore.update((store) => { return { ...store, ...generateResetState(), }; }); }, } as EditorStore); export const hasEdits = (edits: EditorStore['edits']) => { return ( [ ...edits.edits.createWidgets, ...edits.edits.updateWidgets, ...edits.edits.deleteWidgets, ...edits.edits.indexWidgets, ...edits.edits.alignWidgets, ...edits.edits.imageWidgets, ...edits.edits.createSocialTexts, ...edits.edits.indexSocialTexts, ...edits.edits.deleteSocialTexts, ...edits.edits.createSocialLinks, ...edits.edits.indexSocialLinks, ...edits.edits.deleteSocialLinks, ].length !== 0 || edits.edits.deleteAvatar || edits.edits.deleteBanner || edits.edits.updateDescription || edits.edits.updateDisplayName || edits.edits.updateLocation || edits.edits.updateUsername || edits.edits.updateSchool || edits.edits.updateWorkplace || edits.edits.toggleUid || edits.avatar || edits.banner ); };