mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
modifying viewmodel to be more flexible -- preicon button and endiconbuttons
This commit is contained in:
parent
3b01234914
commit
b97802b1ac
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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">
|
||||
|
11
frontend/types/custom.d.ts
vendored
11
frontend/types/custom.d.ts
vendored
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user