mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
allow resizing of left sidebar (#244)
* wip * integrate original sidebar content * ResizableSidebar component * trigger toggleCollapse * remove debugging code * minor refactor. disable text select on mousemove * replace icons with fontawesome icons. fix alignment issues * fix session view width when tabs overflow * prevent index and icon from shifting when resizing * snap effect * minor refactor * apply collapsed mode to sidebar contents * change default width to 240px * backend implementation * fix wrong subcmd * save collapsed state * retore sidebar state on reload/launch * use collapse data form db on first load. use previously saved width on expand. * persist width as well collapse state * various fixes and improvements * bind methods * refactor * more refactor * fix minor bug * fix merge issues * various fixes * refactor * fixes * fix issues * fix all issues * resolve undefind tempWidth * fix toggleCollapsed * use Promise in stopResizing method * use tempCollapsed to for real time toggling between logos * minor method name change * refactor * remove debugging code * fix conflict * fix setting collapsed state via CLI * minor refactor * remove debugging code * create setTempWidthAndTempCollapsed method * handle invalid width set via cli * refactor: setbycli not actually needed * remove unused code
This commit is contained in:
parent
40757fa7f4
commit
37ab1bca90
@ -30,6 +30,7 @@ type OV<V> = mobx.IObservableValue<V>;
|
|||||||
@mobxReact.observer
|
@mobxReact.observer
|
||||||
class App extends React.Component<{}, {}> {
|
class App extends React.Component<{}, {}> {
|
||||||
dcWait: OV<boolean> = mobx.observable.box(false, { name: "dcWait" });
|
dcWait: OV<boolean> = mobx.observable.box(false, { name: "dcWait" });
|
||||||
|
mainContentRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -75,6 +76,13 @@ class App extends React.Component<{}, {}> {
|
|||||||
let hasClientStop = GlobalModel.getHasClientStop();
|
let hasClientStop = GlobalModel.getHasClientStop();
|
||||||
let dcWait = this.dcWait.get();
|
let dcWait = this.dcWait.get();
|
||||||
let platform = GlobalModel.getPlatform();
|
let platform = GlobalModel.getPlatform();
|
||||||
|
let clientData = GlobalModel.clientData.get();
|
||||||
|
|
||||||
|
// Previously, this is done in sidebar.tsx but it causes flicker when clientData is null cos screen-view shifts around.
|
||||||
|
// Doing it here fixes the flicker cos app is not rendered until clientData is populated.
|
||||||
|
if (clientData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (disconnected || hasClientStop) {
|
if (disconnected || hasClientStop) {
|
||||||
if (!dcWait) {
|
if (!dcWait) {
|
||||||
@ -82,8 +90,8 @@ class App extends React.Component<{}, {}> {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
|
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
|
||||||
<div className="main-content">
|
<div ref={this.mainContentRef} className="main-content">
|
||||||
<MainSideBar />
|
<MainSideBar parentRef={this.mainContentRef} clientData={clientData} />
|
||||||
<div className="session-view" />
|
<div className="session-view" />
|
||||||
</div>
|
</div>
|
||||||
<If condition={dcWait}>
|
<If condition={dcWait}>
|
||||||
@ -102,8 +110,8 @@ class App extends React.Component<{}, {}> {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
|
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
|
||||||
<div className="main-content">
|
<div ref={this.mainContentRef} className="main-content">
|
||||||
<MainSideBar />
|
<MainSideBar parentRef={this.mainContentRef} clientData={clientData} />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<PluginsView />
|
<PluginsView />
|
||||||
<WorkspaceView />
|
<WorkspaceView />
|
||||||
|
@ -9,11 +9,12 @@ import ReactMarkdown from "react-markdown";
|
|||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import { If } from "tsx-control-statements/components";
|
import { If } from "tsx-control-statements/components";
|
||||||
import { RemoteType, StatusIndicatorLevel } from "../../types/types";
|
import { RemoteType } from "../../types/types";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { GlobalModel } from "../../model/model";
|
import { GlobalModel, GlobalCommandRunner } from "../../model/model";
|
||||||
import * as appconst from "../appconst";
|
import * as appconst from "../appconst";
|
||||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
|
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
|
||||||
|
import { MagicLayout } from "../magiclayout";
|
||||||
|
|
||||||
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
|
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
|
||||||
import { ReactComponent as CopyIcon } from "../assets/icons/history/copy.svg";
|
import { ReactComponent as CopyIcon } from "../assets/icons/history/copy.svg";
|
||||||
@ -1265,6 +1266,169 @@ In order to use Wave's advanced features like unified history and persistent ses
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ResizableSidebarProps {
|
||||||
|
parentRef: React.RefObject<HTMLElement>;
|
||||||
|
position: "left" | "right";
|
||||||
|
enableSnap?: boolean;
|
||||||
|
className?: string;
|
||||||
|
children?: (toggleCollapsed: () => void) => React.ReactNode;
|
||||||
|
toggleCollapse?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mobxReact.observer
|
||||||
|
class ResizableSidebar extends React.Component<ResizableSidebarProps> {
|
||||||
|
resizeStartWidth: number = 0;
|
||||||
|
startX: number = 0;
|
||||||
|
prevDelta: number = 0;
|
||||||
|
prevDragDirection: string = null;
|
||||||
|
disposeReaction: any;
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
startResizing(event: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let { parentRef, position } = this.props;
|
||||||
|
let parentRect = parentRef.current?.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (!parentRect) return;
|
||||||
|
|
||||||
|
if (position === "right") {
|
||||||
|
this.startX = parentRect.right - event.clientX;
|
||||||
|
} else {
|
||||||
|
this.startX = event.clientX - parentRect.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resizeStartWidth = GlobalModel.mainSidebarModel.getWidth();
|
||||||
|
document.addEventListener("mousemove", this.onMouseMove);
|
||||||
|
document.addEventListener("mouseup", this.stopResizing);
|
||||||
|
|
||||||
|
document.body.style.cursor = "col-resize";
|
||||||
|
mobx.action(() => {
|
||||||
|
GlobalModel.mainSidebarModel.isDragging.set(true);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
onMouseMove(event: MouseEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let { parentRef, enableSnap, position } = this.props;
|
||||||
|
let parentRect = parentRef.current?.getBoundingClientRect();
|
||||||
|
let mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||||
|
|
||||||
|
if (!mainSidebarModel.isDragging.get() || !parentRect) return;
|
||||||
|
|
||||||
|
let delta, newWidth;
|
||||||
|
|
||||||
|
if (position === "right") {
|
||||||
|
delta = parentRect.right - event.clientX - this.startX;
|
||||||
|
} else {
|
||||||
|
delta = event.clientX - parentRect.left - this.startX;
|
||||||
|
}
|
||||||
|
|
||||||
|
newWidth = this.resizeStartWidth + delta;
|
||||||
|
|
||||||
|
if (enableSnap) {
|
||||||
|
let minWidth = MagicLayout.MainSidebarMinWidth;
|
||||||
|
let snapPoint = minWidth + MagicLayout.MainSidebarSnapThreshold;
|
||||||
|
let dragResistance = MagicLayout.MainSidebarDragResistance;
|
||||||
|
let dragDirection;
|
||||||
|
|
||||||
|
if (delta - this.prevDelta > 0) {
|
||||||
|
dragDirection = "+";
|
||||||
|
} else if (delta - this.prevDelta == 0) {
|
||||||
|
if (this.prevDragDirection == "+") {
|
||||||
|
dragDirection = "+";
|
||||||
|
} else {
|
||||||
|
dragDirection = "-";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dragDirection = "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prevDelta = delta;
|
||||||
|
this.prevDragDirection = dragDirection;
|
||||||
|
|
||||||
|
if (newWidth - dragResistance > minWidth && newWidth < snapPoint && dragDirection == "+") {
|
||||||
|
newWidth = snapPoint;
|
||||||
|
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
|
||||||
|
} else if (newWidth + dragResistance < snapPoint && dragDirection == "-") {
|
||||||
|
newWidth = minWidth;
|
||||||
|
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true);
|
||||||
|
} else if (newWidth > snapPoint) {
|
||||||
|
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (newWidth <= MagicLayout.MainSidebarMinWidth) {
|
||||||
|
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true);
|
||||||
|
} else {
|
||||||
|
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
stopResizing() {
|
||||||
|
let mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||||
|
|
||||||
|
GlobalCommandRunner.clientSetSidebar(
|
||||||
|
mainSidebarModel.tempWidth.get(),
|
||||||
|
mainSidebarModel.tempCollapsed.get()
|
||||||
|
).finally(() => {
|
||||||
|
mobx.action(() => {
|
||||||
|
mainSidebarModel.isDragging.set(false);
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.removeEventListener("mousemove", this.onMouseMove);
|
||||||
|
document.removeEventListener("mouseup", this.stopResizing);
|
||||||
|
document.body.style.cursor = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
toggleCollapsed() {
|
||||||
|
let mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||||
|
|
||||||
|
let tempCollapsed = mainSidebarModel.getCollapsed();
|
||||||
|
let width = MagicLayout.MainSidebarDefaultWidth;
|
||||||
|
let newWidth;
|
||||||
|
if (tempCollapsed) {
|
||||||
|
newWidth = width;
|
||||||
|
} else {
|
||||||
|
newWidth = MagicLayout.MainSidebarMinWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, !tempCollapsed);
|
||||||
|
GlobalCommandRunner.clientSetSidebar(newWidth, !tempCollapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { className, children } = this.props;
|
||||||
|
let mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||||
|
let width = mainSidebarModel.getWidth();
|
||||||
|
let isCollapsed = mainSidebarModel.getCollapsed();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("sidebar", className, { collapsed: isCollapsed })} style={{ width }}>
|
||||||
|
<div className="sidebar-content">{children(this.toggleCollapsed)}</div>
|
||||||
|
<div
|
||||||
|
className="sidebar-handle"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
[this.props.position === "left" ? "right" : "left"]: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: "5px",
|
||||||
|
cursor: "col-resize",
|
||||||
|
}}
|
||||||
|
onMouseDown={this.startResizing}
|
||||||
|
onDoubleClick={this.toggleCollapsed}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CmdStrCode,
|
CmdStrCode,
|
||||||
Toggle,
|
Toggle,
|
||||||
@ -1286,5 +1450,6 @@ export {
|
|||||||
LinkButton,
|
LinkButton,
|
||||||
Status,
|
Status,
|
||||||
Modal,
|
Modal,
|
||||||
|
ResizableSidebar,
|
||||||
ShowWaveShellInstallPrompt,
|
ShowWaveShellInstallPrompt,
|
||||||
};
|
};
|
||||||
|
@ -27,6 +27,12 @@ let MagicLayout = {
|
|||||||
ScreenSidebarWidthPadding: 5,
|
ScreenSidebarWidthPadding: 5,
|
||||||
ScreenSidebarMinWidth: 200,
|
ScreenSidebarMinWidth: 200,
|
||||||
ScreenSidebarHeaderHeight: 28,
|
ScreenSidebarHeaderHeight: 28,
|
||||||
|
|
||||||
|
MainSidebarMinWidth: 75,
|
||||||
|
MainSidebarMaxWidth: 300,
|
||||||
|
MainSidebarSnapThreshold: 90,
|
||||||
|
MainSidebarDragResistance: 50,
|
||||||
|
MainSidebarDefaultWidth: 240,
|
||||||
};
|
};
|
||||||
|
|
||||||
let m = MagicLayout;
|
let m = MagicLayout;
|
||||||
|
@ -3,14 +3,13 @@
|
|||||||
|
|
||||||
.main-sidebar {
|
.main-sidebar {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
min-width: 20rem;
|
|
||||||
max-width: 20rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 12.5px;
|
font-size: 12.5px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
.title-bar-drag {
|
.title-bar-drag {
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
@ -24,7 +23,6 @@
|
|||||||
&.collapsed {
|
&.collapsed {
|
||||||
width: 6em;
|
width: 6em;
|
||||||
min-width: 6em;
|
min-width: 6em;
|
||||||
|
|
||||||
.arrow-container,
|
.arrow-container,
|
||||||
.collapse-button {
|
.collapse-button {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
@ -34,7 +32,7 @@
|
|||||||
margin-top: 26px;
|
margin-top: 26px;
|
||||||
|
|
||||||
.top,
|
.top,
|
||||||
.workspaces-item,
|
.workspaces,
|
||||||
.middle,
|
.middle,
|
||||||
.bottom,
|
.bottom,
|
||||||
.separator {
|
.separator {
|
||||||
@ -50,7 +48,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.logo-container img {
|
.logo-container {
|
||||||
width: 45px;
|
width: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +84,14 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.spacer {
|
.spacer {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
@ -150,7 +152,6 @@
|
|||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity 0.1s ease-in-out, visibility 0.1s step-end;
|
|
||||||
width: inherit;
|
width: inherit;
|
||||||
max-width: inherit;
|
max-width: inherit;
|
||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
@ -177,6 +178,7 @@
|
|||||||
float: right;
|
float: right;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
letter-spacing: 6px;
|
letter-spacing: 6px;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
:not(.disabled) .hotkey {
|
:not(.disabled) .hotkey {
|
||||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
|||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import type { RemoteType } from "../../types/types";
|
import type { ClientDataType, RemoteType } from "../../types/types";
|
||||||
import { If } from "tsx-control-statements/components";
|
import { If } from "tsx-control-statements/components";
|
||||||
import { compareLoose } from "semver";
|
import { compareLoose } from "semver";
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ import { ReactComponent as SettingsIcon } from "../assets/icons/settings.svg";
|
|||||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||||
import { GlobalModel, GlobalCommandRunner, Session, VERSION } from "../../model/model";
|
import { GlobalModel, GlobalCommandRunner, Session, VERSION } from "../../model/model";
|
||||||
import { isBlank, openLink } from "../../util/util";
|
import { isBlank, openLink } from "../../util/util";
|
||||||
|
import { ResizableSidebar } from "../common/common";
|
||||||
import * as constants from "../appconst";
|
import * as constants from "../appconst";
|
||||||
|
|
||||||
import "./sidebar.less";
|
import "./sidebar.less";
|
||||||
@ -26,8 +27,6 @@ import { ActionsIcon, CenteredIcon, FrontIcon, StatusIndicator } from "../common
|
|||||||
|
|
||||||
dayjs.extend(localizedFormat);
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
type OV<V> = mobx.IObservableValue<V>;
|
|
||||||
|
|
||||||
class SideBarItem extends React.Component<{
|
class SideBarItem extends React.Component<{
|
||||||
frontIcon: React.ReactNode;
|
frontIcon: React.ReactNode;
|
||||||
contents: React.ReactNode | string;
|
contents: React.ReactNode | string;
|
||||||
@ -59,16 +58,14 @@ class HotKeyIcon extends React.Component<{ hotkey: string }> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mobxReact.observer
|
interface MainSideBarProps {
|
||||||
class MainSideBar extends React.Component<{}, {}> {
|
parentRef: React.RefObject<HTMLElement>;
|
||||||
collapsed: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
clientData: ClientDataType;
|
||||||
|
}
|
||||||
|
|
||||||
@boundMethod
|
@mobxReact.observer
|
||||||
toggleCollapsed() {
|
class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||||
mobx.action(() => {
|
sidebarRef = React.createRef<HTMLDivElement>();
|
||||||
this.collapsed.set(!this.collapsed.get());
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSessionClick(sessionId: string) {
|
handleSessionClick(sessionId: string) {
|
||||||
GlobalCommandRunner.switchSession(sessionId);
|
GlobalCommandRunner.switchSession(sessionId);
|
||||||
@ -208,106 +205,116 @@ class MainSideBar extends React.Component<{}, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const isCollapsed = this.collapsed.get();
|
let clientData = this.props.clientData;
|
||||||
const clientData = GlobalModel.clientData.get();
|
|
||||||
let needsUpdate = false;
|
let needsUpdate = false;
|
||||||
if (!clientData?.clientopts.noreleasecheck && !isBlank(clientData?.releaseinfo?.latestversion)) {
|
if (!clientData?.clientopts.noreleasecheck && !isBlank(clientData?.releaseinfo?.latestversion)) {
|
||||||
needsUpdate = compareLoose(VERSION, clientData.releaseinfo.latestversion) < 0;
|
needsUpdate = compareLoose(VERSION, clientData.releaseinfo.latestversion) < 0;
|
||||||
}
|
}
|
||||||
|
let mainSidebar = GlobalModel.mainSidebarModel;
|
||||||
|
let isCollapsed = mainSidebar.getCollapsed();
|
||||||
return (
|
return (
|
||||||
<div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}>
|
<ResizableSidebar
|
||||||
<div className="title-bar-drag" />
|
className="main-sidebar"
|
||||||
<div className="contents">
|
position="left"
|
||||||
<div className="logo">
|
enableSnap={true}
|
||||||
<If condition={isCollapsed}>
|
parentRef={this.props.parentRef}
|
||||||
<div className="logo-container" onClick={this.toggleCollapsed}>
|
>
|
||||||
<img src="public/logos/wave-logo.png" />
|
{(toggleCollapse) => (
|
||||||
|
<React.Fragment>
|
||||||
|
<div className="title-bar-drag" />
|
||||||
|
<div className="contents">
|
||||||
|
<div className="logo">
|
||||||
|
<If condition={isCollapsed}>
|
||||||
|
<div className="logo-container" onClick={toggleCollapse}>
|
||||||
|
<img src="public/logos/wave-logo.png" />
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<If condition={!isCollapsed}>
|
||||||
|
<div className="logo-container">
|
||||||
|
<img src="public/logos/wave-dark.png" />
|
||||||
|
</div>
|
||||||
|
<div className="spacer" />
|
||||||
|
<div className="collapse-button" onClick={toggleCollapse}>
|
||||||
|
<LeftChevronIcon className="icon" />
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
</div>
|
</div>
|
||||||
</If>
|
<div className="separator" />
|
||||||
<If condition={!isCollapsed}>
|
<div className="top">
|
||||||
<div className="logo-container">
|
<SideBarItem
|
||||||
<img src="public/logos/wave-dark.png" />
|
key="history"
|
||||||
|
frontIcon={<i className="fa-sharp fa-regular fa-clock-rotate-left icon" />}
|
||||||
|
contents="History"
|
||||||
|
endIcons={[<HotKeyIcon key="hotkey" hotkey="H" />]}
|
||||||
|
onClick={this.handleHistoryClick}
|
||||||
|
/>
|
||||||
|
{/* <SideBarItem className="hoverEffect unselectable" frontIcon={<FavoritesIcon className="icon" />} contents="Favorites" endIcon={<span className="hotkey">⌘B</span>} onClick={this.handleBookmarksClick}/> */}
|
||||||
|
<SideBarItem
|
||||||
|
key="connections"
|
||||||
|
frontIcon={<i className="fa-sharp fa-regular fa-globe icon " />}
|
||||||
|
contents="Connections"
|
||||||
|
onClick={this.handleConnectionsClick}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="spacer" />
|
<div className="separator" />
|
||||||
<div className="collapse-button" onClick={this.toggleCollapsed}>
|
|
||||||
<LeftChevronIcon className="icon" />
|
|
||||||
</div>
|
|
||||||
</If>
|
|
||||||
</div>
|
|
||||||
<div className="separator" />
|
|
||||||
<div className="top">
|
|
||||||
<SideBarItem
|
|
||||||
key="history"
|
|
||||||
frontIcon={<i className="fa-sharp fa-regular fa-clock-rotate-left icon" />}
|
|
||||||
contents="History"
|
|
||||||
endIcons={[<HotKeyIcon key="hotkey" hotkey="H" />]}
|
|
||||||
onClick={this.handleHistoryClick}
|
|
||||||
/>
|
|
||||||
{/* <SideBarItem className="hoverEffect unselectable" frontIcon={<FavoritesIcon className="icon" />} contents="Favorites" endIcon={<span className="hotkey">⌘B</span>} onClick={this.handleBookmarksClick}/> */}
|
|
||||||
<SideBarItem
|
|
||||||
key="connections"
|
|
||||||
frontIcon={<i className="fa-sharp fa-regular fa-globe icon " />}
|
|
||||||
contents="Connections"
|
|
||||||
onClick={this.handleConnectionsClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="separator" />
|
|
||||||
<SideBarItem
|
|
||||||
key="workspaces"
|
|
||||||
className="workspaces"
|
|
||||||
frontIcon={<WorkspacesIcon className="icon" />}
|
|
||||||
contents="Workspaces"
|
|
||||||
endIcons={[
|
|
||||||
<CenteredIcon
|
|
||||||
key="add-workspace"
|
|
||||||
className="add-workspace hoverEffect"
|
|
||||||
onClick={this.handleNewSession}
|
|
||||||
>
|
|
||||||
<i className="fa-sharp fa-solid fa-plus"></i>
|
|
||||||
</CenteredIcon>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<div className="middle hideScrollbarUntillHover">{this.getSessions()}</div>
|
|
||||||
<div className="bottom">
|
|
||||||
<If condition={needsUpdate}>
|
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
key="update-available"
|
key="workspaces"
|
||||||
className="updateBanner"
|
className="workspaces"
|
||||||
frontIcon={<i className="fa-sharp fa-regular fa-circle-up icon" />}
|
frontIcon={<WorkspacesIcon className="icon" />}
|
||||||
contents="Update Available"
|
contents="Workspaces"
|
||||||
onClick={() => openLink("https://www.waveterm.dev/download?ref=upgrade")}
|
endIcons={[
|
||||||
|
<CenteredIcon
|
||||||
|
key="add-workspace"
|
||||||
|
className="add-workspace hoverEffect"
|
||||||
|
onClick={this.handleNewSession}
|
||||||
|
>
|
||||||
|
<i className="fa-sharp fa-solid fa-plus"></i>
|
||||||
|
</CenteredIcon>,
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</If>
|
<div className="middle hideScrollbarUntillHover">{this.getSessions()}</div>
|
||||||
<If condition={GlobalModel.isDev}>
|
<div className="bottom">
|
||||||
<SideBarItem
|
<If condition={needsUpdate}>
|
||||||
key="apps"
|
<SideBarItem
|
||||||
frontIcon={<AppsIcon className="icon" />}
|
key="update-available"
|
||||||
contents="Apps"
|
className="updateBanner"
|
||||||
onClick={this.handlePluginsClick}
|
frontIcon={<i className="fa-sharp fa-regular fa-circle-up icon" />}
|
||||||
endIcons={[<HotKeyIcon key="hotkey" hotkey="A" />]}
|
contents="Update Available"
|
||||||
/>
|
onClick={() => openLink("https://www.waveterm.dev/download?ref=upgrade")}
|
||||||
</If>
|
/>
|
||||||
<SideBarItem
|
</If>
|
||||||
key="settings"
|
<If condition={GlobalModel.isDev}>
|
||||||
frontIcon={<SettingsIcon className="icon" />}
|
<SideBarItem
|
||||||
contents="Settings"
|
key="apps"
|
||||||
onClick={this.handleSettingsClick}
|
frontIcon={<AppsIcon className="icon" />}
|
||||||
/>
|
contents="Apps"
|
||||||
<SideBarItem
|
onClick={this.handlePluginsClick}
|
||||||
key="documentation"
|
endIcons={[<HotKeyIcon key="hotkey" hotkey="A" />]}
|
||||||
frontIcon={<i className="fa-sharp fa-regular fa-circle-question icon" />}
|
/>
|
||||||
contents="Documentation"
|
</If>
|
||||||
onClick={() => openLink("https://docs.waveterm.dev")}
|
<SideBarItem
|
||||||
/>
|
key="settings"
|
||||||
<SideBarItem
|
frontIcon={<SettingsIcon className="icon" />}
|
||||||
key="discord"
|
contents="Settings"
|
||||||
frontIcon={<i className="fa-brands fa-discord icon" />}
|
onClick={this.handleSettingsClick}
|
||||||
contents="Discord"
|
/>
|
||||||
onClick={() => openLink("https://discord.gg/XfvZ334gwU")}
|
<SideBarItem
|
||||||
/>
|
key="documentation"
|
||||||
</div>
|
frontIcon={<i className="fa-sharp fa-regular fa-circle-question icon" />}
|
||||||
</div>
|
contents="Documentation"
|
||||||
</div>
|
onClick={() => openLink("https://docs.waveterm.dev")}
|
||||||
|
/>
|
||||||
|
<SideBarItem
|
||||||
|
key="discord"
|
||||||
|
frontIcon={<i className="fa-brands fa-discord icon" />}
|
||||||
|
contents="Discord"
|
||||||
|
onClick={() => openLink("https://discord.gg/XfvZ334gwU")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</ResizableSidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
&.is-hidden {
|
&.is-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
max-width: calc(100% - 20.5em);
|
|
||||||
background: @background-session;
|
background: @background-session;
|
||||||
border: 1px solid @base-border;
|
border: 1px solid @base-border;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: width 0.2s ease;
|
// transition: width 0.2s ease;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
|
||||||
.center-message {
|
.center-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -24,6 +24,3 @@
|
|||||||
color: @text-secondary;
|
color: @text-secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.collapsed + .session-view {
|
|
||||||
max-width: calc(100% - 6.7em);
|
|
||||||
}
|
|
||||||
|
@ -27,20 +27,33 @@ class WorkspaceView extends React.Component<{}, {}> {
|
|||||||
if (session == null) {
|
if (session == null) {
|
||||||
return (
|
return (
|
||||||
<div className="session-view">
|
<div className="session-view">
|
||||||
<div className="center-message"><div>(no active workspace)</div></div>
|
<div className="center-message">
|
||||||
|
<div>(no active workspace)</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let activeScreen = session.getActiveScreen();
|
let activeScreen = session.getActiveScreen();
|
||||||
let cmdInputHeight = model.inputModel.cmdInputHeight.get();
|
let cmdInputHeight = model.inputModel.cmdInputHeight.get();
|
||||||
if (cmdInputHeight == 0) {
|
if (cmdInputHeight == 0) {
|
||||||
cmdInputHeight = MagicLayout.CmdInputHeight; // this is the base size of cmdInput (measured using devtools)
|
cmdInputHeight = MagicLayout.CmdInputHeight; // this is the base size of cmdInput (measured using devtools)
|
||||||
}
|
}
|
||||||
cmdInputHeight += MagicLayout.CmdInputBottom; // reference to .cmd-input, bottom: 12px
|
cmdInputHeight += MagicLayout.CmdInputBottom; // reference to .cmd-input, bottom: 12px
|
||||||
let isHidden = GlobalModel.activeMainView.get() != "session";
|
let isHidden = GlobalModel.activeMainView.get() != "session";
|
||||||
|
let mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||||
|
|
||||||
|
// Has to calc manually because when tabs overflow, the width of the session view is increased for some reason causing inconsistent width.
|
||||||
|
// 6px is the right margin of session view.
|
||||||
|
let width = window.innerWidth - 6 - mainSidebarModel.getWidth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("session-view", { "is-hidden": isHidden })} data-sessionid={session.sessionId}>
|
<div
|
||||||
|
className={cn("session-view", { "is-hidden": isHidden })}
|
||||||
|
data-sessionid={session.sessionId}
|
||||||
|
style={{
|
||||||
|
width: `${width}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ScreenTabs key={"tabs-" + session.sessionId} session={session} />
|
<ScreenTabs key={"tabs-" + session.sessionId} session={session} />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ScreenView key={"screenview-" + session.sessionId} session={session} screen={activeScreen} />
|
<ScreenView key={"screenview-" + session.sessionId} session={session} screen={activeScreen} />
|
||||||
|
@ -7,6 +7,7 @@ import { sprintf } from "sprintf-js";
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
import { debounce } from "throttle-debounce";
|
import { debounce } from "throttle-debounce";
|
||||||
|
import * as mobxReact from "mobx-react";
|
||||||
import {
|
import {
|
||||||
handleJsonFetchResponse,
|
handleJsonFetchResponse,
|
||||||
base64ToString,
|
base64ToString,
|
||||||
@ -2617,6 +2618,74 @@ class ClientSettingsViewModel {
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class MainSidebarModel {
|
||||||
|
tempWidth: OV<number> = mobx.observable.box(null, {
|
||||||
|
name: "MainSidebarModel-tempWidth",
|
||||||
|
});
|
||||||
|
tempCollapsed: OV<boolean> = mobx.observable.box(null, {
|
||||||
|
name: "MainSidebarModel-tempCollapsed",
|
||||||
|
});
|
||||||
|
isDragging: OV<boolean> = mobx.observable.box(false, {
|
||||||
|
name: "MainSidebarModel-isDragging",
|
||||||
|
});
|
||||||
|
|
||||||
|
setTempWidthAndTempCollapsed(newWidth: number, newCollapsed: boolean): void {
|
||||||
|
let width = Math.max(MagicLayout.MainSidebarMinWidth, Math.min(newWidth, MagicLayout.MainSidebarMaxWidth));
|
||||||
|
|
||||||
|
mobx.action(() => {
|
||||||
|
this.tempWidth.set(width);
|
||||||
|
this.tempCollapsed.set(newCollapsed);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidth(): number {
|
||||||
|
let clientData = GlobalModel.clientData.get();
|
||||||
|
let width = clientData.clientopts.mainsidebar.width;
|
||||||
|
if (this.isDragging.get()) {
|
||||||
|
if (this.tempWidth.get() == null && width == null) {
|
||||||
|
return MagicLayout.MainSidebarDefaultWidth;
|
||||||
|
}
|
||||||
|
if (this.tempWidth.get() == null) {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
return this.tempWidth.get();
|
||||||
|
}
|
||||||
|
// Set by CLI and collapsed
|
||||||
|
if (this.getCollapsed() && width != MagicLayout.MainSidebarMinWidth) {
|
||||||
|
this.setTempWidthAndTempCollapsed(MagicLayout.MainSidebarMinWidth, true);
|
||||||
|
return MagicLayout.MainSidebarMinWidth;
|
||||||
|
}
|
||||||
|
// Set by CLI and not collapsed
|
||||||
|
if (!this.getCollapsed()) {
|
||||||
|
if (width <= MagicLayout.MainSidebarMinWidth) {
|
||||||
|
width = MagicLayout.MainSidebarDefaultWidth;
|
||||||
|
}
|
||||||
|
let snapPoint = MagicLayout.MainSidebarMinWidth + MagicLayout.MainSidebarSnapThreshold;
|
||||||
|
if (width < snapPoint || width > MagicLayout.MainSidebarMaxWidth) {
|
||||||
|
width = MagicLayout.MainSidebarDefaultWidth;
|
||||||
|
}
|
||||||
|
this.setTempWidthAndTempCollapsed(width, false);
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
this.setTempWidthAndTempCollapsed(width, this.getCollapsed());
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCollapsed(): boolean {
|
||||||
|
let clientData = GlobalModel.clientData.get();
|
||||||
|
let collapsed = clientData.clientopts.mainsidebar.collapsed;
|
||||||
|
if (this.isDragging.get()) {
|
||||||
|
if (this.tempCollapsed.get() == null && collapsed == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.tempCollapsed.get() == null) {
|
||||||
|
return collapsed;
|
||||||
|
}
|
||||||
|
return this.tempCollapsed.get();
|
||||||
|
}
|
||||||
|
return collapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class BookmarksModel {
|
class BookmarksModel {
|
||||||
bookmarks: OArr<BookmarkType> = mobx.observable.array([], {
|
bookmarks: OArr<BookmarkType> = mobx.observable.array([], {
|
||||||
@ -3387,6 +3456,7 @@ class Model {
|
|||||||
connectionViewModel: ConnectionsViewModel;
|
connectionViewModel: ConnectionsViewModel;
|
||||||
clientSettingsViewModel: ClientSettingsViewModel;
|
clientSettingsViewModel: ClientSettingsViewModel;
|
||||||
modalsModel: ModalsModel;
|
modalsModel: ModalsModel;
|
||||||
|
mainSidebarModel: MainSidebarModel;
|
||||||
clientData: OV<ClientDataType> = mobx.observable.box(null, {
|
clientData: OV<ClientDataType> = mobx.observable.box(null, {
|
||||||
name: "clientData",
|
name: "clientData",
|
||||||
});
|
});
|
||||||
@ -3413,6 +3483,7 @@ class Model {
|
|||||||
this.remotesModalModel = new RemotesModalModel();
|
this.remotesModalModel = new RemotesModalModel();
|
||||||
this.remotesModel = new RemotesModel();
|
this.remotesModel = new RemotesModel();
|
||||||
this.modalsModel = new ModalsModel();
|
this.modalsModel = new ModalsModel();
|
||||||
|
this.mainSidebarModel = new MainSidebarModel();
|
||||||
let isWaveSrvRunning = getApi().getWaveSrvStatus();
|
let isWaveSrvRunning = getApi().getWaveSrvStatus();
|
||||||
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
|
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
|
||||||
name: "model-wavesrv-running",
|
name: "model-wavesrv-running",
|
||||||
@ -4980,6 +5051,11 @@ class CommandRunner {
|
|||||||
return GlobalModel.submitCommand("client", "setconfirmflag", [flag, valueStr], kwargs, false);
|
return GlobalModel.submitCommand("client", "setconfirmflag", [flag, valueStr], kwargs, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientSetSidebar(width: number, collapsed: boolean): Promise<CommandRtnType> {
|
||||||
|
let kwargs = { nohist: "1", width: `${width}`, collapsed: collapsed ? "1" : "0" };
|
||||||
|
return GlobalModel.submitCommand("client", "setsidebar", null, kwargs, false);
|
||||||
|
}
|
||||||
|
|
||||||
editBookmark(bookmarkId: string, desc: string, cmdstr: string) {
|
editBookmark(bookmarkId: string, desc: string, cmdstr: string) {
|
||||||
let kwargs = {
|
let kwargs = {
|
||||||
nohist: "1",
|
nohist: "1",
|
||||||
|
@ -524,6 +524,10 @@ type ClientOptsType = {
|
|||||||
noreleasecheck: boolean;
|
noreleasecheck: boolean;
|
||||||
acceptedtos: number;
|
acceptedtos: number;
|
||||||
confirmflags: ConfirmFlagsType;
|
confirmflags: ConfirmFlagsType;
|
||||||
|
mainsidebar: {
|
||||||
|
collapsed: boolean;
|
||||||
|
width: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type ReleaseInfoType = {
|
type ReleaseInfoType = {
|
||||||
|
@ -92,6 +92,7 @@ var TabIcons = []string{"square", "sparkle", "fire", "ghost", "cloud", "compass"
|
|||||||
var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
|
var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
|
||||||
var RemoteSetArgs = []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}
|
var RemoteSetArgs = []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}
|
||||||
var ConfirmFlags = []string{"hideshellprompt"}
|
var ConfirmFlags = []string{"hideshellprompt"}
|
||||||
|
var SidebarNames = []string{"main"}
|
||||||
|
|
||||||
var ScreenCmds = []string{"run", "comment", "cd", "cr", "clear", "sw", "reset", "signal", "chat"}
|
var ScreenCmds = []string{"run", "comment", "cd", "cr", "clear", "sw", "reset", "signal", "chat"}
|
||||||
var NoHistCmds = []string{"_compgen", "line", "history", "_killserver"}
|
var NoHistCmds = []string{"_compgen", "line", "history", "_killserver"}
|
||||||
@ -218,6 +219,7 @@ func init() {
|
|||||||
registerCmdFn("client:notifyupdatewriter", ClientNotifyUpdateWriterCommand)
|
registerCmdFn("client:notifyupdatewriter", ClientNotifyUpdateWriterCommand)
|
||||||
registerCmdFn("client:accepttos", ClientAcceptTosCommand)
|
registerCmdFn("client:accepttos", ClientAcceptTosCommand)
|
||||||
registerCmdFn("client:setconfirmflag", ClientConfirmFlagCommand)
|
registerCmdFn("client:setconfirmflag", ClientConfirmFlagCommand)
|
||||||
|
registerCmdFn("client:setsidebar", ClientSetSidebarCommand)
|
||||||
|
|
||||||
registerCmdFn("sidebar:open", SidebarOpenCommand)
|
registerCmdFn("sidebar:open", SidebarOpenCommand)
|
||||||
registerCmdFn("sidebar:close", SidebarCloseCommand)
|
registerCmdFn("sidebar:close", SidebarCloseCommand)
|
||||||
@ -4463,6 +4465,62 @@ func ClientConfirmFlagCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
|
|||||||
return update, nil
|
return update, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ClientSetSidebarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
|
clientData, err := sstore.EnsureClientData(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot retrieve client data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle collapsed
|
||||||
|
collapsed, ok := pk.Kwargs["collapsed"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("collapsed key not provided")
|
||||||
|
}
|
||||||
|
collapsedValue := resolveBool(collapsed, false)
|
||||||
|
|
||||||
|
// Handle width
|
||||||
|
var width int
|
||||||
|
if w, exists := pk.Kwargs["width"]; exists {
|
||||||
|
width, err = resolveNonNegInt(w, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error resolving width: %v", err)
|
||||||
|
}
|
||||||
|
} else if clientData.ClientOpts.MainSidebar != nil {
|
||||||
|
width = clientData.ClientOpts.MainSidebar.Width
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize SidebarCollapsed if it's nil
|
||||||
|
if clientData.ClientOpts.MainSidebar == nil {
|
||||||
|
clientData.ClientOpts.MainSidebar = new(sstore.SidebarValueType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the sidebar values
|
||||||
|
var sv sstore.SidebarValueType
|
||||||
|
sv.Collapsed = collapsedValue
|
||||||
|
if width != 0 {
|
||||||
|
sv.Width = width
|
||||||
|
}
|
||||||
|
clientData.ClientOpts.MainSidebar = &sv
|
||||||
|
|
||||||
|
// Update client data
|
||||||
|
err = sstore.SetClientOpts(ctx, clientData.ClientOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error updating client data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve updated client data
|
||||||
|
clientData, err = sstore.EnsureClientData(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
update := &sstore.ModelUpdate{
|
||||||
|
ClientData: clientData,
|
||||||
|
}
|
||||||
|
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
func validateOpenAIAPIToken(key string) error {
|
func validateOpenAIAPIToken(key string) error {
|
||||||
if len(key) > MaxOpenAIAPITokenLen {
|
if len(key) > MaxOpenAIAPITokenLen {
|
||||||
return fmt.Errorf("invalid openai token, too long")
|
return fmt.Errorf("invalid openai token, too long")
|
||||||
|
@ -275,11 +275,17 @@ func (tdata *TelemetryData) Scan(val interface{}) error {
|
|||||||
return quickScanJson(tdata, val)
|
return quickScanJson(tdata, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SidebarValueType struct {
|
||||||
|
Collapsed bool `json:"collapsed"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
}
|
||||||
|
|
||||||
type ClientOptsType struct {
|
type ClientOptsType struct {
|
||||||
NoTelemetry bool `json:"notelemetry,omitempty"`
|
NoTelemetry bool `json:"notelemetry,omitempty"`
|
||||||
NoReleaseCheck bool `json:"noreleasecheck,omitempty"`
|
NoReleaseCheck bool `json:"noreleasecheck,omitempty"`
|
||||||
AcceptedTos int64 `json:"acceptedtos,omitempty"`
|
AcceptedTos int64 `json:"acceptedtos,omitempty"`
|
||||||
ConfirmFlags map[string]bool `json:"confirmflags,omitempty"`
|
ConfirmFlags map[string]bool `json:"confirmflags,omitempty"`
|
||||||
|
MainSidebar *SidebarValueType `json:"mainsidebar,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeOptsType struct {
|
type FeOptsType struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user