diff --git a/public/editor-client/package-lock.json b/public/editor-client/package-lock.json index 1390d184c4..22fea40118 100644 --- a/public/editor-client/package-lock.json +++ b/public/editor-client/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "fp-utilities": "^1.1.4" + "fp-utilities": "^1.1.4", + "valtio": "^1.10.6" }, "devDependencies": { "@babel/parser": "^7.21.8", @@ -6049,8 +6050,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -6299,6 +6299,18 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7419,6 +7431,11 @@ "node": ">= 6" } }, + "node_modules/proxy-compare": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz", + "integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==" + }, "node_modules/pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -7483,6 +7500,18 @@ } ] }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -9129,6 +9158,14 @@ "node": ">=0.10.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9177,6 +9214,26 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/valtio": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.10.6.tgz", + "integrity": "sha512-SxN1bHUmdhW6V8qsQTpCgJEwp7uHbntuH0S9cdLQtiohuevwBksbpXjwj5uDMA7bLwg1WKyq9sEpZrx3TIMrkA==", + "dependencies": { + "proxy-compare": "2.5.1", + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/value-or-function": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", diff --git a/public/editor-client/package.json b/public/editor-client/package.json index e490a1f0ca..f6053f9670 100644 --- a/public/editor-client/package.json +++ b/public/editor-client/package.json @@ -18,7 +18,8 @@ "author": "brizy", "license": "MIT", "dependencies": { - "fp-utilities": "^1.1.4" + "fp-utilities": "^1.1.4", + "valtio": "^1.10.6" }, "devDependencies": { "@babel/parser": "^7.21.8", diff --git a/public/editor-client/src/api/index.ts b/public/editor-client/src/api/index.ts index f0848b841d..a7ad43071e 100644 --- a/public/editor-client/src/api/index.ts +++ b/public/editor-client/src/api/index.ts @@ -1,3 +1,5 @@ +import { ClassSymbol } from "src/types/Symbols"; +import { State } from "src/valtio/types"; import { getConfig } from "../config"; import { Page } from "../types/Page"; import { Rule } from "../types/PopupConditions"; @@ -739,3 +741,86 @@ export const updatePopupRules = async ( }; //#endregion + +//#region Symbols + +export const createSymbol = async (data: ClassSymbol[]): Promise => { + const config = getConfig(); + + if (!config) { + throw new Error(t("Invalid __BRZ_PLUGIN_ENV__ at createSymbols")); + } + + const { editorVersion, url: _url, hash, actions } = config; + + const url = makeUrl(_url, { + action: actions.symbolCreate, + version: editorVersion, + hash + }); + + return request(url, { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8" + }, + body: JSON.stringify(data) + }); +}; + +export const updateSymbol = async (data: ClassSymbol[]): Promise => { + const config = getConfig(); + + if (!config) { + throw new Error(t("Invalid __BRZ_PLUGIN_ENV__ at update symbol")); + } + + const { editorVersion, url: _url, hash, actions } = config; + + const url = makeUrl(_url, { + action: actions.symbolUpdate, + version: editorVersion, + hash + }); + + return request(url, { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8" + }, + body: JSON.stringify(data) + }); +}; + +export const deleteSymbol = async (data: ClassSymbol[]): Promise => { + const config = getConfig(); + + if (!config) { + throw new Error(t("Invalid __BRZ_PLUGIN_ENV__ at delete symbol")); + } + + const { editorVersion, url: _url, hash, actions } = config; + + const url = makeUrl(_url, { + action: actions.symbolDelete, + version: editorVersion, + hash + }); + + return request(url, { + method: "DELETE", + body: JSON.stringify(data) + }); +}; + +export const updateSymbols = async (store: Readonly) => { + const { toCreate, toUpdate, toDelete } = store.symbols; + + const createPromise = createSymbol(toCreate); + const updatePromise = updateSymbol(toUpdate); + const deletePromise = deleteSymbol(toDelete); + + await Promise.all([createPromise, updatePromise, deletePromise]); +}; + +//#endregion diff --git a/public/editor-client/src/config.ts b/public/editor-client/src/config.ts index bb19633d80..b3eee0e4f6 100644 --- a/public/editor-client/src/config.ts +++ b/public/editor-client/src/config.ts @@ -40,6 +40,11 @@ interface Actions { searchPosts: string; getPostObjects: string; + + symbolCreate: string; + symbolList: string; + symbolUpdate: string; + symbolDelete: string; } interface API { @@ -184,6 +189,22 @@ const actionsReader = parseStrict({ getPostObjects: pipe( mPipe(Obj.readKey("getPostObjects"), Str.read), throwOnNullish("Invalid actions: getPostObjects") + ), + symbolList: pipe( + mPipe(Obj.readKey("symbolList"), Str.read), + throwOnNullish("Invalid actions: symbolList") + ), + symbolCreate: pipe( + mPipe(Obj.readKey("symbolCreate"), Str.read), + throwOnNullish("Invalid actions: symbolCreate") + ), + symbolUpdate: pipe( + mPipe(Obj.readKey("symbolUpdate"), Str.read), + throwOnNullish("Invalid actions: symbolUpdate") + ), + symbolDelete: pipe( + mPipe(Obj.readKey("symbolDelete"), Str.read), + throwOnNullish("Invalid actions: symbolDelete") ) }); diff --git a/public/editor-client/src/onChange/index.ts b/public/editor-client/src/onChange/index.ts index 2238ea28a6..104072fb84 100644 --- a/public/editor-client/src/onChange/index.ts +++ b/public/editor-client/src/onChange/index.ts @@ -1,5 +1,6 @@ import { updatePage, updateProject } from "../api"; import { OnChange } from "../types/OnChange"; +import { handleSymbols } from "./symbols"; export const onChange = (data: OnChange) => { if (data.projectData) { @@ -9,4 +10,8 @@ export const onChange = (data: OnChange) => { if (data.pageData) { updatePage(data.pageData, { is_autosave: 0 }); } + + if (data.symbols) { + handleSymbols(data.symbols); + } }; diff --git a/public/editor-client/src/onChange/symbols.ts b/public/editor-client/src/onChange/symbols.ts new file mode 100644 index 0000000000..1b4b82a4d0 --- /dev/null +++ b/public/editor-client/src/onChange/symbols.ts @@ -0,0 +1,32 @@ +import { isSymbol } from "src/symbols/utils"; +import { SymbolAction } from "src/types/Symbols"; +import { store } from "src/valtio"; + +export const handleSymbols = (data: SymbolAction): void => { + const { type, payload } = data; + + if (type && payload && isSymbol(payload)) { + switch (type) { + case "CREATE": { + store.symbols.toCreate.push(payload); + break; + } + case "UPDATE": { + const candidateIndex = store.symbols.toUpdate.findIndex( + (s) => s.uid === payload.uid + ); + + if (candidateIndex === -1) { + store.symbols.toUpdate.push(payload); + } + + store.symbols.toUpdate[candidateIndex] = payload; + break; + } + case "DELETE": { + store.symbols.toDelete.push(payload); + break; + } + } + } +}; diff --git a/public/editor-client/src/publish/index.ts b/public/editor-client/src/publish/index.ts index cb7e15eb78..ca94536f68 100644 --- a/public/editor-client/src/publish/index.ts +++ b/public/editor-client/src/publish/index.ts @@ -1,10 +1,14 @@ -import { updatePage, updateProject } from "../api"; +import { store } from "src/valtio"; +import { State } from "src/valtio/types"; +import { clearSymbols } from "src/valtio/utils"; +import { snapshot } from "valtio/vanilla"; +import { updatePage, updateProject, updateSymbols } from "../api"; import { Publish } from "../types/Publish"; import { t } from "../utils/i18n"; export const publish: Publish = { async handler(res, rej, args) { - const { projectData, pageData } = args; + const { projectData, pageData, symbols } = args; if (projectData) { try { @@ -23,5 +27,18 @@ export const publish: Publish = { rej(t("Failed to update page")); } } + + if (symbols) { + try { + const snap = snapshot(store) as Readonly; + + await updateSymbols(snap); + + clearSymbols(store); + res(args); + } catch (error) { + rej(t("Failed to update symbols")); + } + } } }; diff --git a/public/editor-client/src/symbols/index.ts b/public/editor-client/src/symbols/index.ts new file mode 100644 index 0000000000..ebae898771 --- /dev/null +++ b/public/editor-client/src/symbols/index.ts @@ -0,0 +1,62 @@ +// import { createSymbol, deleteSymbol, getSymbols, updateSymbol } from "src/api"; +// import { Symbols } from "src/types/Symbols"; +// import { t } from "src/utils/i18n"; + +// export const symbols: Symbols = { +// async get(res, rej) { +// try { +// const data = await getSymbols(); + +// if (!data) { +// return rej(t("Could not get symbols")); +// } + +// res(data); +// } catch (e) { +// rej(t("Failed to get symbols")); +// } +// }, +// async create(res, rej, extra) { +// try { +// const data = await createSymbol(extra); + +// if (!data.data) { +// return rej(t("Could not create symbols")); +// } + +// res(data.data); +// } catch (error) { +// rej(t("Failed to create symbol")); +// } +// }, +// async update(res, rej, extra) { +// try { +// const data = await updateSymbol(extra); + +// if (!data.data) { +// return rej(t("Could not update symbols")); +// } + +// console.log("data: ", data); + +// res(data.data); +// } catch (error) { +// rej(t("Failed to update symbol")); +// } +// }, +// async delete(res, rej, extra) { +// try { +// const data = await deleteSymbol(extra); + +// if (!data.success) { +// return rej(t("Could not delete symbols")); +// } + +// res(data.success); +// } catch (error) { +// rej(t("Failed to delete symbol")); +// } +// } +// }; + +export {}; diff --git a/public/editor-client/src/symbols/utils.ts b/public/editor-client/src/symbols/utils.ts new file mode 100644 index 0000000000..ab672e24cd --- /dev/null +++ b/public/editor-client/src/symbols/utils.ts @@ -0,0 +1,14 @@ +import { ClassSymbol } from "src/types/Symbols"; +import * as Obj from "src/utils/reader/object"; + +export const isSymbol = (s: unknown): s is ClassSymbol => { + const symbol = s as ClassSymbol; + + return ( + !!symbol.uid && + !!symbol.label && + !!symbol.version && + !!symbol.data && + Obj.isObject(symbol.data) + ); +}; diff --git a/public/editor-client/src/types/OnChange.ts b/public/editor-client/src/types/OnChange.ts index 4ee08043f9..d2c5db88a1 100644 --- a/public/editor-client/src/types/OnChange.ts +++ b/public/editor-client/src/types/OnChange.ts @@ -1,5 +1,6 @@ import { Page } from "./Page"; import { Project } from "./Project"; +import { SymbolAction } from "./Symbols"; export interface OnChange { // TODO Currently only projectData and pageData is used @@ -7,4 +8,5 @@ export interface OnChange { projectData: Project; pageData: Page; // globalBlocks: Array; + symbols: SymbolAction; } diff --git a/public/editor-client/src/types/Publish.ts b/public/editor-client/src/types/Publish.ts index addd6abf2f..2feee0ce7e 100644 --- a/public/editor-client/src/types/Publish.ts +++ b/public/editor-client/src/types/Publish.ts @@ -1,12 +1,14 @@ import { Page } from "./Page"; import { Project } from "./Project"; import { Response } from "./Response"; +import { ClassSymbol } from "./Symbols"; export interface Data { // TODO Currently only projectData and pageData is used // Need to add globalBlocks projectData?: Project; pageData?: Page; + symbols: ClassSymbol[]; // globalBlocks: Array; } diff --git a/public/editor-client/src/types/Symbols.ts b/public/editor-client/src/types/Symbols.ts new file mode 100644 index 0000000000..02d6855f09 --- /dev/null +++ b/public/editor-client/src/types/Symbols.ts @@ -0,0 +1,58 @@ +import { Response } from "./Response"; + +export interface ClassSymbol { + uid: string; + label: string; + data: { className: string; type: string; model: Record }; + version: string; +} + +export interface SymbolCreateResponse { + success: boolean; + data?: ClassSymbol[]; +} + +export interface SymbolUpdateResponse { + success: boolean; + data?: ClassSymbol[]; +} + +export interface SymbolDeleteResponse { + success: boolean; +} + +interface SymbolCreate { + type: "CREATE"; + payload: ClassSymbol; +} + +interface SymbolUpdate { + type: "UPDATE"; + payload: ClassSymbol; +} + +interface SymbolDelete { + type: "DELETE"; + payload: string; +} + +export type SymbolAction = SymbolCreate | SymbolUpdate | SymbolDelete; + +export interface Symbols { + get: (res: Response, rej: Response) => void; + create: ( + res: Response, + rej: Response, + extra: ClassSymbol[] + ) => void; + update: ( + res: Response, + rej: Response, + extra: ClassSymbol[] + ) => void; + delete: ( + res: Response, + rej: Response, + extra: { uid: string } + ) => void; +} diff --git a/public/editor-client/src/valtio/index.ts b/public/editor-client/src/valtio/index.ts new file mode 100644 index 0000000000..b1c96ceb9d --- /dev/null +++ b/public/editor-client/src/valtio/index.ts @@ -0,0 +1,10 @@ +import { proxy } from "valtio/vanilla"; +import type { State } from "./types"; + +export const store = proxy({ + symbols: { + toCreate: [], + toUpdate: [], + toDelete: [] + } +}); diff --git a/public/editor-client/src/valtio/types.ts b/public/editor-client/src/valtio/types.ts new file mode 100644 index 0000000000..488b2aea8d --- /dev/null +++ b/public/editor-client/src/valtio/types.ts @@ -0,0 +1,9 @@ +import { ClassSymbol } from "src/types/Symbols"; + +export interface State { + symbols: { + toCreate: ClassSymbol[]; + toUpdate: ClassSymbol[]; + toDelete: ClassSymbol[]; + }; +} diff --git a/public/editor-client/src/valtio/utils.ts b/public/editor-client/src/valtio/utils.ts new file mode 100644 index 0000000000..a1670e897c --- /dev/null +++ b/public/editor-client/src/valtio/utils.ts @@ -0,0 +1,7 @@ +import { State } from "./types"; + +export const clearSymbols = (store: State): void => { + store.symbols.toCreate = []; + store.symbols.toUpdate = []; + store.symbols.toDelete = []; +};