2
0
mirror of https://github.com/frappe/books.git synced 2025-01-03 07:12:21 +00:00

ux: allow to view attached files directly

This commit is contained in:
Mildred Ki'Lya 2024-11-20 19:43:14 +01:00
parent a2b80a3414
commit 8de91f74fe
5 changed files with 99 additions and 9 deletions

28
main.ts
View File

@ -6,6 +6,7 @@ require('source-map-support').install({
import { emitMainProcessError } from 'backend/helpers';
import {
shell,
app,
BrowserWindow,
BrowserWindowConstructorOptions,
@ -22,6 +23,19 @@ import registerIpcMainActionListeners from './main/registerIpcMainActionListener
import registerIpcMainMessageListeners from './main/registerIpcMainMessageListeners';
import registerProcessListeners from './main/registerProcessListeners';
const EXTENSIONS = {
'.js': 'text/javascript',
'.css': 'text/css',
'.html': 'text/html',
'.svg': 'image/svg+xml',
'.json': 'application/json',
'.pdf': 'application/pdf',
};
const MIME_TYPES = Object.fromEntries(
Object.entries(EXTENSIONS).map(([ext, mime]) => [mime, ext])
);
export class Main {
title = 'Frappe Books';
icon: string;
@ -119,6 +133,11 @@ export class Main {
const options = this.getOptions();
this.mainWindow = new BrowserWindow(options);
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
if (this.isDevelopment) {
this.setViteServerURL();
} else {
@ -189,14 +208,7 @@ function bufferProtocolCallback(
fs.readFile(filePath, (_, data) => {
const extension = path.extname(filePath).toLowerCase();
const mimeType =
{
'.js': 'text/javascript',
'.css': 'text/css',
'.html': 'text/html',
'.svg': 'image/svg+xml',
'.json': 'application/json',
}[extension] ?? '';
const mimeType = EXTENSIONS[extension] ?? '';
callback({ mimeType, data });
});

View File

@ -117,6 +117,10 @@ const ipc = {
ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, link);
},
openDataURL(link: string, filename: string) {
ipcRenderer.send(IPC_MESSAGES.OPEN_DATA_URL, { link, filename });
},
async deleteFile(filePath: string) {
return (await ipcRenderer.invoke(
IPC_ACTIONS.DELETE_FILE,

View File

@ -1,8 +1,42 @@
import { ipcMain, Menu, shell } from 'electron';
import { ipcMain, Menu, shell, app } from 'electron';
import fs from 'fs';
import path from 'path';
import { Main } from '../main';
import { IPC_MESSAGES } from '../utils/messages';
import { emitMainProcessError } from 'backend/helpers';
function parseDataURL(url) {
const regex =
/^data:([a-z]+\/[a-z0-9-+.]+(;[a-z0-9-.!#$%*+.{}|~`]+=[a-z0-9-.!#$%*+.{}()_|~`]+)*)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s<>]*?)$/i;
const parts = url.trim().match(regex);
if (!parts) return null;
const parsed = {};
parsed.mediaType = (parts[1] || 'text/plain;charset=us-ascii').toLowerCase();
const mediaTypeParts = parsed.mediaType
.split(';')
.map((x) => x.toLowerCase());
parsed.contentType = mediaTypeParts[0];
mediaTypeParts.slice(1).forEach((attribute) => {
const p = attribute.split('=');
parsed[p[0]] = p[1];
});
parsed.base64 = !!parts[parts.length - 2];
parsed.data = parts[parts.length - 1] || '';
parsed.encoding = parsed.base64 ? 'base64' : 'utf8';
parsed.buffer = Buffer.from(
parsed.base64 ? parsed.data : decodeURIComponent(parsed.data),
parsed.encoding
);
return parsed;
}
export default function registerIpcMainMessageListeners(main: Main) {
ipcMain.on(IPC_MESSAGES.OPEN_MENU, (event) => {
if (event.sender === null) {
@ -49,6 +83,25 @@ export default function registerIpcMainMessageListeners(main: Main) {
shell.openExternal(link).catch((err) => emitMainProcessError(err));
});
ipcMain.on(
IPC_MESSAGES.OPEN_DATA_URL,
(_, { link, filename }: { link: string; filename: string }) => {
const data = parseDataURL(link);
if (data) {
const s =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const temp = Array.apply(null, Array(16))
.map(() => {
return s.charAt(Math.floor(Math.random() * s.length));
})
.join('');
const filepath = path.join(app.getPath('temp'), temp + ' ' + filename);
fs.writeFileSync(filepath, data.buffer);
shell.openPath(filepath);
}
}
);
ipcMain.on(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, (_, filePath: string) => {
return shell.showItemInFolder(filePath);
});

View File

@ -33,6 +33,14 @@
/>
</button>
<!-- Open Button -->
<button v-if="value" class="p-0.5 rounded" @click="open">
<FeatherIcon
name="eye"
class="h-4 w-4 text-gray-600 dark:text-gray-400"
/>
</button>
<!-- Download Button -->
<button v-if="value" class="p-0.5 rounded" @click="download">
<FeatherIcon
@ -125,6 +133,18 @@ export default defineComponent({
a.click();
document.body.removeChild(a);
},
open() {
if (!this.value) {
return;
}
const { name, data } = this.value;
if (!name || !data) {
return;
}
ipc.openDataURL(data, name);
},
async selectFile(e: Event) {
const target = e.target as HTMLInputElement;
const file = target.files?.[0];

View File

@ -3,6 +3,7 @@ export enum IPC_MESSAGES {
OPEN_MENU = 'open-menu',
OPEN_SETTINGS = 'open-settings',
OPEN_EXTERNAL = 'open-external',
OPEN_DATA_URL = 'open-data-url',
SHOW_ITEM_IN_FOLDER = 'show-item-in-folder',
RELOAD_MAIN_WINDOW = 'reload-main-window',
MINIMIZE_MAIN_WINDOW = 'minimize-main-window',