Skip to content

Commit 82c970a

Browse files
[WIKI-804] fix: refactor image uploader (#8210)
* fix: refactor uploader * fix: props * fix: sites fix
1 parent 392c8cf commit 82c970a

File tree

9 files changed

+63
-56
lines changed

9 files changed

+63
-56
lines changed

apps/api/plane/space/views/asset.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
from rest_framework.permissions import AllowAny, IsAuthenticated
1212
from rest_framework.response import Response
1313

14-
# Module imports
15-
from .base import BaseAPIView
14+
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
1615
from plane.db.models import DeployBoard, FileAsset
1716
from plane.settings.storage import S3Storage
18-
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
17+
18+
# Module imports
19+
from .base import BaseAPIView
1920

2021

2122
class EntityAssetEndpoint(BaseAPIView):
@@ -167,15 +168,15 @@ def delete(self, request, anchor, pk):
167168
class AssetRestoreEndpoint(BaseAPIView):
168169
"""Endpoint to restore a deleted assets."""
169170

170-
def post(self, request, anchor, asset_id):
171+
def post(self, request, anchor, pk):
171172
# Get the deploy board
172173
deploy_board = DeployBoard.objects.filter(anchor=anchor, entity_name="project").first()
173174
# Check if the project is published
174175
if not deploy_board:
175176
return Response({"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND)
176177

177178
# Get the asset
178-
asset = FileAsset.all_objects.get(id=asset_id, workspace=deploy_board.workspace)
179+
asset = FileAsset.all_objects.get(id=pk, workspace=deploy_board.workspace)
179180
asset.is_deleted = False
180181
asset.deleted_at = None
181182
asset.save(update_fields=["is_deleted", "deleted_at"])

packages/editor/src/core/components/menus/block-menu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { Editor } from "@tiptap/react";
1212
import type { LucideIcon } from "lucide-react";
1313
import { Copy, Trash2 } from "lucide-react";
1414
import { useCallback, useEffect, useRef, useState } from "react";
15+
import type { ISvgIcons } from "@plane/propel/icons";
1516
import { cn } from "@plane/utils";
1617
// constants
1718
import { CORE_EXTENSIONS } from "@/constants/extension";
@@ -27,7 +28,7 @@ type Props = {
2728
workItemIdentifier?: IEditorProps["workItemIdentifier"];
2829
};
2930
export type BlockMenuOption = {
30-
icon: LucideIcon;
31+
icon: LucideIcon | React.FC<ISvgIcons>;
3132
key: string;
3233
label: string;
3334
onClick: (e: React.MouseEvent) => void;

packages/editor/src/core/extensions/callout/logo-selector.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,27 +53,29 @@ export function CalloutBlockLogoSelector(props: Props) {
5353
};
5454
if (val.type === "emoji") {
5555
// val.value is now a string in decimal format (e.g. "128512")
56+
const emojiValue = val.value as string;
5657
newLogoValue = {
57-
"data-emoji-unicode": val.value,
58+
"data-emoji-unicode": emojiValue,
5859
"data-emoji-url": undefined,
5960
};
6061
newLogoValueToStoreInLocalStorage = {
6162
in_use: "emoji",
6263
emoji: {
63-
value: val.value,
64+
value: emojiValue,
6465
url: undefined,
6566
},
6667
};
6768
} else if (val.type === "icon") {
69+
const iconValue = val.value as { name: string; color: string };
6870
newLogoValue = {
69-
"data-icon-name": val.value.name,
70-
"data-icon-color": val.value.color,
71+
"data-icon-name": iconValue.name,
72+
"data-icon-color": iconValue.color,
7173
};
7274
newLogoValueToStoreInLocalStorage = {
7375
in_use: "icon",
7476
icon: {
75-
name: val.value.name,
76-
color: val.value.color,
77+
name: iconValue.name,
78+
color: iconValue.color,
7779
},
7880
};
7981
}

packages/editor/src/core/extensions/custom-image/components/uploader.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export function CustomImageUploader(props: CustomImageUploaderProps) {
4040
// refs
4141
const fileInputRef = useRef<HTMLInputElement>(null);
4242
const hasTriggeredFilePickerRef = useRef(false);
43+
const hasTriedUploadingOnMountRef = useRef(false);
4344
const { id: imageEntityId } = node.attrs;
4445
// derived values
4546
const imageComponentImageFileMap = useMemo(() => getImageComponentImageFileMap(editor), [editor]);
@@ -124,17 +125,16 @@ export function CustomImageUploader(props: CustomImageUploaderProps) {
124125
uploader: uploadFile,
125126
});
126127

127-
// the meta data of the image component
128-
const meta = useMemo(
129-
() => imageComponentImageFileMap?.get(imageEntityId ?? ""),
130-
[imageComponentImageFileMap, imageEntityId]
131-
);
132-
133128
// after the image component is mounted we start the upload process based on
134129
// it's uploaded
135130
useEffect(() => {
131+
if (hasTriedUploadingOnMountRef.current) return;
132+
133+
// the meta data of the image component
134+
const meta = imageComponentImageFileMap?.get(imageEntityId ?? "");
136135
if (meta) {
137136
if (meta.event === "drop" && "file" in meta) {
137+
hasTriedUploadingOnMountRef.current = true;
138138
uploadFile(meta.file);
139139
} else if (meta.event === "insert" && fileInputRef.current && !hasTriggeredFilePickerRef.current) {
140140
if (meta.hasOpenedFileInputOnce) return;
@@ -144,8 +144,10 @@ export function CustomImageUploader(props: CustomImageUploaderProps) {
144144
hasTriggeredFilePickerRef.current = true;
145145
imageComponentImageFileMap?.set(imageEntityId ?? "", { ...meta, hasOpenedFileInputOnce: true });
146146
}
147+
} else {
148+
hasTriedUploadingOnMountRef.current = true;
147149
}
148-
}, [meta, uploadFile, imageComponentImageFileMap, imageEntityId, isTouchDevice]);
150+
}, [imageEntityId, isTouchDevice, uploadFile, imageComponentImageFileMap]);
149151

150152
const onFileChange = useCallback(
151153
async (e: ChangeEvent<HTMLInputElement>) => {

packages/editor/src/core/extensions/extensions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
115115
CustomCalloutExtension,
116116
UtilityExtension({
117117
disabledExtensions,
118+
flaggedExtensions,
118119
fileHandler,
119120
getEditorMetaData,
120121
isEditable: editable,

packages/editor/src/core/extensions/utility.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ export type UtilityExtensionStorage = {
5151
isTouchDevice: boolean;
5252
};
5353

54-
type Props = Pick<IEditorProps, "disabledExtensions" | "getEditorMetaData"> & {
54+
type Props = Pick<IEditorProps, "disabledExtensions" | "flaggedExtensions" | "getEditorMetaData"> & {
5555
fileHandler: TFileHandler;
5656
isEditable: boolean;
5757
isTouchDevice: boolean;
5858
};
5959

6060
export const UtilityExtension = (props: Props) => {
61-
const { disabledExtensions, fileHandler, getEditorMetaData, isEditable, isTouchDevice } = props;
61+
const { disabledExtensions, flaggedExtensions, fileHandler, getEditorMetaData, isEditable, isTouchDevice } = props;
6262
const { restore } = fileHandler;
6363

6464
return Extension.create<Record<string, unknown>, UtilityExtensionStorage>({
@@ -79,6 +79,7 @@ export const UtilityExtension = (props: Props) => {
7979
}),
8080
DropHandlerPlugin({
8181
disabledExtensions,
82+
flaggedExtensions,
8283
editor: this.editor,
8384
}),
8485
PasteAssetPlugin(),

packages/editor/src/core/plugins/drop.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import type { TEditorCommands, TExtensions } from "@/types";
77

88
type Props = {
99
disabledExtensions?: TExtensions[];
10+
flaggedExtensions?: TExtensions[];
1011
editor: Editor;
1112
};
1213

1314
export const DropHandlerPlugin = (props: Props): Plugin => {
14-
const { disabledExtensions, editor } = props;
15+
const { disabledExtensions, flaggedExtensions, editor } = props;
1516

1617
return new Plugin({
1718
key: new PluginKey("drop-handler-plugin"),
@@ -33,6 +34,7 @@ export const DropHandlerPlugin = (props: Props): Plugin => {
3334
const pos = view.state.selection.from;
3435
insertFilesSafely({
3536
disabledExtensions,
37+
flaggedExtensions,
3638
editor,
3739
files: acceptedFiles,
3840
initialPos: pos,
@@ -84,6 +86,7 @@ export const DropHandlerPlugin = (props: Props): Plugin => {
8486

8587
type InsertFilesSafelyArgs = {
8688
disabledExtensions?: TExtensions[];
89+
flaggedExtensions?: TExtensions[];
8790
editor: Editor;
8891
event: "insert" | "drop";
8992
files: File[];

packages/editor/src/core/plugins/file/delete.ts

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHand
2121
[nodeType: string]: Set<string> | undefined;
2222
} = {};
2323
if (!transactions.some((tr) => tr.docChanged)) return null;
24+
if (transactions.some((tr) => tr.getMeta(CORE_EDITOR_META.SKIP_FILE_DELETION))) return null;
2425

2526
newState.doc.descendants((node) => {
2627
const nodeType = node.type.name as keyof NodeFileMapType;
@@ -34,40 +35,35 @@ export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHand
3435
}
3536
});
3637

37-
transactions.forEach((transaction) => {
38-
// if the transaction has meta of skipFileDeletion set to true, then return (like while clearing the editor content programmatically)
39-
if (transaction.getMeta(CORE_EDITOR_META.SKIP_FILE_DELETION)) return;
38+
const removedFiles: TFileNode[] = [];
4039

41-
const removedFiles: TFileNode[] = [];
42-
43-
// iterate through all the nodes in the old state
44-
oldState.doc.descendants((node) => {
45-
const nodeType = node.type.name as keyof NodeFileMapType;
46-
const isAValidNode = NODE_FILE_MAP[nodeType];
47-
// if the node doesn't match, then return as no point in checking
48-
if (!isAValidNode) return;
49-
// Check if the node has been deleted or replaced
50-
if (!newFileSources[nodeType]?.has(node.attrs.src)) {
51-
removedFiles.push(node as TFileNode);
52-
}
53-
});
40+
// iterate through all the nodes in the old state
41+
oldState.doc.descendants((node) => {
42+
const nodeType = node.type.name as keyof NodeFileMapType;
43+
const isAValidNode = NODE_FILE_MAP[nodeType];
44+
// if the node doesn't match, then return as no point in checking
45+
if (!isAValidNode) return;
46+
// Check if the node has been deleted or replaced
47+
if (!newFileSources[nodeType]?.has(node.attrs.src)) {
48+
removedFiles.push(node as TFileNode);
49+
}
50+
});
5451

55-
removedFiles.forEach(async (node) => {
56-
const nodeType = node.type.name as keyof NodeFileMapType;
57-
const src = node.attrs.src;
58-
const nodeFileSetDetails = NODE_FILE_MAP[nodeType];
59-
if (!nodeFileSetDetails || !src) return;
60-
try {
61-
editor.storage[nodeType]?.[nodeFileSetDetails.fileSetName]?.set(src, true);
62-
// update assets list storage value
63-
editor.commands.updateAssetsList?.({
64-
idToRemove: node.attrs.id,
65-
});
66-
await deleteHandler(src);
67-
} catch (error) {
68-
console.error("Error deleting file via delete utility plugin:", error);
69-
}
70-
});
52+
removedFiles.forEach(async (node) => {
53+
const nodeType = node.type.name as keyof NodeFileMapType;
54+
const src = node.attrs.src;
55+
const nodeFileSetDetails = NODE_FILE_MAP[nodeType];
56+
if (!nodeFileSetDetails || !src) return;
57+
try {
58+
editor.storage[nodeType]?.[nodeFileSetDetails.fileSetName]?.set(src, true);
59+
// update assets list storage value
60+
editor.commands.updateAssetsList?.({
61+
idToRemove: node.attrs.id,
62+
});
63+
await deleteHandler(src);
64+
} catch (error) {
65+
console.error("Error deleting file via delete utility plugin:", error);
66+
}
7167
});
7268

7369
return null;

packages/services/src/file/sites-file.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ export class SitesFileService extends FileService {
9898
* @returns {Promise<void>} Promise resolving to void
9999
* @throws {Error} If the request fails
100100
*/
101-
async restoreNewAsset(workspaceSlug: string, src: string): Promise<void> {
101+
async restoreNewAsset(anchor: string, src: string): Promise<void> {
102102
// remove the last slash and get the asset id
103103
const assetId = getAssetIdFromUrl(src);
104-
return this.post(`/api/public/assets/v2/workspaces/${workspaceSlug}/restore/${assetId}/`)
104+
return this.post(`/api/public/assets/v2/anchor/${anchor}/restore/${assetId}/`)
105105
.then((response) => response?.data)
106106
.catch((error) => {
107107
throw error?.response?.data;

0 commit comments

Comments
 (0)