mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
286 lines
12 KiB
JavaScript
286 lines
12 KiB
JavaScript
import { createElement, onDestroy } from "../../lib/skeleton/index.js";
|
|
import rxjs, { effect } from "../../lib/rx.js";
|
|
import ajax from "../../lib/ajax.js";
|
|
import { qs } from "../../lib/dom.js";
|
|
import { join } from "../../lib/path.js";
|
|
import { loadJS, loadCSS } from "../../helpers/loader.js";
|
|
import { buttonDownload } from "../../pages/viewerpage/component_menubar.js";
|
|
import { $ICON } from "../../pages/viewerpage/common_fab.js";
|
|
import { save } from "../../pages/viewerpage/model_files.js";
|
|
import "../../components/fab.js";
|
|
|
|
import { $toolbar } from "./lib/dom.js";
|
|
|
|
await loadCSS(import.meta.url, "./loader_lowa.css");
|
|
|
|
let $canvas = null;
|
|
window.Module = {
|
|
uno_scripts: [join(import.meta.url, "./lib/lowa/zeta.js"), join(import.meta.url, "./loader_lowa.uno.js")],
|
|
locateFile: (path, prefix) => (prefix || join(import.meta.url, "./lib/lowa/")) + path,
|
|
};
|
|
|
|
export default async function(render, { mime, getDownloadUrl, getFilename, $menubar, acl$ }) {
|
|
const canWrite = (await acl$.toPromise()).indexOf("POST") >= 0;
|
|
const $page = createElement(`
|
|
<div class="component_word">
|
|
<canvas id="qtcanvas" contenteditable="${canWrite}" style="visibility:hidden"></canvas>
|
|
<button is="component-fab" class="hidden"></button>
|
|
</div>
|
|
`);
|
|
render($page);
|
|
|
|
// feature1: init
|
|
const filename = getFilename();
|
|
const $fab = qs($page, `[is="component-fab"]`);
|
|
const $qcanvas = qs($page, "canvas");
|
|
if ($canvas) {
|
|
$qcanvas.remove();
|
|
$page.appendChild($canvas);
|
|
} else {
|
|
$canvas = $qcanvas;
|
|
}
|
|
Object.assign($canvas.style, {
|
|
width: "100%",
|
|
height: "100%",
|
|
});
|
|
|
|
// feature2: toolbar init
|
|
if (canWrite) {
|
|
$menubar.add(buttonDownload(filename, getDownloadUrl()));
|
|
if (isWriter(mime)) {
|
|
$menubar.add($toolbar.bullet);
|
|
$menubar.add($toolbar.alignment);
|
|
$menubar.add($toolbar.title);
|
|
}
|
|
$menubar.add($toolbar.size);
|
|
$menubar.add($toolbar.strike);
|
|
$menubar.add($toolbar.underline);
|
|
$menubar.add($toolbar.italic);
|
|
$menubar.add($toolbar.bold);
|
|
$menubar.add($toolbar.color);
|
|
}
|
|
|
|
// feature3: setup lowa
|
|
window.Module.canvas = $canvas;
|
|
await loadJS(import.meta.url, "./lib/lowa/soffice.js");
|
|
let port = await Module.uno_main;
|
|
onDestroy(() => {
|
|
$canvas.style.visibility = "hidden";
|
|
port.postMessage({ cmd: "destroy", mime });
|
|
});
|
|
|
|
// feature4: display rule for save button
|
|
const action$ = new rxjs.Subject();
|
|
if (canWrite) effect(rxjs.merge(rxjs.fromEvent($canvas, "keyup"), action$).pipe(rxjs.tap(() => {
|
|
$fab.classList.remove("hidden");
|
|
$fab.render($ICON.SAVING);
|
|
$fab.onclick = () => {
|
|
$fab.render($ICON.LOADING);
|
|
$fab.disabled = true;
|
|
port.postMessage({ cmd: "save" });
|
|
};
|
|
})));
|
|
|
|
// feature5: load file
|
|
await effect(ajax({ url: getDownloadUrl(), responseType: "arraybuffer" }).pipe(
|
|
rxjs.mergeMap(async ({ response }) => {
|
|
try { FS.mkdir("/tmp/office/"); } catch {}
|
|
await FS.writeFile("/tmp/office/" + filename , new Uint8Array(response));
|
|
await port.postMessage({ cmd: "load", filename, mime });
|
|
onDestroy(() => FS.unlink("/tmp/office/" + filename));
|
|
$canvas.focus();
|
|
}),
|
|
));
|
|
await new Promise((resolve) => {
|
|
port.onmessage = function(e) {
|
|
switch (e.data.cmd) {
|
|
case "loaded":
|
|
window.dispatchEvent(new Event("resize"));
|
|
setTimeout(() => {
|
|
resolve();
|
|
$canvas.style.visibility = "visible";
|
|
}, 250);
|
|
break;
|
|
case "save":
|
|
const bytes = FS.readFile("/tmp/office/" + filename);
|
|
effect(save(new Blob([bytes], {})).pipe(rxjs.tap(() => {
|
|
$fab.classList.add("hidden");
|
|
$fab.render($ICON.SAVING);
|
|
$fab.disabled = false;
|
|
})));
|
|
break;
|
|
case "setFormat":
|
|
switch(e.data.id) {
|
|
case "Bold":
|
|
e.data.state ? $toolbar.bold.classList.add("active") : $toolbar.bold.classList.remove("active");
|
|
break;
|
|
case "Italic":
|
|
e.data.state ? $toolbar.italic.classList.add("active") : $toolbar.italic.classList.remove("active");
|
|
break;
|
|
case "Underline":
|
|
e.data.state ? $toolbar.underline.classList.add("active") : $toolbar.underline.classList.remove("active");
|
|
break;
|
|
case "Strikeout":
|
|
e.data.state ? $toolbar.strike.classList.add("active") : $toolbar.strike.classList.remove("active");
|
|
break;
|
|
case "LeftPara":
|
|
if (e.data.state) qs($toolbar.alignment, "select").value = "left";
|
|
break;
|
|
case "RightPara":
|
|
if (e.data.state) qs($toolbar.alignment, "select").value = "right";
|
|
break;
|
|
case "CenterPara":
|
|
if (e.data.state) qs($toolbar.alignment, "select").value = "center";
|
|
break;
|
|
case "JustifyPara":
|
|
if (e.data.state) qs($toolbar.alignment, "select").value = "justify";
|
|
break;
|
|
case "DefaultBullet":
|
|
qs($toolbar.bullet, "select").value = e.data.state ? "ul" : "normal";
|
|
break;
|
|
case "DefaultNumbering":
|
|
qs($toolbar.bullet, "select").value = e.data.state ? "ol" : "normal";
|
|
break;
|
|
case "StyleApply":
|
|
let value = "normal";
|
|
if (e.data.state === "Title") value = "title";
|
|
else if (e.data.state === "Heading 1") value = "head1";
|
|
else if (e.data.state === "Heading 2") value = "head2";
|
|
else if (e.data.state === "Heading 3") value = "head3";
|
|
qs($toolbar.title, "select").value = value;
|
|
break;
|
|
case "Color":
|
|
const hex = e.data.state && e.data.state > 0 ? "#" + e.data.state.toString(16).padStart(6, "0") : "#000000";
|
|
$toolbar.color.children[0].style.fill = hex;
|
|
$toolbar.color.children[1].value = hex;
|
|
break;
|
|
case "FontHeight":
|
|
const fontSize = e.data.state;
|
|
qs($toolbar.size, "input").value = fontSize;
|
|
break;
|
|
default:
|
|
console.log("format", e);
|
|
throw new Error("Unknown format");
|
|
}
|
|
$canvas.focus();
|
|
break;
|
|
default:
|
|
console.log("message", e);
|
|
throw new Error("Unknown message");
|
|
}
|
|
};
|
|
});
|
|
|
|
// feature6: toolbar events
|
|
$toolbar.bold.onclick = () => {
|
|
$toolbar.bold.classList.toggle("active");
|
|
action$.next();
|
|
port.postMessage({ cmd: "toggleFormatting", id: "Bold" });
|
|
};
|
|
$toolbar.italic.onclick = () => {
|
|
$toolbar.italic.classList.toggle("active");
|
|
action$.next();
|
|
port.postMessage({ cmd: "toggleFormatting", id: "Italic" });
|
|
};
|
|
$toolbar.underline.onclick = () => {
|
|
$toolbar.underline.classList.toggle("active");
|
|
action$.next();
|
|
port.postMessage({ cmd: "toggleFormatting", id: "Underline" });
|
|
};
|
|
$toolbar.bullet.onchange = (e) => {
|
|
switch(e.target.value) {
|
|
case "normal":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "RemoveBullets" });
|
|
break;
|
|
case "ul":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "DefaultBullet" });
|
|
break;
|
|
case "ol":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "DefaultNumbering" });
|
|
break;
|
|
}
|
|
action$.next();
|
|
};
|
|
$toolbar.strike.onclick = () => {
|
|
$toolbar.strike.classList.toggle("active");
|
|
action$.next();
|
|
port.postMessage({ cmd: "toggleFormatting", id: "Strikeout" });
|
|
};
|
|
$toolbar.alignment.onchange = (e) => {
|
|
switch(e.target.value) {
|
|
case "left":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "LeftPara" });
|
|
break;
|
|
case "right":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "RightPara" });
|
|
break;
|
|
case "center":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "CenterPara" });
|
|
break;
|
|
case "justify":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "JustifyPara" });
|
|
break;
|
|
default:
|
|
throw new Error("Unknown tool alignment");
|
|
}
|
|
action$.next();
|
|
};
|
|
$toolbar.title.onchange = (e) => {
|
|
switch(e.target.value) {
|
|
case "normal":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Standard&FamilyName:string=ParagraphStyles" });
|
|
break;
|
|
case "title":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Title&FamilyName:string=ParagraphStyles" });
|
|
break;
|
|
case "head1":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Heading 1&FamilyName:string=ParagraphStyles" });
|
|
break;
|
|
case "head2":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Heading 2&FamilyName:string=ParagraphStyles" });
|
|
break;
|
|
case "head3":
|
|
port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Heading 3&FamilyName:string=ParagraphStyles" });
|
|
break;
|
|
default:
|
|
throw new Error("Unknown text style");
|
|
}
|
|
action$.next();
|
|
};
|
|
$toolbar.color.onclick = (e) => {
|
|
if (e.target.tagName === "INPUT") return;
|
|
const $svg = e.target.closest("svg")
|
|
const $input = $svg.nextElementSibling;
|
|
$input.onchange = (e) => {
|
|
$svg.style.fill = e.target.value;
|
|
const color = parseInt(e.target.value.slice(1), 16);
|
|
port.postMessage({ cmd: "toggleFormatting", id: `Color?Color:long=${color}` })
|
|
};
|
|
$input.click();
|
|
action$.next();
|
|
};
|
|
effect(rxjs.fromEvent(qs($toolbar.size, "input"), "keyup").pipe(
|
|
rxjs.debounceTime(250),
|
|
rxjs.tap((e) => {
|
|
const fontSize = parseInt(e.target.value);
|
|
port.postMessage({ cmd: "toggleFormatting", id: `FontHeight?FontHeight.Height:float=${fontSize}` });
|
|
action$.next();
|
|
}),
|
|
));
|
|
|
|
// feature7: workaround known lowa bug
|
|
// - when pressing escape, lowa goes out of fullscreen and show some unwanted stuff
|
|
// - context menu functions like "replace" image which does crash everything with errors generated from soffice.js
|
|
// - ctrl + s is broken
|
|
effect(rxjs.fromEvent($page, "keydown", { capture: true }).pipe(rxjs.tap((e) => {
|
|
if (e.key === "Escape") e.stopPropagation();
|
|
if (e.key === "s" && e.ctrlKey) e.stopPropagation();
|
|
})));
|
|
effect(rxjs.fromEvent($page, "mousedown", { capture: true }).pipe(rxjs.tap((e) => {
|
|
if (e.which === 3) e.stopPropagation();
|
|
})));
|
|
}
|
|
|
|
function isWriter(mime) {
|
|
return ["application/word", "application/msword", "application/rtf", "application/vnd.oasis.opendocument.text"].indexOf(mime) >= 0;
|
|
}
|