From b97802b1acfabae453888d8b377254f0d3474707 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 8 Jul 2024 16:36:30 -0700 Subject: [PATCH] modifying viewmodel to be more flexible -- preicon button and endiconbuttons --- frontend/app/block/block.less | 17 ++++++- frontend/app/block/block.tsx | 64 +++++++++++++++++--------- frontend/app/view/directorypreview.tsx | 47 ++++++++++--------- frontend/app/view/preview.tsx | 42 +++++++++++++---- frontend/types/custom.d.ts | 11 ++++- 5 files changed, 125 insertions(+), 56 deletions(-) diff --git a/frontend/app/block/block.less b/frontend/app/block/block.less index c4d75ee65..5550eda0a 100644 --- a/frontend/app/block/block.less +++ b/frontend/app/block/block.less @@ -84,8 +84,13 @@ gap: 8px; color: var(--main-text-color); - .block-frame-back-button { + .block-frame-preicon-button { + opacity: 0.7; cursor: pointer; + + &:hover { + opacity: 1; + } } .block-frame-view-icon { @@ -117,6 +122,16 @@ display: flex; align-items: center; + .block-frame-endicon-button { + opacity: 0.7; + cursor: pointer; + padding: 4px 6px; + + &:hover { + opacity: 1; + } + } + .block-frame-settings { display: flex; width: 24px; diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 46c1267b4..21962c555 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -230,8 +230,8 @@ const BlockFrame_Default_Component = ({ let isFocused = jotai.useAtomValue(isFocusedAtom); const viewIcon = jotai.useAtomValue(viewModel.viewIcon); const viewText = jotai.useAtomValue(viewModel.viewText); - const hasBackButton = jotai.useAtomValue(viewModel.hasBackButton); - const hasForwardButton = jotai.useAtomValue(viewModel.hasForwardButton); + const preIconButton = jotai.useAtomValue(viewModel.preIconButton); + const endIconButtons = jotai.useAtomValue(viewModel.endIconButtons); if (preview) { isFocused = true; } @@ -242,6 +242,43 @@ const BlockFrame_Default_Component = ({ if (isFocused && blockData?.meta?.["frame:bordercolor:focused"]) { style.borderColor = blockData.meta["frame:bordercolor:focused"]; } + let preIconButtonElem: JSX.Element = null; + if (preIconButton) { + preIconButtonElem = ( +
+ +
+ ); + } + let endIconsElem: JSX.Element[] = []; + if (endIconButtons && endIconButtons.length > 0) { + for (let idx = 0; idx < endIconButtons.length; idx++) { + const button = endIconButtons[idx]; + endIconsElem.push( +
+ +
+ ); + } + } + endIconsElem.push( +
handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose)} + > + +
+ ); + endIconsElem.push( +
+ +
+ ); return (
handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose)} >
- {hasBackButton && !hasForwardButton && ( -
- -
- )} + {preIconButtonElem}
{getBlockHeaderIcon(viewIcon, blockData)}
{blockViewToName(blockData?.view)}
{settingsConfig?.blockheader?.showblockids && ( @@ -275,19 +308,8 @@ const BlockFrame_Default_Component = ({
{util.isBlank(viewText) ? null :
{viewText}
}
-
-
handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose)} - > - -
-
- -
-
+
{endIconsElem}
- {preview ?
: children}
); @@ -412,8 +434,8 @@ function makeDefaultViewModel(blockId: string): ViewModel { const blockData = get(blockDataAtom); return blockData?.meta?.title; }), - hasBackButton: jotai.atom(false), - hasForwardButton: jotai.atom(false), + preIconButton: jotai.atom(null), + endIconButtons: jotai.atom(null), hasSearch: jotai.atom(false), }; return viewModel; diff --git a/frontend/app/view/directorypreview.tsx b/frontend/app/view/directorypreview.tsx index dd3967c26..3d832aa1c 100644 --- a/frontend/app/view/directorypreview.tsx +++ b/frontend/app/view/directorypreview.tsx @@ -4,6 +4,7 @@ import * as services from "@/store/services"; import * as keyutil from "@/util/keyutil"; import * as util from "@/util/util"; +import type { PreviewModel } from "@/view/preview"; import { Row, Table, @@ -30,7 +31,7 @@ interface DirectoryTableProps { setFileName: (_: string) => void; setSearch: (_: string) => void; setSelectedPath: (_: string) => void; - setRefresh: React.Dispatch>; + setRefreshVersion: React.Dispatch>; } const columnHelper = createColumnHelper(); @@ -143,7 +144,7 @@ function DirectoryTable({ setFileName, setSearch, setSelectedPath, - setRefresh, + setRefreshVersion, }: DirectoryTableProps) { let settings = jotai.useAtomValue(atoms.settingsConfigAtom); const getIconFromMimeType = React.useCallback( @@ -299,7 +300,7 @@ function DirectoryTable({ setFocusIndex={setFocusIndex} setSearch={setSearch} setSelectedPath={setSelectedPath} - setRefresh={setRefresh} + setRefreshVersion={setRefreshVersion} /> ) : ( )} @@ -327,7 +328,7 @@ interface TableBodyProps { setFileName: (_: string) => void; setSearch: (_: string) => void; setSelectedPath: (_: string) => void; - setRefresh: React.Dispatch>; + setRefreshVersion: React.Dispatch>; } function TableBody({ @@ -339,7 +340,7 @@ function TableBody({ setFileName, setSearch, setSelectedPath, - setRefresh, + setRefreshVersion, }: TableBodyProps) { const dummyLineRef = React.useRef(null); const parentRef = React.useRef(null); @@ -391,7 +392,7 @@ function TableBody({ label: "Delete File", click: async () => { await services.FileService.DeleteFile(path).catch((e) => console.log(e)); //todo these errors need a popup - setRefresh((current) => !current); + setRefreshVersion((current) => current + 1); }, }); menu.push({ @@ -402,7 +403,7 @@ function TableBody({ }); ContextMenuModel.showContextMenu(menu, e); }, - [setRefresh] + [setRefreshVersion] ); const displayRow = React.useCallback( @@ -463,17 +464,26 @@ const MemoizedTableBody = React.memo( interface DirectoryPreviewProps { fileNameAtom: jotai.WritableAtom; + model: PreviewModel; } -function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) { - console.log("DirectoryPreview render"); +function DirectoryPreview({ fileNameAtom, model }: DirectoryPreviewProps) { const [searchText, setSearchText] = React.useState(""); const [focusIndex, setFocusIndex] = React.useState(0); const [content, setContent] = React.useState([]); const [fileName, setFileName] = jotai.useAtom(fileNameAtom); - const [hideHiddenFiles, setHideHiddenFiles] = React.useState(true); + const [hideHiddenFiles, setHideHiddenFiles] = jotai.useAtom(model.showHiddenFiles); const [selectedPath, setSelectedPath] = React.useState(""); - const [refresh, setRefresh] = React.useState(false); + const [refreshVersion, setRefreshVersion] = React.useState(0); + + React.useEffect(() => { + model.refreshCallback = () => { + setRefreshVersion((refreshVersion) => refreshVersion + 1); + }; + return () => { + model.refreshCallback = null; + }; + }, [setRefreshVersion]); React.useEffect(() => { const getContent = async () => { @@ -489,7 +499,7 @@ function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) { setContent(filtered); }; getContent(); - }, [fileName, searchText, hideHiddenFiles, refresh]); + }, [fileName, searchText, hideHiddenFiles, refreshVersion]); const handleKeyDown = React.useCallback( (waveEvent: WaveKeyboardEvent): boolean => { @@ -533,15 +543,6 @@ function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) { autoFocus={true} value={searchText} /> -
setHideHiddenFiles((current) => !current)} className="dir-table-button"> - {!hideHiddenFiles && } - {hideHiddenFiles && } - {}}> -
-
setRefresh((current) => !current)} className="dir-table-button"> - - {}}> -
); diff --git a/frontend/app/view/preview.tsx b/frontend/app/view/preview.tsx index c0f6b900a..476024b82 100644 --- a/frontend/app/view/preview.tsx +++ b/frontend/app/view/preview.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Markdown } from "@/element/markdown"; -import { getBackendHostPort, getObjectId, globalStore, useBlockAtom } from "@/store/global"; +import { getBackendHostPort, globalStore, useBlockAtom } from "@/store/global"; import * as services from "@/store/services"; import * as WOS from "@/store/wos"; import * as util from "@/util/util"; @@ -26,8 +26,8 @@ export class PreviewModel implements ViewModel { viewIcon: jotai.Atom; viewName: jotai.Atom; viewText: jotai.Atom; - hasBackButton: jotai.Atom; - hasForwardButton: jotai.Atom; + preIconButton: jotai.Atom; + endIconButtons: jotai.Atom; hasSearch: jotai.Atom; fileName: jotai.WritableAtom; @@ -37,12 +37,16 @@ export class PreviewModel implements ViewModel { fileMimeTypeLoadable: jotai.Atom>; fileContent: jotai.Atom>; + showHiddenFiles: jotai.PrimitiveAtom; + refreshCallback: () => void; + setPreviewFileName(fileName: string) { services.ObjectService.UpdateObjectMeta(`block:${this.blockId}`, { file: fileName }); } constructor(blockId: string) { this.blockId = blockId; + this.showHiddenFiles = jotai.atom(true); this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); this.viewIcon = jotai.atom((get) => { let blockData = get(this.blockAtom); @@ -57,13 +61,34 @@ export class PreviewModel implements ViewModel { this.viewText = jotai.atom((get) => { return get(this.fileName); }); - this.hasBackButton = jotai.atom(true); - this.hasForwardButton = jotai.atom((get) => { + this.preIconButton = jotai.atom((get) => { const mimeType = util.jotaiLoadableValue(get(this.fileMimeTypeLoadable), ""); if (mimeType == "directory") { - return true; + return null; } - return false; + return { + icon: "chevron-left", + click: this.onBack.bind(this), + }; + }); + this.endIconButtons = jotai.atom((get) => { + const mimeType = util.jotaiLoadableValue(get(this.fileMimeTypeLoadable), ""); + if (mimeType == "directory") { + let showHiddenFiles = get(this.showHiddenFiles); + return [ + { + icon: showHiddenFiles ? "eye" : "eye-slash", + click: () => { + globalStore.set(this.showHiddenFiles, (prev) => !prev); + }, + }, + { + icon: "arrows-rotate", + click: () => this.refreshCallback?.(), + }, + ]; + } + return null; }); this.hasSearch = jotai.atom(false); @@ -310,7 +335,6 @@ function iconForFile(mimeType: string, fileName: string): string { } function PreviewView({ blockId, model }: { blockId: string; model: PreviewModel }) { - console.log("render previewview", getObjectId(model)); const ref = useRef(null); const blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); const fileNameAtom = model.fileName; @@ -359,7 +383,7 @@ function PreviewView({ blockId, model }: { blockId: string; model: PreviewModel ) { specializedView = ; } else if (mimeType === "directory") { - specializedView = ; + specializedView = ; } else { specializedView = (
diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 657328b05..5ef825cd8 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -116,12 +116,19 @@ declare global { type SubjectWithRef = rxjs.Subject & { refCount: number; release: () => void }; + type IconButtonDecl = { + icon: string; + title?: string; + click: () => void; + }; + interface ViewModel { viewIcon: jotai.Atom; viewName: jotai.Atom; viewText: jotai.Atom; - hasBackButton: jotai.Atom; - hasForwardButton: jotai.Atom; + preIconButton: jotai.Atom; + endIconButtons: jotai.Atom; + hasSearch: jotai.Atom; onBack?: () => void;