Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
nekiro committed Jan 8, 2022
1 parent 23c1ba4 commit bfe490f
Show file tree
Hide file tree
Showing 11 changed files with 512 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
package-lock.json
rcc/qresource/
*.rcc
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
# Nekiro-s-Rcc-Editor
# Nekiro's Rcc Editor

You need nodejs and npm installed.

### How to use?

- Navigate to project directory
- npm i
- npm run start

To compile write

- npm run dist

Compiled binaries are located in "dist" directory.
By default both win32 and x64 versions are being compiled

If you don't want to compile, download latest version from releases.

Enjoy.

## Donate

If you like my work and respect my time, consider becoming [Github Sponsor](https://github.com/sponsors/nekiro).
Binary file added assets/icon.ico
Binary file not shown.
76 changes: 76 additions & 0 deletions css/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
body {
margin: 0;
padding: 0;
height: 100vh;
background-color: #222831;
display: grid;
grid-template-columns: 1fr 4fr;
user-select: none;
}

div::-webkit-scrollbar-track {
background-color: #222831;
border: 1px solid black;
border-right: 1px transparent;
}
div::-webkit-scrollbar {
width: 12px;
}
div::-webkit-scrollbar-thumb {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #393e46;
}

.list {
background-color: #393e46;
border: 1px solid black;
overflow: auto;
overflow-x: hidden;
}
.list button {
text-align: end;
width: 100%;
min-height: 30px;
max-height: 200px;
border: transparent;
border-bottom: 1px solid black;
background: transparent;
color: lightgray;
display: grid;
grid-template-columns: 80% 20%;
align-items: center;
column-gap: 10px;
justify-content: center;
}
.list button:hover {
color: gold;
background-color: #222831;
}
.focused {
background-color: #222831 !important;
color: gold !important;
}

.preview {
background-color: #393e46;
padding: 25px;
}

.center-container {
display: flex;
justify-content: center;
align-items: center;
}
.miniature-holder {
overflow: auto;
margin-left: 30px;
margin-right: 30px;
}
.miniature {
width: auto;
height: auto;
max-width: 40px;
justify-self: start;
max-height: 30px;
object-fit: contain;
}
21 changes: 21 additions & 0 deletions html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'"
/>
<link rel="stylesheet" href="../css/style.css" />
</head>
<body>
<div class="list"></div>
<div class="center-container miniature-holder">
<img
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
class="preview"
/>
</div>
<script src="../js/mainRenderer.js"></script>
</body>
</html>
14 changes: 14 additions & 0 deletions js/ipcMain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { ipcMain } = require("electron");
const { getImageByIndex, replaceImage } = require("./reader");

ipcMain.on("get-image-data", (event, index) => {
event.reply("update-preview", getImageByIndex(index));
});

ipcMain.on("replace-image", async (event, obj) => {
const data = await replaceImage(obj.index, obj.path);
if (data) {
event.reply("update-preview", data);
event.reply("update-miniature", { index: obj.index, data });
}
});
77 changes: 77 additions & 0 deletions js/mainRenderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//renderer process
const { ipcRenderer } = require("electron");

const preview = document.querySelector(".preview");
const list = document.querySelector(".list");
let focused = null;

preview.addEventListener("drop", (event) => {
event.preventDefault();
event.stopPropagation();

if (!focused) {
return;
}

ipcRenderer.send("replace-image", {
index: parseInt(focused.id.split("-")[1]),
path: fevent.dataTransfer.files[0].path,
});
});

preview.addEventListener("dragover", (e) => {
e.preventDefault();
e.stopPropagation();
});

ipcRenderer.on("update-preview", (event, data) => {
preview.src = `data:image/png;base64,${Buffer.from(data).toString("base64")}`;
});

ipcRenderer.on("update-miniature", (event, { index, data }) => {
list.querySelector(
`#btn-${index} > img`
).src = `data:image/png;base64,${Buffer.from(data).toString("base64")}`;
});

ipcRenderer.on("populate-list", (event, images) => {
focused = null;

while (list.firstChild) {
list.removeChild(list.firstChild);
}

for (let index = 0; index < images.length; ++index) {
const image = images[index];

if (!image.isImage) {
continue;
}

let btn = document.createElement("button");
btn.innerText = `${image.name}`;
btn.id = `btn-${index}`;
btn.onclick = (event) => {
if (focused == event.target) {
return;
}

if (focused) {
focused.classList.remove("focused");
}

focused = event.target;
focused.classList.add("focused");
ipcRenderer.send("get-image-data", index);
};

let img = document.createElement("img");
img.className = "miniature";
img.src = `data:image/png;base64,${Buffer.from(image.data).toString(
"base64"
)}`;
btn.appendChild(img);

list.appendChild(btn);
}
});
163 changes: 163 additions & 0 deletions js/reader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
const { dialog, BrowserWindow, app } = require("electron");
const path = require("path");
const util = require("util");
const execFile = util.promisify(require("child_process").execFile);

const Promise = require("bluebird");
const fs = Promise.promisifyAll(require("fs-extra"));

let loadedFilePath = null;
let images = [];

const getResourcePath = () => {
if (app.isPackaged) {
return process.resourcesPath;
} else {
return ".";
}
};

const getFiles = async (path = "./") => {
const entries = await fs.readdir(path, { withFileTypes: true });
const files = entries
.filter((file) => !file.isDirectory())
.map((file) => ({ ...file, path: path + file.name }));

const folders = entries.filter((folder) => folder.isDirectory());
for (const folder of folders) {
files.push(...(await getFiles(`${path}/${folder.name}/`)));
}
return files;
};

const loadRcc = async (filePath) => {
const localPath = `${path.resolve(getResourcePath(), "rcc")}`;

// clear previous images
images = [];

// delete res directory
try {
await fs.rmdir(`${localPath}/qresource`, { recursive: true });
} catch {}

await fs.copyFile(filePath, `${localPath}/res.rcc`);

const result = await execFile(`${localPath}/rcc.exe`, ["--reverse"], {
cwd: `${localPath}/`,
});

// get directory content
const files = await getFiles(`${localPath}/qresource/res/res.rcc`);
for (const file of files) {
const ext = path.extname(file.path);
images.push({
name: path.parse(file.name).name,
path: path.relative(`${localPath}/qresource/res/res.rcc`, file.path),
isImage: ext === ".png" || ext === ".jpg",
data: Buffer.from(await fs.readFile(file.path, "binary"), "binary"),
});
}

// sort by name
images.sort((a, b) => a.name.localeCompare(b.name));

// cleanup
await fs.rmdir(`${localPath}/qresource`, { recursive: true });
await fs.rm(`${localPath}/res.rcc`);

loadedFilePath = filePath;

BrowserWindow.getAllWindows()[0].webContents.send("populate-list", images);
};

const extractToPng = async (directoryPath) => {
if (images.length === 0) {
dialog.showErrorBox("Error", "Nothing to extract.");
return;
}

for (const image of images) {
if (image.isImage) {
await fs.outputFileAsync(`${directoryPath}/${image.path}`, image.data);
}
}

dialog.showMessageBox(null, {
message: `Png images extracted successfully. Extracted ${images.length} images.`,
type: "info",
});
};

const saveRcc = async (filePath) => {
if (images.length === 0) {
return;
}

filePath = filePath || loadedFilePath;

const localPath = `${path.resolve(getResourcePath(), "rcc")}`;

// create .qrc file
let data = `<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n`;

for (const image of images) {
data += `<file>${image.path}</file>\n`;
}

data += `</qresource>\n</RCC>`;

await fs.outputFileAsync(`${localPath}/res/res.qrc`, data);

// dump images
for (const image of images) {
await fs.outputFileAsync(`${localPath}/res/${image.path}`, image.data);
}

const result = await execFile(
`${localPath}/rcc.exe`,
[
"--format-version",
"1",
"--binary",
`./res/res.qrc`,
"-o",
"./res/res_output.rcc",
],
{
cwd: `${localPath}/`,
}
);

await fs.move("./rcc/res/res_output.rcc", `${filePath}`, { overwrite: true });

// cleanup
await fs.rmdir(`${localPath}/res`, { recursive: true });

dialog.showMessageBox(null, {
message: `Rcc saved successfully.`,
type: "info",
});
};

const replaceImage = async (index, filePath) => {
const image = images[index];
if (!image) {
return null;
}

image.data = Buffer.from(await fs.readFile(filePath, "binary"), "binary");
return image.data;
};

const getImageByIndex = (index) => {
return images[index]?.data;
};

module.exports = {
loadRcc,
saveRcc,
replaceImage,
getImageByIndex,
extractToPng,
};
Loading

0 comments on commit bfe490f

Please sign in to comment.