Skip to content

Commit

Permalink
Merge pull request #1992 from ThemeFuse/22690-Custom-SVG-For-Icon
Browse files Browse the repository at this point in the history
Added custom icons in editor
  • Loading branch information
maxval1 committed May 1, 2024
2 parents 7f38a52 + 345c031 commit 695ac3c
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 7 deletions.
80 changes: 77 additions & 3 deletions public/editor-client/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Arr, Obj, Str } from "@brizy/readers";
import { Config, getConfig } from "@/config";
import { ConfigDCItem } from "@/types/DynamicContent";
import { GlobalBlock } from "@/types/GlobalBlocks";
import { IconUploadData } from "@/types/Icon";
import { Page } from "@/types/Page";
import { Rule } from "@/types/PopupConditions";
import { Project } from "@/types/Project";
import { ResponseWithBody } from "@/types/Response";
import { ConfigDCItem } from "@/types/DynamicContent";
import {
CreateSavedBlock,
CreateSavedLayout,
Expand All @@ -15,8 +15,9 @@ import {
SavedLayoutMeta
} from "@/types/SavedBlocks";
import { ScreenshotData } from "@/types/Screenshots";
import { Dictionary } from "../types/utils";
import { t } from "@/utils/i18n";
import { Arr, Obj, Str } from "@brizy/readers";
import { Dictionary } from "../types/utils";
import { Literal } from "../utils/types";
import {
GetCollections,
Expand Down Expand Up @@ -415,6 +416,7 @@ export interface UploadSavedBlocksData {
errors: Array<{ uid: string; message: string }>;
success: Array<SavedBlock>;
}

export const uploadSaveBlocks = async (
files: Array<File>
): Promise<UploadSavedBlocksData> => {
Expand Down Expand Up @@ -1262,3 +1264,75 @@ export const updateGlobalBlocks = async (
};

//#endregion

//#region CustomIcon
export const getCustomIcons = async (): Promise<IconUploadData[]> => {
const config = getConfig();
if (!config) {
throw new Error(t("Invalid __BRZ_PLUGIN_ENV__"));
}
const { api } = config;

const url = makeUrl(api.iconsUrl, {
"orderBy[id]": "DESC",
count: "1000"
});

const response = await request(url, {
method: "GET"
});

if (response.ok) {
const { data } = await response.json();
return data;
}

throw new Error(t("Failed to get icons"));
};

export const uploadIcon = async (
attachment: string,
filename: string
): Promise<IconUploadData> => {
const config = getConfig();
if (!config) {
throw new Error(t("Invalid __BRZ_PLUGIN_ENV__"));
}

const { api } = config;
const response = await request(api.uploadIconUrl, {
method: "POST",
body: new URLSearchParams({
attachment,
filename
})
});

if (response.ok) {
const { data } = await response.json();
return data;
}

throw new Error(t("Failed to upload icon"));
};

export const deleteIcon = async (uid: string): Promise<Response> => {
const config = getConfig();

if (!config) {
throw new Error(t("Invalid __BRZ_PLUGIN_ENV__"));
}

const { api } = config;

const response = await request(`${api.deleteIconUrl}${uid}`, {
method: "DELETE"
});

if (response.ok) {
return response;
}

throw new Error(t("Failed to delete icon"));
};
//#endregion
34 changes: 34 additions & 0 deletions public/editor-client/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { readIconUrl } from "@/types/Icon";
import { Arr, Bool, Obj, Str } from "@brizy/readers";
import { match, mPipe, optional, parseStrict } from "fp-utilities";
import { CollectionType } from "./types/Collections";
Expand Down Expand Up @@ -68,7 +69,13 @@ interface API {
fileUrl: string;
templates: DefaultTemplates;
openAIUrl?: string;
iconsUrl: string;
iconUrl: string;
deleteIconUrl: string;
uploadIconUrl: string;
imagePatterns: ImagePatterns;
}

export interface Config {
hash: string;
editorVersion: string;
Expand Down Expand Up @@ -130,6 +137,33 @@ const apiReader = parseStrict<PLUGIN_ENV["api"], API>({
throwOnNullish("Invalid API: templates")
),
openAIUrl: optional(pipe(mPipe(Obj.readKey("openAIUrl"), Str.read))),
imagePatterns: pipe(
mPipe(
Obj.readKey("media"),
Obj.read,
Obj.readKey("imagePatterns"),
Obj.read,
parseStrict<PLUGIN_ENV, ImagePatterns>({
full: pipe(
mPipe(Obj.readKey("full"), Str.read),
throwOnNullish("Invalid API: ImagePatterns full pattern")
),
original: pipe(
mPipe(Obj.readKey("original"), Str.read),
throwOnNullish("Invalid API: ImagePatterns original pattern")
),
split: pipe(
mPipe(Obj.readKey("split"), Str.read),
throwOnNullish("Invalid API: ImagePatterns split pattern")
)
})
),
throwOnNullish("Invalid API: image patterns")
),
iconUrl: readIconUrl("iconUrl"),
iconsUrl: readIconUrl("getIconsUrl"),
uploadIconUrl: readIconUrl("uploadIconUrl"),
deleteIconUrl: readIconUrl("deleteIconUrl")
});

const actionsReader = parseStrict<PLUGIN_ENV["actions"], Actions>({
Expand Down
62 changes: 62 additions & 0 deletions public/editor-client/src/customIcon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { deleteIcon as apiDeleteIcon, getCustomIcons } from "../api";
import { CustomIcon, IconData } from "../types/Icon";
import { createUpload } from "../utils/createUpload";
import { t } from "../utils/i18n";
import { uploadIcon } from "./utils";

export const customIcon: CustomIcon = {
async get(res, rej) {
try {
const icons = await getCustomIcons();

const normalizedIcons = icons.map(({ uid }) => ({
name: uid
}));

res(normalizedIcons);
} catch (e) {
rej(t("Failed to load icons"));
}
},

async add(res, rej, { acceptedExtensions = [".svg", ".ico"] }) {
try {
const icons = await createUpload({
accept: acceptedExtensions?.join(","),
multiple: true
});

const uploadedIcons = await Promise.all(
icons.map(
(icon) =>
new Promise<IconData>((resolve) => {
uploadIcon(
icon,
({ uid }) => {
resolve({
name: uid
});
},
() => {
rej(t("Failed to upload icon"));
}
);
})
)
);

res(uploadedIcons);
} catch (e) {
rej(t("Icon upload cancelled"));
}
},

async delete(res, rej, { uid }) {
try {
await apiDeleteIcon(uid);
res(uid);
} catch (e) {
rej(t("Failed to delete icon"));
}
}
};
11 changes: 11 additions & 0 deletions public/editor-client/src/customIcon/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { uploadIcon as apiUploadIcon } from "../api";
import { UploadIconData } from "../types/Icon";
import { handleFileUpload } from "../utils/uploadFile";

export const uploadIcon: UploadIconData = (icon, onUpload, onError) => {
handleFileUpload(icon, {
upload: apiUploadIcon,
onUpload,
onError
});
};
13 changes: 9 additions & 4 deletions public/editor-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import merge from "lodash/merge";
import set from "lodash/set";
import { doAiRequest } from "./aiText";
import { autoSave } from "./autoSave";
Expand All @@ -6,6 +7,7 @@ import { searchCollectionItems } from "./collectionItems/searchCollectionItems";
import { loadCollectionTypes } from "./collectionTypes/loadCollectionTypes";
import { getConfig } from "./config";
import { addFile } from "./customFile/addFile";
import { customIcon } from "./customIcon";
import {
defaultKits,
defaultLayouts,
Expand All @@ -15,9 +17,9 @@ import {
import { placeholderData, placeholders } from "./dynamicContent";
import { handler as posts } from "./Elements/Posts";
import { uploadedFonts } from "./fonts";
import { heartBeat } from "./heartBeat";
import {globalBlocks } from "./globalBlocks/blocks";
import { globalBlocks } from "./globalBlocks/blocks";
import { globalPopups } from "./globalBlocks/popups";
import { heartBeat } from "./heartBeat";
import { addMedia } from "./media/addMedia";
import { addMediaGallery } from "./media/addMediaGallery";
import { onChange } from "./onChange";
Expand All @@ -28,7 +30,6 @@ import { savedBlocks } from "./savedBlocks/savedBlocks";
import { savedLayouts } from "./savedBlocks/savedLayouts";
import { savedPopups } from "./savedBlocks/savedPopups";
import { screenshots } from "./screenshots";
import { merge } from "lodash";

const config = getConfig();

Expand All @@ -41,12 +42,16 @@ const api = {
media: {
addMedia,
addMediaGallery,
mediaResizeUrl: config.api.mediaResizeUrl,
mediaResizeUrl: config.api.mediaResizeUrl
},
customFile: {
addFile,
fileUrl: config.api.fileUrl
},
customIcon: {
...customIcon,
iconUrl: config.api.iconUrl
},
savedBlocks,
savedPopups,
savedLayouts,
Expand Down
46 changes: 46 additions & 0 deletions public/editor-client/src/types/Icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { throwOnNullish } from "@/utils/throwOnNullish";
import { Obj, pipe, Str } from "@brizy/readers";
import { mPipe } from "fp-utilities";
import { Response } from "./Response";

export interface IconUploadData {
uid: string;
}

export interface IconData {
name: string;
}

export type UploadIconData = (
icon: File,
onUpload: (uploadData: IconUploadData) => void,
onError: VoidFunction
) => void;

export interface AddIconExtra {
acceptedExtensions?: Array<string>;
}

export interface DeleteIconExtra {
uid: string;
}

export interface CustomIcon {
get: (res: Response<IconData[]>, rej: Response<string>) => void;
add: (
res: Response<IconData[]>,
rej: Response<string>,
extra: AddIconExtra
) => void;
delete: (
res: Response<string>,
rej: Response<string>,
extra: DeleteIconExtra
) => void;
}

export const readIconUrl = (url: string) =>
pipe(
mPipe(Obj.readKey("customIcon"), Obj.read, Obj.readKey(url), Str.read),
throwOnNullish(`Invalid API: ${url}`)
);
10 changes: 10 additions & 0 deletions public/editor-client/src/utils/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export async function getBase64(
file: File
): Promise<string | ArrayBuffer | null> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
}
34 changes: 34 additions & 0 deletions public/editor-client/src/utils/uploadFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { UploadData } from "../types/File";
import { getBase64 } from "./base64";
import { t } from "./i18n";

interface Options<T> {
upload: (attachment: string, filename: string) => Promise<T>;
onUpload: (uploadData: T) => void;
onError: (e: unknown) => void;
componentId?: string;
}

export const handleFileUpload = <T = UploadData>(
File: File,
{ upload, onUpload, onError }: Options<T>
): void => {
Promise.resolve(File)
.then(getBase64)
.then((base64) => {
if (typeof base64 !== "string") {
throw {
status: 406,
message: t("Extension is not accepted")
};
}

const attachment = base64.replace(/.+;base64,/, "");

return upload(attachment, File.name).then((res) => {
return res;
});
})
.then(onUpload)
.catch(onError);
};

0 comments on commit 695ac3c

Please sign in to comment.