Compare commits
5 Commits
master
...
preserve-a
Author | SHA1 | Date | |
---|---|---|---|
|
8984d8f19a | ||
|
b80706cd4a | ||
|
cf34cbdd30 | ||
|
6ead3ff839 | ||
|
d7f0d4ee21 |
290
src/appState.ts
290
src/appState.ts
@ -101,90 +101,228 @@ const APP_STATE_STORAGE_CONF = (<
|
||||
Values extends {
|
||||
/** whether to keep when storing to browser storage (localStorage/IDB) */
|
||||
browser: boolean;
|
||||
/** whether to keep when exporting to file/database */
|
||||
export: boolean;
|
||||
/** whether to keep when exporting to a text file */
|
||||
text: boolean;
|
||||
/** whether to keep when exporting to an image file */
|
||||
image: boolean;
|
||||
/** server (shareLink/collab/...) */
|
||||
server: boolean;
|
||||
},
|
||||
T extends Record<keyof AppState, Values>,
|
||||
>(config: { [K in keyof T]: K extends keyof AppState ? T[K] : never }) =>
|
||||
config)({
|
||||
theme: { browser: true, export: false, server: false },
|
||||
collaborators: { browser: false, export: false, server: false },
|
||||
currentChartType: { browser: true, export: false, server: false },
|
||||
currentItemBackgroundColor: { browser: true, export: false, server: false },
|
||||
currentItemEndArrowhead: { browser: true, export: false, server: false },
|
||||
currentItemFillStyle: { browser: true, export: false, server: false },
|
||||
currentItemFontFamily: { browser: true, export: false, server: false },
|
||||
currentItemFontSize: { browser: true, export: false, server: false },
|
||||
currentItemLinearStrokeSharpness: {
|
||||
theme: { browser: true, text: false, image: false, server: false },
|
||||
collaborators: { browser: false, text: false, image: false, server: false },
|
||||
currentChartType: { browser: true, text: false, image: false, server: false },
|
||||
currentItemBackgroundColor: {
|
||||
browser: true,
|
||||
export: false,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemEndArrowhead: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemFillStyle: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemFontFamily: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemFontSize: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemLinearStrokeSharpness: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemOpacity: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemRoughness: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemStartArrowhead: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemStrokeColor: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemStrokeSharpness: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemStrokeStyle: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemStrokeWidth: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemTextAlign: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
cursorButton: { browser: true, text: false, image: false, server: false },
|
||||
draggingElement: { browser: false, text: false, image: false, server: false },
|
||||
editingElement: { browser: false, text: false, image: false, server: false },
|
||||
editingGroupId: { browser: true, text: false, image: false, server: false },
|
||||
editingLinearElement: {
|
||||
browser: false,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
activeTool: { browser: true, text: false, image: false, server: false },
|
||||
penMode: { browser: true, text: false, image: false, server: false },
|
||||
penDetected: { browser: true, text: false, image: false, server: false },
|
||||
errorMessage: { browser: false, text: false, image: false, server: false },
|
||||
exportBackground: { browser: true, text: false, image: true, server: false },
|
||||
exportEmbedScene: { browser: true, text: false, image: true, server: false },
|
||||
exportScale: { browser: true, text: false, image: true, server: false },
|
||||
exportWithDarkMode: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: true,
|
||||
server: false,
|
||||
},
|
||||
fileHandle: { browser: false, text: false, image: false, server: false },
|
||||
gridSize: { browser: true, text: true, image: true, server: true },
|
||||
height: { browser: false, text: false, image: false, server: false },
|
||||
isBindingEnabled: {
|
||||
browser: false,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
isLibraryOpen: { browser: true, text: false, image: false, server: false },
|
||||
isLibraryMenuDocked: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
isLoading: { browser: false, text: false, image: false, server: false },
|
||||
isResizing: { browser: false, text: false, image: false, server: false },
|
||||
isRotating: { browser: false, text: false, image: false, server: false },
|
||||
lastPointerDownWith: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
multiElement: { browser: false, text: false, image: false, server: false },
|
||||
name: { browser: true, text: false, image: false, server: false },
|
||||
offsetLeft: { browser: false, text: false, image: false, server: false },
|
||||
offsetTop: { browser: false, text: false, image: false, server: false },
|
||||
openMenu: { browser: true, text: false, image: false, server: false },
|
||||
openPopup: { browser: false, text: false, image: false, server: false },
|
||||
pasteDialog: { browser: false, text: false, image: false, server: false },
|
||||
previousSelectedElementIds: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
resizingElement: { browser: false, text: false, image: false, server: false },
|
||||
scrolledOutside: { browser: true, text: false, image: false, server: false },
|
||||
scrollX: { browser: true, text: false, image: false, server: false },
|
||||
scrollY: { browser: true, text: false, image: false, server: false },
|
||||
selectedElementIds: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
selectedGroupIds: { browser: true, text: false, image: false, server: false },
|
||||
selectionElement: {
|
||||
browser: false,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
shouldCacheIgnoreZoom: {
|
||||
browser: true,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
showHelpDialog: { browser: false, text: false, image: false, server: false },
|
||||
showStats: { browser: true, text: false, image: false, server: false },
|
||||
startBoundElement: {
|
||||
browser: false,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
suggestedBindings: {
|
||||
browser: false,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
toastMessage: { browser: false, text: false, image: false, server: false },
|
||||
viewBackgroundColor: {
|
||||
browser: true,
|
||||
text: true,
|
||||
image: true,
|
||||
server: true,
|
||||
},
|
||||
width: { browser: false, text: false, image: false, server: false },
|
||||
zenModeEnabled: { browser: true, text: false, image: false, server: false },
|
||||
zoom: { browser: true, text: false, image: false, server: false },
|
||||
viewModeEnabled: { browser: false, text: false, image: false, server: false },
|
||||
pendingImageElementId: {
|
||||
browser: false,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
showHyperlinkPopup: {
|
||||
browser: false,
|
||||
text: false,
|
||||
image: false,
|
||||
server: false,
|
||||
},
|
||||
currentItemOpacity: { browser: true, export: false, server: false },
|
||||
currentItemRoughness: { browser: true, export: false, server: false },
|
||||
currentItemStartArrowhead: { browser: true, export: false, server: false },
|
||||
currentItemStrokeColor: { browser: true, export: false, server: false },
|
||||
currentItemStrokeSharpness: { browser: true, export: false, server: false },
|
||||
currentItemStrokeStyle: { browser: true, export: false, server: false },
|
||||
currentItemStrokeWidth: { browser: true, export: false, server: false },
|
||||
currentItemTextAlign: { browser: true, export: false, server: false },
|
||||
cursorButton: { browser: true, export: false, server: false },
|
||||
draggingElement: { browser: false, export: false, server: false },
|
||||
editingElement: { browser: false, export: false, server: false },
|
||||
editingGroupId: { browser: true, export: false, server: false },
|
||||
editingLinearElement: { browser: false, export: false, server: false },
|
||||
activeTool: { browser: true, export: false, server: false },
|
||||
penMode: { browser: true, export: false, server: false },
|
||||
penDetected: { browser: true, export: false, server: false },
|
||||
errorMessage: { browser: false, export: false, server: false },
|
||||
exportBackground: { browser: true, export: false, server: false },
|
||||
exportEmbedScene: { browser: true, export: false, server: false },
|
||||
exportScale: { browser: true, export: false, server: false },
|
||||
exportWithDarkMode: { browser: true, export: false, server: false },
|
||||
fileHandle: { browser: false, export: false, server: false },
|
||||
gridSize: { browser: true, export: true, server: true },
|
||||
height: { browser: false, export: false, server: false },
|
||||
isBindingEnabled: { browser: false, export: false, server: false },
|
||||
isLibraryOpen: { browser: true, export: false, server: false },
|
||||
isLibraryMenuDocked: { browser: true, export: false, server: false },
|
||||
isLoading: { browser: false, export: false, server: false },
|
||||
isResizing: { browser: false, export: false, server: false },
|
||||
isRotating: { browser: false, export: false, server: false },
|
||||
lastPointerDownWith: { browser: true, export: false, server: false },
|
||||
multiElement: { browser: false, export: false, server: false },
|
||||
name: { browser: true, export: false, server: false },
|
||||
offsetLeft: { browser: false, export: false, server: false },
|
||||
offsetTop: { browser: false, export: false, server: false },
|
||||
openMenu: { browser: true, export: false, server: false },
|
||||
openPopup: { browser: false, export: false, server: false },
|
||||
pasteDialog: { browser: false, export: false, server: false },
|
||||
previousSelectedElementIds: { browser: true, export: false, server: false },
|
||||
resizingElement: { browser: false, export: false, server: false },
|
||||
scrolledOutside: { browser: true, export: false, server: false },
|
||||
scrollX: { browser: true, export: false, server: false },
|
||||
scrollY: { browser: true, export: false, server: false },
|
||||
selectedElementIds: { browser: true, export: false, server: false },
|
||||
selectedGroupIds: { browser: true, export: false, server: false },
|
||||
selectionElement: { browser: false, export: false, server: false },
|
||||
shouldCacheIgnoreZoom: { browser: true, export: false, server: false },
|
||||
showHelpDialog: { browser: false, export: false, server: false },
|
||||
showStats: { browser: true, export: false, server: false },
|
||||
startBoundElement: { browser: false, export: false, server: false },
|
||||
suggestedBindings: { browser: false, export: false, server: false },
|
||||
toastMessage: { browser: false, export: false, server: false },
|
||||
viewBackgroundColor: { browser: true, export: true, server: true },
|
||||
width: { browser: false, export: false, server: false },
|
||||
zenModeEnabled: { browser: true, export: false, server: false },
|
||||
zoom: { browser: true, export: false, server: false },
|
||||
viewModeEnabled: { browser: false, export: false, server: false },
|
||||
pendingImageElementId: { browser: false, export: false, server: false },
|
||||
showHyperlinkPopup: { browser: false, export: false, server: false },
|
||||
});
|
||||
|
||||
const _clearAppStateForStorage = <
|
||||
ExportType extends "export" | "browser" | "server",
|
||||
ExportType extends "image" | "text" | "browser" | "server",
|
||||
>(
|
||||
appState: Partial<AppState>,
|
||||
exportType: ExportType,
|
||||
@ -211,8 +349,12 @@ export const clearAppStateForLocalStorage = (appState: Partial<AppState>) => {
|
||||
return _clearAppStateForStorage(appState, "browser");
|
||||
};
|
||||
|
||||
export const cleanAppStateForExport = (appState: Partial<AppState>) => {
|
||||
return _clearAppStateForStorage(appState, "export");
|
||||
export const cleanAppStateForTextExport = (appState: Partial<AppState>) => {
|
||||
return _clearAppStateForStorage(appState, "text");
|
||||
};
|
||||
|
||||
export const cleanAppStateForImageExport = (appState: Partial<AppState>) => {
|
||||
return _clearAppStateForStorage(appState, "image");
|
||||
};
|
||||
|
||||
export const clearAppStateForDatabase = (appState: Partial<AppState>) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { cleanAppStateForImageExport } from "../appState";
|
||||
import { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
import { ExcalidrawElement, FileId } from "../element/types";
|
||||
@ -143,7 +143,7 @@ export const loadSceneOrLibraryFromBlob = async (
|
||||
appState: {
|
||||
theme: localAppState?.theme,
|
||||
fileHandle: fileHandle || blob.handle || null,
|
||||
...cleanAppStateForExport(data.appState || {}),
|
||||
...cleanAppStateForImageExport(data.appState || {}),
|
||||
...(localAppState
|
||||
? calculateScrollCenter(
|
||||
data.elements || [],
|
||||
|
@ -82,7 +82,7 @@ export const exportCanvas = async (
|
||||
await import(/* webpackChunkName: "image" */ "./image")
|
||||
).encodePngMetadata({
|
||||
blob,
|
||||
metadata: serializeAsJSON(elements, appState, files, "local"),
|
||||
metadata: serializeAsJSON(elements, appState, files, "image"),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { fileOpen, fileSave } from "./filesystem";
|
||||
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
|
||||
import {
|
||||
cleanAppStateForImageExport,
|
||||
cleanAppStateForTextExport,
|
||||
clearAppStateForDatabase,
|
||||
} from "../appState";
|
||||
import {
|
||||
EXPORT_DATA_TYPES,
|
||||
EXPORT_SOURCE,
|
||||
@ -43,25 +47,32 @@ export const serializeAsJSON = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: Partial<AppState>,
|
||||
files: BinaryFiles,
|
||||
type: "local" | "database",
|
||||
destination: "text" | "image" | "database",
|
||||
): string => {
|
||||
const cleanAppState = () => {
|
||||
switch (destination) {
|
||||
case "database":
|
||||
return clearAppStateForDatabase(appState);
|
||||
case "text":
|
||||
return cleanAppStateForTextExport(appState);
|
||||
case "image":
|
||||
return cleanAppStateForImageExport(appState);
|
||||
}
|
||||
};
|
||||
const data: ExportedDataState = {
|
||||
type: EXPORT_DATA_TYPES.excalidraw,
|
||||
version: VERSIONS.excalidraw,
|
||||
source: EXPORT_SOURCE,
|
||||
elements:
|
||||
type === "local"
|
||||
? clearElementsForExport(elements)
|
||||
: clearElementsForDatabase(elements),
|
||||
appState:
|
||||
type === "local"
|
||||
? cleanAppStateForExport(appState)
|
||||
: clearAppStateForDatabase(appState),
|
||||
destination === "database"
|
||||
? clearElementsForDatabase(elements)
|
||||
: clearElementsForExport(elements),
|
||||
appState: cleanAppState(),
|
||||
files:
|
||||
type === "local"
|
||||
? filterOutDeletedFiles(elements, files)
|
||||
: // will be stripped from JSON
|
||||
undefined,
|
||||
destination === "database"
|
||||
? // will be stripped from JSON
|
||||
undefined
|
||||
: filterOutDeletedFiles(elements, files),
|
||||
};
|
||||
|
||||
return JSON.stringify(data, null, 2);
|
||||
@ -72,7 +83,7 @@ export const saveAsJSON = async (
|
||||
appState: AppState,
|
||||
files: BinaryFiles,
|
||||
) => {
|
||||
const serialized = serializeAsJSON(elements, appState, files, "local");
|
||||
const serialized = serializeAsJSON(elements, appState, files, "text");
|
||||
const blob = new Blob([serialized], {
|
||||
type: MIME_TYPES.excalidraw,
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
LibraryItems,
|
||||
LibraryItems_anyVersion,
|
||||
} from "../types";
|
||||
import type { cleanAppStateForExport } from "../appState";
|
||||
import type { cleanAppStateForTextExport } from "../appState";
|
||||
import { VERSIONS } from "../constants";
|
||||
|
||||
export interface ExportedDataState {
|
||||
@ -13,7 +13,7 @@ export interface ExportedDataState {
|
||||
version: number;
|
||||
source: string;
|
||||
elements: readonly ExcalidrawElement[];
|
||||
appState: ReturnType<typeof cleanAppStateForExport>;
|
||||
appState: ReturnType<typeof cleanAppStateForTextExport>;
|
||||
files: BinaryFiles | undefined;
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,7 @@ export const exportToBlob = async (
|
||||
opts.elements,
|
||||
opts.appState,
|
||||
opts.files || {},
|
||||
"local",
|
||||
"image",
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ export const exportToSvg = async (
|
||||
metadata = await (
|
||||
await import(/* webpackChunkName: "image" */ "../../src/data/image")
|
||||
).encodeSvgMetadata({
|
||||
text: serializeAsJSON(elements, appState, files || {}, "local"),
|
||||
text: serializeAsJSON(elements, appState, files || {}, "image"),
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
@ -45,7 +45,7 @@ describe("export", () => {
|
||||
const pngBlob = await API.loadFile("./fixtures/smiley.png");
|
||||
const pngBlobEmbedded = await encodePngMetadata({
|
||||
blob: pngBlob,
|
||||
metadata: serializeAsJSON(testElements, h.state, {}, "local"),
|
||||
metadata: serializeAsJSON(testElements, h.state, {}, "image"),
|
||||
});
|
||||
API.drop(pngBlobEmbedded);
|
||||
|
||||
@ -58,7 +58,7 @@ describe("export", () => {
|
||||
|
||||
it("test encoding/decoding scene for SVG export", async () => {
|
||||
const encoded = await encodeSvgMetadata({
|
||||
text: serializeAsJSON(testElements, h.state, {}, "local"),
|
||||
text: serializeAsJSON(testElements, h.state, {}, "image"),
|
||||
});
|
||||
const decoded = JSON.parse(await decodeSvgMetadata({ svg: encoded }));
|
||||
expect(decoded.elements).toEqual([
|
||||
|
@ -93,7 +93,7 @@ exports[`exportToSvg with elements that have a link 1`] = `
|
||||
exports[`exportToSvg with exportEmbedScene 1`] = `
|
||||
"
|
||||
<!-- svg-source:excalidraw -->
|
||||
<!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1SPW/CMFx1MDAxMN35XHUwMDE1kbtcIpGk4aNstFRVpapcdTAwMWRcdTAwMTiQWnUw8YVYMbaxXHUwMDFkPoT477VccsRtxNpcclx1MDAwZpbu+b278907dKJcYpm9XHUwMDA0NI5cdTAwMTDscswoUXiLulx1MDAwZd+A0lRw+5T6WIta5Z5ZXHUwMDFhI8e9XHUwMDFlXHUwMDEzVlBcbm1OfGCwXHUwMDAybrRlfNk4ilx1MDAwZf62L5Q41Wau1lx1MDAxZpOiopyk63w1fJtOXj691JN2lpMlWVx1MDAxM+9d4fthXHUwMDEzbykxpcWSOG6wXHUwMDEy6LI0LVx1MDAxMPMlc21cdTAwMDZEXHUwMDFiJSp4XHUwMDEyTCjXyF3sTyi9wHm1VKLmJHCSPsaLXCJwXG7K2Mzs2WlcdTAwMDA4L2tcdTAwMDWoVWF+abGFNzot7ICDypZcXJZcdTAwMWO0/qNcdTAwMTFcdTAwMTLn1Oxbv3L9yVfip/vdzl9iJc95kHbBr85cdTAwMDCIT5Ulg/7wIVx1MDAxZTUvYb9JXHUwMDFht9F3wf2uk2Q0iuMsXHUwMDFkXHUwMDBlXHUwMDFhXHUwMDA21VO7auPTXHUwMDE2mGlcYnN0I3xcdTAwMGU24DVjzWMtXHQ+icJXXHUwMDE55VWbZ11VXcl9cSmheCU4QVx1MDAxZT92b0a7XHUwMDE57X+MXHUwMDA2jFGp4Ww0e/thICzlzNj8lnKyXHUwMDFk2lDYPl5ZbOGP03ubusWCa/Zw7Fx1MDAxY39cdTAwMDCLqmbvIn0=<!-- payload-end -->
|
||||
<!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1UPW/CMFx1MDAxMN35XHUwMDE1kbtcIpFQvspcdTAwMDZcdTAwMDVVlap2YKjUqoOJL8SKsY3t8CHEf69tQlxcXCLWbmSwdM/v3p3vnnJsRVx1MDAxMTJcdTAwMDdcdGhcdTAwMWMh2KeYUaLwXHUwMDBltVx1MDAxZL5cdTAwMDWlqeD2qutjLUqVemZujFx1MDAxY3c6TNiEXFxoc+ZcdTAwMDODNXCjLePbxlF09Ke9ocRlbT/V5mOSXHUwMDE1lJPuJl1cdTAwMGbfZpOXL5/qSXvL6SW9Oj64wo/DOt5RYnKLJXFcXGM50FVuXHUwMDFhIOYr5tpcZog2Slx1MDAxNPAsmFCukYfYf6H0XHUwMDEyp8VKiZKTwEn6XHUwMDE4L7PAyShjXHUwMDBic2DnXHUwMDAx4DQvXHUwMDE1oEaFz0uLXHK8ztPCXHUwMDBlOGTZkqucg9ZXOULilJpD41WuP/lK/HR/mvo5VrLSQdpcdTAwMDV/Olx1MDAwMyBeqpdcZvrDp3hU34T9Jt24ib5cdTAwMGLud50ko1FcdTAwMWP3usNBzaB6ZldtvGyGmYYwRzfCebBcdTAwMDEvXHUwMDE5qy9LSfA5KTyVUV40edZVxVxy7YtLXHTFa8FcdPL4qX032t1o/2M0YIxKXHKV0ezph4GwlFx1MDAwYmP1LeVsO/vflEKZab3aa0W0pbCb3th75r9qfpXIfL1cdTAwMDSySIE7eaNKaFVcdTAwMWV3rlx1MDAwMPfS46l1+lx1MDAwNa05enEifQ==<!-- payload-end -->
|
||||
<defs>
|
||||
<style>
|
||||
@font-face {
|
||||
|
Loading…
x
Reference in New Issue
Block a user