Skip to content

Commit 9b832b2

Browse files
committed
fix drag and drop actions
1 parent a656dd2 commit 9b832b2

9 files changed

Lines changed: 190 additions & 13 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "rcc-editor",
33
"productName": "rcc-editor",
4-
"version": "2.2.0",
4+
"version": "2.2.1",
55
"description": "edit qt .rcc files",
66
"main": ".webpack/main",
77
"scripts": {

src/main/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { app, BrowserWindow, ipcMain, Menu } from "electron";
2-
import { getImages, replaceImage } from "./rcc";
2+
import { getImages, replaceImage, replaceImageWithData } from "./rcc";
33
import { Image } from "../types/image";
44
import createMenu from "./menu";
55
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
@@ -22,6 +22,9 @@ const createWindow = async () => {
2222
width: 800,
2323
webPreferences: {
2424
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
25+
webSecurity: false,
26+
nodeIntegration: false,
27+
contextIsolation: true,
2528
},
2629
backgroundColor: "#222831",
2730
title: "Nekiro's Rcc Editor",
@@ -77,3 +80,10 @@ ipcMain.handle("list:replace-image", async (event: Electron.IpcMainInvokeEvent,
7780
mainWindow.webContents.send("list:image", index, getPreparedImage(image));
7881
return getPreparedImage(image);
7982
});
83+
84+
ipcMain.handle("list:replace-image-data", async (event: Electron.IpcMainInvokeEvent, index: number, data: ArrayBuffer) => {
85+
const image = await replaceImageWithData(index, data);
86+
if (!image) return;
87+
mainWindow.webContents.send("list:image", index, getPreparedImage(image));
88+
return getPreparedImage(image);
89+
});

src/main/rcc.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,29 @@ export const replaceImage = async (index: number, filePath: string) => {
158158
return null;
159159
}
160160

161-
image.data = Buffer.from(await fs.readFile(filePath, "binary"), "binary"); // TODO check this
162-
return image;
161+
try {
162+
const fileBuffer = await fs.readFile(filePath);
163+
image.data = fileBuffer;
164+
return image;
165+
} catch (error) {
166+
console.error("Error reading file:", error);
167+
return null;
168+
}
169+
};
170+
171+
export const replaceImageWithData = async (index: number, data: ArrayBuffer) => {
172+
const image = getImageByIndex(index);
173+
if (!image) {
174+
return null;
175+
}
176+
177+
try {
178+
image.data = Buffer.from(data);
179+
return image;
180+
} catch (error) {
181+
console.error("Error setting image data:", error);
182+
return null;
183+
}
163184
};
164185

165186
export const getImageByIndex = (index: number) => {

src/renderer/components/ImageButton/index.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,22 @@ type ImageButtonProps = {
66
image: Image;
77
id: string;
88
onClick: React.MouseEventHandler<HTMLButtonElement>;
9+
onDrop?: React.DragEventHandler<HTMLButtonElement>;
10+
onDragOver?: React.DragEventHandler<HTMLButtonElement>;
11+
onDragStart?: React.DragEventHandler<HTMLButtonElement>;
912
};
1013

11-
export default function ImageButton({ image, id, onClick, ...props }: ImageButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>) {
14+
export default function ImageButton({
15+
image,
16+
id,
17+
onClick,
18+
onDrop,
19+
onDragOver,
20+
onDragStart,
21+
...props
22+
}: ImageButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>) {
1223
return (
13-
<button {...props} id={id} onClick={onClick}>
24+
<button {...props} id={id} onClick={onClick} onDrop={onDrop} onDragOver={onDragOver} onDragStart={onDragStart}>
1425
{image.name}
1526
<img src={`data:image/png;base64,${image.data}`} className={styles.miniature} />
1627
</button>

src/renderer/components/Preview/index.tsx

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,73 @@ export default function Preview() {
99
event.preventDefault();
1010
event.stopPropagation();
1111

12-
if (!selected.element || !event.dataTransfer.files[0].path) return;
12+
if (!selected.element) return;
1313

14-
const image = await window.api.replaceImage(parseInt(selected.element.id.split("-")[1], 10), event.dataTransfer.files[0].path);
14+
let filePath: string | undefined;
1515

16-
setSelected(selected.element, image);
16+
if (event.dataTransfer.files[0]?.path) {
17+
filePath = event.dataTransfer.files[0].path;
18+
} else if (event.dataTransfer.items[0]?.getAsFile()) {
19+
const file = event.dataTransfer.items[0].getAsFile();
20+
if (file) {
21+
const reader = new FileReader();
22+
reader.onload = async (e) => {
23+
if (e.target?.result) {
24+
try {
25+
const index = parseInt(selected.element!.id.split("-")[1], 10);
26+
const image = await window.api.replaceImageWithData(index, e.target.result as ArrayBuffer);
27+
if (image) {
28+
setSelected(selected.element!, image);
29+
}
30+
} catch (error) {
31+
console.error("Error replacing image:", error);
32+
}
33+
}
34+
};
35+
reader.readAsArrayBuffer(file);
36+
return;
37+
}
38+
}
39+
40+
if (!filePath) return;
41+
42+
try {
43+
const index = parseInt(selected.element.id.split("-")[1], 10);
44+
const image = await window.api.replaceImage(index, filePath);
45+
if (image) {
46+
setSelected(selected.element, image);
47+
}
48+
} catch (error) {
49+
console.error("Error replacing image:", error);
50+
}
1751
};
1852

1953
const onDragOverToPreview = (event: React.DragEvent<HTMLImageElement>) => {
2054
event.preventDefault();
2155
event.stopPropagation();
2256
};
2357

58+
const onDragStartFromPreview = (event: React.DragEvent<HTMLImageElement>) => {
59+
if (!selected.element || !selected.image) return;
60+
61+
event.dataTransfer.setData("text/plain", selected.image.name);
62+
event.dataTransfer.setData("text/uri-list", `data:image/png;base64,${selected.image.data}`);
63+
event.dataTransfer.setData("image/png", `data:image/png;base64,${selected.image.data}`);
64+
event.dataTransfer.setData("application/octet-stream", `data:image/png;base64,${selected.image.data}`);
65+
66+
const img = new Image();
67+
img.src = `data:image/png;base64,${selected.image.data}`;
68+
event.dataTransfer.setDragImage(img, 0, 0);
69+
70+
event.dataTransfer.effectAllowed = "copyMove";
71+
event.dataTransfer.setData("DownloadURL", `image/png:${selected.image.name}:data:image/png;base64,${selected.image.data}`);
72+
};
73+
2474
return (
2575
<img
2676
onDrop={onDropToPreview}
2777
onDragOver={onDragOverToPreview}
78+
onDragStart={onDragStartFromPreview}
2879
className={`${styles.preview} ${selected.element ? styles.visible : ""}`}
2980
src={selected.element ? `data:image/png;base64,${selected.image?.data}` : ""}
3081
/>

src/renderer/components/SelectableList/index.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,93 @@ type SelectableListProps = {
1111
export default function SelectableList({ images }: SelectableListProps) {
1212
const [selected, setElement] = useSelectedElement();
1313

14+
React.useEffect(() => {
15+
const handleGlobalDragOver = (event: DragEvent) => {
16+
event.preventDefault();
17+
event.dataTransfer!.dropEffect = "copy";
18+
};
19+
20+
const handleGlobalDrop = (event: DragEvent) => {
21+
event.preventDefault();
22+
};
23+
24+
document.addEventListener("dragover", handleGlobalDragOver);
25+
document.addEventListener("drop", handleGlobalDrop);
26+
27+
return () => {
28+
document.removeEventListener("dragover", handleGlobalDragOver);
29+
document.removeEventListener("drop", handleGlobalDrop);
30+
};
31+
}, []);
32+
33+
const handleDrop = async (event: React.DragEvent<HTMLButtonElement>, index: number) => {
34+
event.preventDefault();
35+
event.stopPropagation();
36+
37+
let filePath: string | undefined;
38+
39+
if (event.dataTransfer.files[0]?.path) {
40+
filePath = event.dataTransfer.files[0].path;
41+
} else if (event.dataTransfer.items[0]?.getAsFile()) {
42+
const file = event.dataTransfer.items[0].getAsFile();
43+
if (file) {
44+
const reader = new FileReader();
45+
reader.onload = async (e) => {
46+
if (e.target?.result) {
47+
try {
48+
const image = await window.api.replaceImageWithData(index, e.target.result as ArrayBuffer);
49+
if (image) {
50+
setElement(event.currentTarget, image);
51+
}
52+
} catch (error) {
53+
console.error("Error replacing image:", error);
54+
}
55+
}
56+
};
57+
reader.readAsArrayBuffer(file);
58+
return;
59+
}
60+
}
61+
62+
if (!filePath) return;
63+
64+
try {
65+
const image = await window.api.replaceImage(index, filePath);
66+
if (image) {
67+
setElement(event.currentTarget, image);
68+
}
69+
} catch (error) {
70+
console.error("Error replacing image:", error);
71+
}
72+
};
73+
74+
const handleDragOver = (event: React.DragEvent<HTMLButtonElement>) => {
75+
event.preventDefault();
76+
event.stopPropagation();
77+
};
78+
79+
const handleDragStart = (event: React.DragEvent<HTMLButtonElement>, image: Image) => {
80+
event.dataTransfer.setData("text/plain", image.name);
81+
event.dataTransfer.setData("text/uri-list", `data:image/png;base64,${image.data}`);
82+
event.dataTransfer.setData("image/png", `data:image/png;base64,${image.data}`);
83+
event.dataTransfer.setData("application/octet-stream", `data:image/png;base64,${image.data}`);
84+
85+
const img = new Image();
86+
img.src = `data:image/png;base64,${image.data}`;
87+
event.dataTransfer.setDragImage(img, 0, 0);
88+
89+
event.dataTransfer.effectAllowed = "copyMove";
90+
event.dataTransfer.setData("DownloadURL", `image/png:${image.name}:data:image/png;base64,${image.data}`);
91+
};
92+
1493
return (
1594
<div className={styles.list}>
1695
{images.map((image, index) => (
1796
<ImageButton
1897
onClick={(event) => setElement(event.currentTarget, image)}
98+
onDrop={(event) => handleDrop(event, index)}
99+
onDragOver={handleDragOver}
100+
onDragStart={(event) => handleDragStart(event, image)}
19101
id={`btn-${index}`}
20102
image={image}
21103
key={`btn-${index}`}

src/renderer/declarations.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ export type RendererHandlers = {
1313

1414
export interface MainApi {
1515
replaceImage: (index: number, path: string) => Promise<Image>;
16+
replaceImageWithData: (index: number, data: ArrayBuffer) => Promise<Image>;
1617
getImages: () => Promise<Image[]>;
1718
handlers: RendererHandlers;
1819
}
1920

2021
export type Image = {
2122
name: string;
23+
fullName: string;
2224
path: string;
23-
isImage: boolean;
24-
data: Buffer;
25+
data: Buffer | string;
2526
};
2627

2728
declare global {

src/renderer/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const handlers: RendererHandlers = {
1414

1515
const api: MainApi = {
1616
replaceImage: (index: number, path: string) => ipcRenderer.invoke("list:replace-image", index, path),
17+
replaceImageWithData: (index: number, data: ArrayBuffer) => ipcRenderer.invoke("list:replace-image-data", index, data),
1718
getImages: () => ipcRenderer.invoke("list:images"),
1819
handlers,
1920
};

0 commit comments

Comments
 (0)