modifying viewmodel to be more flexible -- preicon button and endiconbuttons

This commit is contained in:
sawka 2024-07-08 16:36:30 -07:00
parent 3b01234914
commit b97802b1ac
5 changed files with 125 additions and 56 deletions

View File

@ -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;

View File

@ -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 = (
<div className="block-frame-preicon-button" title={preIconButton.title} onClick={preIconButton.click}>
<i className={util.makeIconClass(preIconButton.icon, true)} />
</div>
);
}
let endIconsElem: JSX.Element[] = [];
if (endIconButtons && endIconButtons.length > 0) {
for (let idx = 0; idx < endIconButtons.length; idx++) {
const button = endIconButtons[idx];
endIconsElem.push(
<div key={idx} className="block-frame-endicon-button" title={button.title} onClick={button.click}>
<i className={util.makeIconClass(button.icon, true)} />
</div>
);
}
}
endIconsElem.push(
<div
key="settings"
className="block-frame-endicon-button block-frame-settings"
onClick={(e) => handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose)}
>
<i className="fa fa-solid fa-cog fa-fw" />
</div>
);
endIconsElem.push(
<div
key="close"
className={clsx("block-frame-endicon-button block-frame-default-close")}
onClick={layoutModel?.onClose}
>
<i className="fa fa-solid fa-xmark-large fa-fw" />
</div>
);
return (
<div
className={clsx(
@ -262,11 +299,7 @@ const BlockFrame_Default_Component = ({
onContextMenu={(e) => handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose)}
>
<div className="block-frame-default-header-iconview">
{hasBackButton && !hasForwardButton && (
<div className="block-frame-back-button" onClick={viewModel.onBack}>
<i className="fa fa-solid fa-chevron-left fa-fw" />
</div>
)}
{preIconButtonElem}
<div className="block-frame-view-icon">{getBlockHeaderIcon(viewIcon, blockData)}</div>
<div className="block-frame-view-type">{blockViewToName(blockData?.view)}</div>
{settingsConfig?.blockheader?.showblockids && (
@ -275,19 +308,8 @@ const BlockFrame_Default_Component = ({
</div>
{util.isBlank(viewText) ? null : <div className="block-frame-text">{viewText}</div>}
<div className="flex-spacer"></div>
<div className="block-frame-end-icons">
<div
className="block-frame-settings"
onClick={(e) => handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose)}
>
<i className="fa fa-solid fa-cog fa-fw" />
<div className="block-frame-end-icons">{endIconsElem}</div>
</div>
<div className={clsx("block-frame-default-close")} onClick={layoutModel?.onClose}>
<i className="fa fa-solid fa-xmark-large fa-fw" />
</div>
</div>
</div>
{preview ? <div className="block-frame-preview" /> : children}
</div>
);
@ -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;

View File

@ -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<React.SetStateAction<boolean>>;
setRefreshVersion: React.Dispatch<React.SetStateAction<number>>;
}
const columnHelper = createColumnHelper<FileInfo>();
@ -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}
/>
) : (
<TableBody
@ -311,7 +312,7 @@ function DirectoryTable({
setFocusIndex={setFocusIndex}
setSearch={setSearch}
setSelectedPath={setSelectedPath}
setRefresh={setRefresh}
setRefreshVersion={setRefreshVersion}
/>
)}
</div>
@ -327,7 +328,7 @@ interface TableBodyProps {
setFileName: (_: string) => void;
setSearch: (_: string) => void;
setSelectedPath: (_: string) => void;
setRefresh: React.Dispatch<React.SetStateAction<boolean>>;
setRefreshVersion: React.Dispatch<React.SetStateAction<number>>;
}
function TableBody({
@ -339,7 +340,7 @@ function TableBody({
setFileName,
setSearch,
setSelectedPath,
setRefresh,
setRefreshVersion,
}: TableBodyProps) {
const dummyLineRef = React.useRef<HTMLDivElement>(null);
const parentRef = React.useRef<HTMLDivElement>(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<string, [string], void>;
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<FileInfo[]>([]);
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}
/>
<div onClick={() => setHideHiddenFiles((current) => !current)} className="dir-table-button">
{!hideHiddenFiles && <i className={"fa-sharp fa-solid fa-eye-slash"} />}
{hideHiddenFiles && <i className={"fa-sharp fa-solid fa-eye"} />}
<input type="text" value={searchText} onChange={() => {}}></input>
</div>
<div onClick={() => setRefresh((current) => !current)} className="dir-table-button">
<i className="fa-solid fa-arrows-rotate" />
<input type="text" value={searchText} onChange={() => {}}></input>
</div>
</div>
<DirectoryTable
data={content}
@ -551,7 +552,7 @@ function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) {
setFocusIndex={setFocusIndex}
setSearch={setSearchText}
setSelectedPath={setSelectedPath}
setRefresh={setRefresh}
setRefreshVersion={setRefreshVersion}
/>
</div>
);

View File

@ -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<string>;
viewName: jotai.Atom<string>;
viewText: jotai.Atom<string>;
hasBackButton: jotai.Atom<boolean>;
hasForwardButton: jotai.Atom<boolean>;
preIconButton: jotai.Atom<IconButtonDecl>;
endIconButtons: jotai.Atom<IconButtonDecl[]>;
hasSearch: jotai.Atom<boolean>;
fileName: jotai.WritableAtom<string, [string], void>;
@ -37,12 +37,16 @@ export class PreviewModel implements ViewModel {
fileMimeTypeLoadable: jotai.Atom<Loadable<string>>;
fileContent: jotai.Atom<Promise<string>>;
showHiddenFiles: jotai.PrimitiveAtom<boolean>;
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>(`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<HTMLDivElement>(null);
const blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
const fileNameAtom = model.fileName;
@ -359,7 +383,7 @@ function PreviewView({ blockId, model }: { blockId: string; model: PreviewModel
) {
specializedView = <CodeEditPreview readonly={true} contentAtom={fileContentAtom} filename={fileName} />;
} else if (mimeType === "directory") {
specializedView = <DirectoryPreview fileNameAtom={fileNameAtom} />;
specializedView = <DirectoryPreview fileNameAtom={fileNameAtom} model={model} />;
} else {
specializedView = (
<div className="view-preview">

View File

@ -116,12 +116,19 @@ declare global {
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
type IconButtonDecl = {
icon: string;
title?: string;
click: () => void;
};
interface ViewModel {
viewIcon: jotai.Atom<string>;
viewName: jotai.Atom<string>;
viewText: jotai.Atom<string>;
hasBackButton: jotai.Atom<boolean>;
hasForwardButton: jotai.Atom<boolean>;
preIconButton: jotai.Atom<IconButtonDecl>;
endIconButtons: jotai.Atom<IconButtonDecl[]>;
hasSearch: jotai.Atom<boolean>;
onBack?: () => void;