mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 03:16:11 +01:00
feat(react/ribbon): port image properties
This commit is contained in:
@@ -296,7 +296,7 @@ function isHtmlEmpty(html: string) {
|
||||
);
|
||||
}
|
||||
|
||||
async function clearBrowserCache() {
|
||||
export async function clearBrowserCache() {
|
||||
if (isElectron()) {
|
||||
const win = dynamicRequire("@electron/remote").getCurrentWindow();
|
||||
await win.webContents.session.clearCache();
|
||||
|
||||
@@ -10,6 +10,7 @@ import NoteContext from "../../components/note_context";
|
||||
import { ReactWrappedWidget } from "../basic_widget";
|
||||
import FNote from "../../entities/fnote";
|
||||
import attributes from "../../services/attributes";
|
||||
import FBlob from "../../entities/fblob";
|
||||
|
||||
type TriliumEventHandler<T extends EventNames> = (data: EventData<T>) => void;
|
||||
const registeredHandlers: Map<Component, Map<EventNames, TriliumEventHandler<any>[]>> = new Map();
|
||||
@@ -389,3 +390,24 @@ export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: s
|
||||
|
||||
return [ labelValue, setter ] as const;
|
||||
}
|
||||
|
||||
export function useNoteBlob(note: FNote | null | undefined): [ FBlob | null | undefined ] {
|
||||
if (!note) {
|
||||
return [ undefined ];
|
||||
}
|
||||
|
||||
const [ blob, setBlob ] = useState<FBlob | null>();
|
||||
|
||||
function refresh() {
|
||||
note?.getBlob().then(setBlob);
|
||||
}
|
||||
|
||||
useEffect(refresh, [ note?.noteId ]);
|
||||
useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => {
|
||||
if (note && loadResults.hasRevisionForNote(note.noteId)) {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
return [ blob ] as const;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ComponentChild, createContext, render, type JSX, type RefObject } from "preact";
|
||||
import Component from "../../components/component";
|
||||
import { EventData, EventNames } from "../../components/app_context";
|
||||
import { useContext } from "preact/hooks";
|
||||
|
||||
export const ParentComponent = createContext<Component | null>(null);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import { formatSize } from "../../services/utils";
|
||||
import FormFileUpload, { FormFileUploadButton } from "../react/FormFileUpload";
|
||||
import { useNoteLabel, useTriliumEventBeta } from "../react/hooks";
|
||||
import { useNoteBlob, useNoteLabel, useTriliumEventBeta } from "../react/hooks";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
import FBlob from "../../entities/fblob";
|
||||
import Button from "../react/Button";
|
||||
@@ -13,19 +13,8 @@ import server from "../../services/server";
|
||||
|
||||
export default function FilePropertiesTab({ note }: TabContext) {
|
||||
const [ originalFileName ] = useNoteLabel(note, "originalFileName");
|
||||
const [ blob, setBlob ] = useState<FBlob | null>();
|
||||
const canAccessProtectedNote = !note?.isProtected || protected_session_holder.isProtectedSessionAvailable();
|
||||
|
||||
function refresh() {
|
||||
note?.getBlob().then(setBlob);
|
||||
}
|
||||
|
||||
useEffect(refresh, [ note?.noteId ]);
|
||||
useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => {
|
||||
if (note && loadResults.hasRevisionForNote(note.noteId)) {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
const [ blob ] = useNoteBlob(note);
|
||||
|
||||
return (
|
||||
<div className="file-properties-widget">
|
||||
|
||||
82
apps/client/src/widgets/ribbon/ImagePropertiesTab.tsx
Normal file
82
apps/client/src/widgets/ribbon/ImagePropertiesTab.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { t } from "../../services/i18n";
|
||||
import { useNoteBlob, useNoteLabel } from "../react/hooks";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
import { clearBrowserCache, formatSize } from "../../services/utils";
|
||||
import Button from "../react/Button";
|
||||
import { downloadFileNote, openNoteExternally } from "../../services/open";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { useContext } from "preact/hooks";
|
||||
import { FormFileUploadButton } from "../react/FormFileUpload";
|
||||
import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
|
||||
export default function ImagePropertiesTab({ note, ntxId }: TabContext) {
|
||||
const [ originalFileName ] = useNoteLabel(note, "originalFileName");
|
||||
const [ blob ] = useNoteBlob(note);
|
||||
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
|
||||
return (
|
||||
<div className="image-properties">
|
||||
{note && (
|
||||
<>
|
||||
<div style={{ display: "flex", justifyContent: "space-evenly", margin: "10px" }}>
|
||||
<span>
|
||||
<strong>{t("image_properties.original_file_name")}:</strong>{" "}
|
||||
<span>{originalFileName ?? "?"}</span>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<strong>{t("image_properties.file_type")}:</strong>{" "}
|
||||
<span>{note.mime}</span>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<strong>{t("image_properties.file_size")}:</strong>{" "}
|
||||
<span>{formatSize(blob?.contentLength)}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "space-evenly", margin: "10px" }}>
|
||||
<Button
|
||||
text={t("image_properties.download")}
|
||||
icon="bx bx-download"
|
||||
primary
|
||||
onClick={() => downloadFileNote(note.noteId)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
text={t("image_properties.open")}
|
||||
icon="bx bx-link-external"
|
||||
onClick={() => openNoteExternally(note.noteId, note.mime)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
text={t("image_properties.copy_reference_to_clipboard")}
|
||||
icon="bx bx-copy"
|
||||
onClick={() => parentComponent?.triggerEvent("copyImageReferenceToClipboard", { ntxId })}
|
||||
/>
|
||||
|
||||
<FormFileUploadButton
|
||||
text={t("image_properties.upload_new_revision")}
|
||||
icon="bx bx-folder-open"
|
||||
onChange={async (files) => {
|
||||
if (!files) return;
|
||||
const fileToUpload = files[0]; // copy to allow reset below
|
||||
|
||||
const result = await server.upload(`images/${note.noteId}`, fileToUpload);
|
||||
|
||||
if (result.uploaded) {
|
||||
toast.showMessage(t("image_properties.upload_success"));
|
||||
await clearBrowserCache();
|
||||
} else {
|
||||
toast.showError(t("image_properties.upload_failed", { message: result.message }));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import NotePropertiesTab from "./NotePropertiesTab";
|
||||
import NoteInfoTab from "./NoteInfoTab";
|
||||
import SimilarNotesTab from "./SimilarNotesTab";
|
||||
import FilePropertiesTab from "./FilePropertiesTab";
|
||||
import ImagePropertiesTab from "./ImagePropertiesTab";
|
||||
|
||||
interface TitleContext {
|
||||
note: FNote | null | undefined;
|
||||
@@ -86,9 +87,12 @@ const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>([
|
||||
activate: true
|
||||
},
|
||||
{
|
||||
// ImagePropertiesWidget
|
||||
title: t("image_properties.title"),
|
||||
icon: "bx bx-image"
|
||||
icon: "bx bx-image",
|
||||
content: ImagePropertiesTab,
|
||||
show: ({ note }) => note?.type === "image",
|
||||
toggleCommand: "toggleRibbonTabImageProperties",
|
||||
activate: true,
|
||||
},
|
||||
{
|
||||
// BasicProperties
|
||||
@@ -135,7 +139,7 @@ const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>([
|
||||
]);
|
||||
|
||||
export default function Ribbon() {
|
||||
const { note } = useNoteContext();
|
||||
const { note, ntxId } = useNoteContext();
|
||||
const titleContext: TitleContext = { note };
|
||||
const [ activeTabIndex, setActiveTabIndex ] = useState<number | undefined>();
|
||||
const filteredTabs = useMemo(() => TAB_CONFIGURATION.filter(tab => tab.show?.(titleContext)), [ titleContext, note ]);
|
||||
@@ -171,7 +175,11 @@ export default function Ribbon() {
|
||||
return;
|
||||
}
|
||||
|
||||
return tab?.content && tab.content({ note, hidden: !isActive });
|
||||
return tab?.content && tab.content({
|
||||
note,
|
||||
hidden: !isActive,
|
||||
ntxId
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,4 +3,5 @@ import FNote from "../../entities/fnote";
|
||||
export interface TabContext {
|
||||
note: FNote | null | undefined;
|
||||
hidden: boolean;
|
||||
ntxId?: string | null | undefined;
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
import server from "../../services/server.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import openService from "../../services/open.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="image-properties">
|
||||
<div style="display: flex; justify-content: space-evenly; margin: 10px;">
|
||||
<span>
|
||||
<strong>${t("image_properties.original_file_name")}:</strong>
|
||||
<span class="image-filename"></span>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<strong>${t("image_properties.file_type")}:</strong>
|
||||
<span class="image-filetype"></span>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<strong>${t("image_properties.file_size")}:</strong>
|
||||
<span class="image-filesize"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="no-print" style="display: flex; justify-content: space-evenly; margin: 10px;">
|
||||
<button class="image-download btn btn-sm btn-primary" type="button">
|
||||
<span class="bx bx-download"></span>
|
||||
${t("image_properties.download")}
|
||||
</button>
|
||||
|
||||
<button class="image-open btn btn-sm btn-primary" type="button">
|
||||
<span class="bx bx-link-external"></span>
|
||||
${t("image_properties.open")}
|
||||
</button>
|
||||
|
||||
<button class="image-copy-reference-to-clipboard btn btn-sm btn-primary" type="button">
|
||||
<span class="bx bx-copy"></span>
|
||||
${t("image_properties.copy_reference_to_clipboard")}
|
||||
</button>
|
||||
|
||||
<button class="image-upload-new-revision btn btn-sm btn-primary" type="button">
|
||||
<span class="bx bx-folder-open"></span>
|
||||
${t("image_properties.upload_new_revision")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="file" class="image-upload-new-revision-input" style="display: none">
|
||||
</div>`;
|
||||
|
||||
export default class ImagePropertiesWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $copyReferenceToClipboardButton!: JQuery<HTMLElement>;
|
||||
private $uploadNewRevisionButton!: JQuery<HTMLElement>;
|
||||
private $uploadNewRevisionInput!: JQuery<HTMLFormElement>;
|
||||
private $fileName!: JQuery<HTMLElement>;
|
||||
private $fileType!: JQuery<HTMLElement>;
|
||||
private $fileSize!: JQuery<HTMLElement>;
|
||||
private $openButton!: JQuery<HTMLElement>;
|
||||
private $imageDownloadButton!: JQuery<HTMLElement>;
|
||||
|
||||
get name() {
|
||||
return "imageProperties";
|
||||
}
|
||||
|
||||
get toggleCommand() {
|
||||
return "toggleRibbonTabImageProperties";
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.note && this.note.type === "image";
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return {
|
||||
show: this.isEnabled(),
|
||||
activate: true,
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.contentSized();
|
||||
|
||||
this.$copyReferenceToClipboardButton = this.$widget.find(".image-copy-reference-to-clipboard");
|
||||
this.$copyReferenceToClipboardButton.on("click", () => this.triggerEvent(`copyImageReferenceToClipboard`, { ntxId: this.noteContext?.ntxId }));
|
||||
|
||||
this.$uploadNewRevisionButton = this.$widget.find(".image-upload-new-revision");
|
||||
this.$uploadNewRevisionInput = this.$widget.find(".image-upload-new-revision-input");
|
||||
|
||||
this.$fileName = this.$widget.find(".image-filename");
|
||||
this.$fileType = this.$widget.find(".image-filetype");
|
||||
this.$fileSize = this.$widget.find(".image-filesize");
|
||||
|
||||
this.$openButton = this.$widget.find(".image-open");
|
||||
this.$openButton.on("click", () => this.noteId && this.note && openService.openNoteExternally(this.noteId, this.note.mime));
|
||||
|
||||
this.$imageDownloadButton = this.$widget.find(".image-download");
|
||||
this.$imageDownloadButton.on("click", () => this.noteId && openService.downloadFileNote(this.noteId));
|
||||
|
||||
this.$uploadNewRevisionButton.on("click", () => {
|
||||
this.$uploadNewRevisionInput.trigger("click");
|
||||
});
|
||||
|
||||
this.$uploadNewRevisionInput.on("change", async () => {
|
||||
const fileToUpload = this.$uploadNewRevisionInput[0].files[0]; // copy to allow reset below
|
||||
this.$uploadNewRevisionInput.val("");
|
||||
|
||||
const result = await server.upload(`images/${this.noteId}`, fileToUpload);
|
||||
|
||||
if (result.uploaded) {
|
||||
toastService.showMessage(t("image_properties.upload_success"));
|
||||
|
||||
await utils.clearBrowserCache();
|
||||
|
||||
this.refresh();
|
||||
} else {
|
||||
toastService.showError(t("image_properties.upload_failed", { message: result.message }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.$widget.show();
|
||||
|
||||
const blob = await this.note?.getBlob();
|
||||
|
||||
this.$fileName.text(note.getLabelValue("originalFileName") || "?");
|
||||
this.$fileSize.text(utils.formatSize(blob?.contentLength ?? 0));
|
||||
this.$fileType.text(note.mime);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user