2
0
mirror of https://github.com/frappe/books.git synced 2025-01-05 08:02:15 +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 { emitMainProcessError } from 'backend/helpers';
import { import {
shell,
app, app,
BrowserWindow, BrowserWindow,
BrowserWindowConstructorOptions, BrowserWindowConstructorOptions,
@ -22,6 +23,19 @@ import registerIpcMainActionListeners from './main/registerIpcMainActionListener
import registerIpcMainMessageListeners from './main/registerIpcMainMessageListeners'; import registerIpcMainMessageListeners from './main/registerIpcMainMessageListeners';
import registerProcessListeners from './main/registerProcessListeners'; 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 { export class Main {
title = 'Frappe Books'; title = 'Frappe Books';
icon: string; icon: string;
@ -119,6 +133,11 @@ export class Main {
const options = this.getOptions(); const options = this.getOptions();
this.mainWindow = new BrowserWindow(options); this.mainWindow = new BrowserWindow(options);
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
if (this.isDevelopment) { if (this.isDevelopment) {
this.setViteServerURL(); this.setViteServerURL();
} else { } else {
@ -189,14 +208,7 @@ function bufferProtocolCallback(
fs.readFile(filePath, (_, data) => { fs.readFile(filePath, (_, data) => {
const extension = path.extname(filePath).toLowerCase(); const extension = path.extname(filePath).toLowerCase();
const mimeType = const mimeType = EXTENSIONS[extension] ?? '';
{
'.js': 'text/javascript',
'.css': 'text/css',
'.html': 'text/html',
'.svg': 'image/svg+xml',
'.json': 'application/json',
}[extension] ?? '';
callback({ mimeType, data }); callback({ mimeType, data });
}); });

View File

@ -117,6 +117,10 @@ const ipc = {
ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, link); 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) { async deleteFile(filePath: string) {
return (await ipcRenderer.invoke( return (await ipcRenderer.invoke(
IPC_ACTIONS.DELETE_FILE, 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 { Main } from '../main';
import { IPC_MESSAGES } from '../utils/messages'; import { IPC_MESSAGES } from '../utils/messages';
import { emitMainProcessError } from 'backend/helpers'; 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) { export default function registerIpcMainMessageListeners(main: Main) {
ipcMain.on(IPC_MESSAGES.OPEN_MENU, (event) => { ipcMain.on(IPC_MESSAGES.OPEN_MENU, (event) => {
if (event.sender === null) { if (event.sender === null) {
@ -49,6 +83,25 @@ export default function registerIpcMainMessageListeners(main: Main) {
shell.openExternal(link).catch((err) => emitMainProcessError(err)); 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) => { ipcMain.on(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, (_, filePath: string) => {
return shell.showItemInFolder(filePath); return shell.showItemInFolder(filePath);
}); });

View File

@ -33,6 +33,14 @@
/> />
</button> </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 --> <!-- Download Button -->
<button v-if="value" class="p-0.5 rounded" @click="download"> <button v-if="value" class="p-0.5 rounded" @click="download">
<FeatherIcon <FeatherIcon
@ -125,6 +133,18 @@ export default defineComponent({
a.click(); a.click();
document.body.removeChild(a); 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) { async selectFile(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
const file = target.files?.[0]; const file = target.files?.[0];

View File

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