Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add per-block WebView homepage setting, help view is now just a skinned webview #982

Merged
merged 8 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 36 additions & 36 deletions frontend/app/block/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import {
registerBlockComponentModel,
unregisterBlockComponentModel,
} from "@/store/global";
import * as WOS from "@/store/wos";
import { getWaveObjectAtom, makeORef, useWaveObjectValue } from "@/store/wos";
import { focusedBlockId, getElemAsStr } from "@/util/focusutil";
import * as util from "@/util/util";
import { isBlank } from "@/util/util";
import { CpuPlotView, CpuPlotViewModel, makeCpuPlotViewModel } from "@/view/cpuplot/cpuplot";
import { HelpView, HelpViewModel, makeHelpViewModel } from "@/view/helpview/helpview";
import { QuickTipsView, QuickTipsViewModel } from "@/view/quicktipsview/quicktipsview";
import { TermViewModel, TerminalView, makeTerminalModel } from "@/view/term/term";
import { WaveAi, WaveAiModel, makeWaveAiViewModel } from "@/view/waveai/waveai";
import { WebView, WebViewModel, makeWebViewModel } from "@/view/webview/webview";
import * as jotai from "jotai";
import * as React from "react";
import { QuickTipsView, QuickTipsViewModel } from "../view/quicktipsview/quicktipsview";
import { atom, useAtomValue } from "jotai";
import { Suspense, memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import "./block.less";
import { BlockFrame } from "./blockframe";
import { blockViewToIcon, blockViewToName } from "./blockutil";
Expand Down Expand Up @@ -51,7 +51,7 @@ function makeViewModel(blockId: string, blockView: string, nodeModel: NodeModel)
return makeCpuPlotViewModel(blockId);
}
if (blockView === "help") {
return makeHelpViewModel(blockId);
return makeHelpViewModel(blockId, nodeModel);
}
return makeDefaultViewModel(blockId, blockView);
}
Expand All @@ -63,7 +63,7 @@ function getViewElem(
blockView: string,
viewModel: ViewModel
): JSX.Element {
if (util.isBlank(blockView)) {
if (isBlank(blockView)) {
return <CenteredDiv>No View</CenteredDiv>;
}
if (blockView === "term") {
Expand Down Expand Up @@ -102,25 +102,25 @@ function getViewElem(
}

function makeDefaultViewModel(blockId: string, viewType: string): ViewModel {
const blockDataAtom = WOS.getWaveObjectAtom<Block>(WOS.makeORef("block", blockId));
const blockDataAtom = getWaveObjectAtom<Block>(makeORef("block", blockId));
let viewModel: ViewModel = {
viewType: viewType,
viewIcon: jotai.atom((get) => {
viewIcon: atom((get) => {
const blockData = get(blockDataAtom);
return blockViewToIcon(blockData?.meta?.view);
}),
viewName: jotai.atom((get) => {
viewName: atom((get) => {
const blockData = get(blockDataAtom);
return blockViewToName(blockData?.meta?.view);
}),
preIconButton: jotai.atom(null),
endIconButtons: jotai.atom(null),
preIconButton: atom(null),
endIconButtons: atom(null),
};
return viewModel;
}

const BlockPreview = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
const BlockPreview = memo(({ nodeModel, viewModel }: FullBlockProps) => {
const [blockData] = useWaveObjectValue<Block>(makeORef("block", nodeModel.blockId));
if (!blockData) {
return null;
}
Expand All @@ -135,22 +135,22 @@ const BlockPreview = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
);
});

const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => {
counterInc("render-BlockFull");
const focusElemRef = React.useRef<HTMLInputElement>(null);
const blockRef = React.useRef<HTMLDivElement>(null);
const contentRef = React.useRef<HTMLDivElement>(null);
const [blockClicked, setBlockClicked] = React.useState(false);
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
const isFocused = jotai.useAtomValue(nodeModel.isFocused);
const disablePointerEvents = jotai.useAtomValue(nodeModel.disablePointerEvents);
const focusElemRef = useRef<HTMLInputElement>(null);
const blockRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const [blockClicked, setBlockClicked] = useState(false);
const [blockData] = useWaveObjectValue<Block>(makeORef("block", nodeModel.blockId));
const isFocused = useAtomValue(nodeModel.isFocused);
const disablePointerEvents = useAtomValue(nodeModel.disablePointerEvents);
const innerRect = useDebouncedNodeInnerRect(nodeModel);

React.useLayoutEffect(() => {
useLayoutEffect(() => {
setBlockClicked(isFocused);
}, [isFocused]);

React.useLayoutEffect(() => {
useLayoutEffect(() => {
if (!blockClicked) {
return;
}
Expand All @@ -164,13 +164,13 @@ const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
}
}, [blockClicked, isFocused]);

const setBlockClickedTrue = React.useCallback(() => {
const setBlockClickedTrue = useCallback(() => {
setBlockClicked(true);
}, []);

const [blockContentOffset, setBlockContentOffset] = React.useState<Dimensions>();
const [blockContentOffset, setBlockContentOffset] = useState<Dimensions>();

React.useEffect(() => {
useEffect(() => {
if (blockRef.current && contentRef.current) {
const blockRect = blockRef.current.getBoundingClientRect();
const contentRect = contentRef.current.getBoundingClientRect();
Expand All @@ -183,7 +183,7 @@ const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
}
}, [blockRef, contentRef]);

const blockContentStyle = React.useMemo<React.CSSProperties>(() => {
const blockContentStyle = useMemo<React.CSSProperties>(() => {
const retVal: React.CSSProperties = {
pointerEvents: disablePointerEvents ? "none" : undefined,
};
Expand All @@ -194,12 +194,12 @@ const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
return retVal;
}, [innerRect, disablePointerEvents, blockContentOffset]);

const viewElem = React.useMemo(
const viewElem = useMemo(
() => getViewElem(nodeModel.blockId, blockRef, contentRef, blockData?.meta?.view, viewModel),
[nodeModel.blockId, blockData?.meta?.view, viewModel]
);

const handleChildFocus = React.useCallback(
const handleChildFocus = useCallback(
(event: React.FocusEvent<HTMLDivElement, Element>) => {
console.log("setFocusedChild", nodeModel.blockId, getElemAsStr(event.target));
if (!isFocused) {
Expand All @@ -210,7 +210,7 @@ const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
[isFocused]
);

const setFocusTarget = React.useCallback(() => {
const setFocusTarget = useCallback(() => {
const ok = viewModel?.giveFocus?.();
if (ok) {
return;
Expand Down Expand Up @@ -244,29 +244,29 @@ const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
</div>
<div key="content" className="block-content" ref={contentRef} style={blockContentStyle}>
<ErrorBoundary>
<React.Suspense fallback={<CenteredDiv>Loading...</CenteredDiv>}>{viewElem}</React.Suspense>
<Suspense fallback={<CenteredDiv>Loading...</CenteredDiv>}>{viewElem}</Suspense>
</ErrorBoundary>
</div>
</BlockFrame>
);
});

const Block = React.memo((props: BlockProps) => {
const Block = memo((props: BlockProps) => {
counterInc("render-Block");
counterInc("render-Block-" + props.nodeModel.blockId.substring(0, 8));
const [blockData, loading] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", props.nodeModel.blockId));
const [blockData, loading] = useWaveObjectValue<Block>(makeORef("block", props.nodeModel.blockId));
const bcm = getBlockComponentModel(props.nodeModel.blockId);
let viewModel = bcm?.viewModel;
if (viewModel == null || viewModel.viewType != blockData?.meta?.view) {
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel);
registerBlockComponentModel(props.nodeModel.blockId, { viewModel });
}
React.useEffect(() => {
useEffect(() => {
return () => {
unregisterBlockComponentModel(props.nodeModel.blockId);
};
}, []);
if (loading || util.isBlank(props.nodeModel.blockId) || blockData == null) {
if (loading || isBlank(props.nodeModel.blockId) || blockData == null) {
return null;
}
if (props.preview) {
Expand Down
61 changes: 22 additions & 39 deletions frontend/app/view/helpview/helpview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,46 @@
// SPDX-License-Identifier: Apache-2.0

import { getApi } from "@/app/store/global";
import { WebView, WebViewModel } from "@/app/view/webview/webview";
import { NodeModel } from "@/layout/index";
import { WebviewTag } from "electron";
import { createRef, useEffect, useState } from "react";
import { atom } from "jotai";
import { createRef } from "react";
import "./helpview.less";

class HelpViewModel implements ViewModel {
class HelpViewModel extends WebViewModel {
viewType: string;
blockId: string;
webviewRef: React.RefObject<WebviewTag>;

constructor(blockId: string) {
constructor(blockId: string, nodeModel: NodeModel) {
super(blockId, nodeModel);
this.getSettingsMenuItems = undefined;
this.viewText = atom([
{
elemtype: "iconbutton",
icon: "house",
click: this.handleHome.bind(this),
disabled: this.shouldDisabledHomeButton(),
},
]);
this.homepageUrl = atom(getApi().getDocsiteUrl());
this.viewType = "help";
this.blockId = blockId;
this.viewIcon = atom("circle-question");
this.viewName = atom("Help");
this.webviewRef = createRef<WebviewTag>();
}
}

function makeHelpViewModel(blockId: string) {
return new HelpViewModel(blockId);
function makeHelpViewModel(blockId: string, nodeModel: NodeModel) {
return new HelpViewModel(blockId, nodeModel);
}

function HelpView({ model }: { model: HelpViewModel }) {
const [url] = useState(() => getApi().getDocsiteUrl());
const [webContentsId, setWebContentsId] = useState(null);
const [domReady, setDomReady] = useState(false);

useEffect(() => {
if (model.webviewRef.current && domReady) {
const wcId = model.webviewRef.current.getWebContentsId?.();
if (wcId) {
setWebContentsId(wcId);
}
}
}, [model.webviewRef.current, domReady]);

useEffect(() => {
const webview = model.webviewRef.current;
if (!webview) {
return;
}
const handleDomReady = () => {
setDomReady(true);
};
webview.addEventListener("dom-ready", handleDomReady);
return () => {
webview.removeEventListener("dom-ready", handleDomReady);
};
});

return (
<div className="help-view">
<webview
ref={model.webviewRef}
data-blockid={model.blockId}
data-webcontentsid={webContentsId} // needed for emain
className="docsite-webview"
src={url}
/>
<WebView blockId={model.blockId} model={model} />
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/view/quicktipsview/quicktipsview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class QuickTipsViewModel implements ViewModel {
}
}

function makeHelpViewModel() {
function makeQuickTipsViewModel() {
return new QuickTipsViewModel();
}

Expand All @@ -33,4 +33,4 @@ function QuickTipsView({ model }: { model: QuickTipsViewModel }) {
);
}

export { makeHelpViewModel, QuickTipsView, QuickTipsViewModel };
export { makeQuickTipsViewModel, QuickTipsView, QuickTipsViewModel };
Loading