mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout * fix more golang warnings * sidebar open/close * add ability to set width of split * sidebar add and remove, set width, etc. * almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there * add isSidebarOpen() method * fix resize jump -- must set width in error handler as well (before window is loaded) * sidebar UI touchups and help * more sidebar progress, render more like regular lines, just in the right column * merge * move migration to 26 * simplify sidebar types * checkpoint * proxy things through parent screen object for sidebar * checkpoint, add/remove from sidebar * work on add/remove icons for sidebar * fix height calculation, remove close button * bring back close button when no line is selected * add sidebar flag to run command to run new command output in sidebar * implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.) * prettier * minor fixes * working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize) * fix sidebar terminal command resizing * add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
This commit is contained in:
@@ -7,3 +7,7 @@ export const SCREEN_SETTINGS = "screenSettings";
|
||||
export const SESSION_SETTINGS = "sessionSettings";
|
||||
export const LINE_SETTINGS = "lineSettings";
|
||||
export const CLIENT_SETTINGS = "clientSettings";
|
||||
|
||||
export const LineContainer_Main = "main";
|
||||
export const LineContainer_History = "history";
|
||||
export const LineContainer_Sidebar = "sidebar";
|
||||
|
||||
+68
-29
@@ -39,7 +39,7 @@ import { PluginModel } from "../../plugins/plugins";
|
||||
import { Prompt } from "../common/prompt/prompt";
|
||||
import * as lineutil from "./lineutil";
|
||||
import { ErrorBoundary } from "../../app/common/error/errorboundary";
|
||||
import * as constants from "../appconst";
|
||||
import * as appconst from "../appconst";
|
||||
|
||||
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
|
||||
import { ReactComponent as CommentIcon } from "../assets/icons/line/comment.svg";
|
||||
@@ -130,7 +130,7 @@ class LineCmd extends React.Component<
|
||||
isOverflow: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "line-overflow",
|
||||
});
|
||||
isMinimised: OV<boolean> = mobx.observable.box(false, {
|
||||
isMinimized: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "line-minimised",
|
||||
});
|
||||
isCmdExpanded: OV<boolean> = mobx.observable.box(false, {
|
||||
@@ -355,12 +355,23 @@ class LineCmd extends React.Component<
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickMinimise() {
|
||||
clickMinimize() {
|
||||
mobx.action(() => {
|
||||
this.isMinimised.set(!this.isMinimised.get());
|
||||
this.isMinimized.set(!this.isMinimized.get());
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickMoveToSidebar() {
|
||||
let { line } = this.props;
|
||||
GlobalCommandRunner.screenSidebarAddLine(line.lineid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickRemoveFromSidebar() {
|
||||
GlobalCommandRunner.screenSidebarRemove();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleResizeButton() {
|
||||
console.log("resize button");
|
||||
@@ -443,7 +454,7 @@ class LineCmd extends React.Component<
|
||||
mobx.action(() => {
|
||||
GlobalModel.lineSettingsModal.set(line.linenum);
|
||||
})();
|
||||
GlobalModel.modalsModel.pushModal(constants.LINE_SETTINGS);
|
||||
GlobalModel.modalsModel.pushModal(appconst.LINE_SETTINGS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +609,14 @@ class LineCmd extends React.Component<
|
||||
{ name: "computed-shouldCmdFocus" }
|
||||
)
|
||||
.get();
|
||||
let isInSidebar = mobx
|
||||
.computed(
|
||||
() => {
|
||||
return screen.isSidebarOpen() && screen.isLineIdInSidebar(line.lineid);
|
||||
},
|
||||
{ name: "computed-isInSidebar" }
|
||||
)
|
||||
.get();
|
||||
let isStatic = staticRender;
|
||||
let isRunning = cmd.isRunning();
|
||||
let isExpanded = this.isCmdExpanded.get();
|
||||
@@ -622,6 +641,7 @@ class LineCmd extends React.Component<
|
||||
if (rtnStateDiffSize < 10) {
|
||||
rtnStateDiffSize = Math.max(termFontSize, 10);
|
||||
}
|
||||
let containerType = screen.getContainerType();
|
||||
return (
|
||||
<div
|
||||
className={mainDivCn}
|
||||
@@ -639,38 +659,57 @@ class LineCmd extends React.Component<
|
||||
{this.renderMeta1(cmd)}
|
||||
<If condition={!hidePrompt}>{this.renderCmdText(cmd)}</If>
|
||||
</div>
|
||||
<div
|
||||
key="pin"
|
||||
title="Pin"
|
||||
className={cn("line-icon", { active: line.pinned })}
|
||||
onClick={this.clickPin}
|
||||
style={{ display: "none" }}
|
||||
>
|
||||
<PinIcon className="icon" />
|
||||
</div>
|
||||
<div
|
||||
key="bookmark"
|
||||
title="Bookmark"
|
||||
className={cn("line-icon", "line-bookmark", "hoverEffect")}
|
||||
onClick={this.clickBookmark}
|
||||
>
|
||||
<FavoritesIcon className="icon" />
|
||||
</div>
|
||||
<div
|
||||
key="minimise"
|
||||
title={`${this.isMinimised.get() ? "Maximise" : "Minimise"}`}
|
||||
className={cn(
|
||||
"line-icon",
|
||||
"line-minimise",
|
||||
"hoverEffect",
|
||||
this.isMinimised.get() ? "line-icon-show" : ""
|
||||
)}
|
||||
onClick={this.clickMinimise}
|
||||
>
|
||||
{this.isMinimised.get() ? <PlusIcon className="icon plus" /> : <MinusIcon className="icon" />}
|
||||
<i className="fa-sharp fa-regular fa-bookmark" />
|
||||
</div>
|
||||
<If condition={containerType == appconst.LineContainer_Main}>
|
||||
<div
|
||||
key="minimize"
|
||||
title={`${this.isMinimized.get() ? "Maximise" : "Minimize"}`}
|
||||
className={cn(
|
||||
"line-icon",
|
||||
"line-minimize",
|
||||
"hoverEffect",
|
||||
this.isMinimized.get() ? "line-icon-show" : ""
|
||||
)}
|
||||
onClick={this.clickMinimize}
|
||||
>
|
||||
<If condition={this.isMinimized.get()}>
|
||||
<i className="fa-sharp fa-regular fa-circle-plus" />
|
||||
</If>
|
||||
<If condition={!this.isMinimized.get()}>
|
||||
<i className="fa-sharp fa-regular fa-circle-minus" />
|
||||
</If>
|
||||
</div>
|
||||
<div
|
||||
className="line-icon line-sidebar"
|
||||
onClick={this.clickMoveToSidebar}
|
||||
title="Move to Sidebar"
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-right-to-line" />
|
||||
</div>
|
||||
</If>
|
||||
<If condition={containerType == appconst.LineContainer_Sidebar}>
|
||||
<div
|
||||
className="line-icon line-sidebar"
|
||||
onClick={this.clickRemoveFromSidebar}
|
||||
title="Move to Sidebar"
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-left-to-line" />
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
<If condition={!this.isMinimised.get()}>
|
||||
<If condition={isInSidebar}>
|
||||
<div className="sidebar-message" style={{ fontSize: termFontSize }}>
|
||||
showing in sidebar =>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={!this.isMinimized.get() && !isInSidebar}>
|
||||
<ErrorBoundary plugin={rendererPlugin?.name} lineContext={lineutil.getRendererContext(line)}>
|
||||
<If condition={rendererPlugin == null && !isNoneRenderer}>
|
||||
<TerminalRenderer
|
||||
|
||||
+6
-11
@@ -35,11 +35,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-message {
|
||||
color: @term-yellow;
|
||||
}
|
||||
|
||||
.line-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-bottom: 0.7rem;
|
||||
width: 100%;
|
||||
line-height: 1.2;
|
||||
|
||||
&.is-expanded {
|
||||
height: auto;
|
||||
@@ -54,9 +59,8 @@
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
padding: 3px;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.line-icon-show {
|
||||
@@ -307,15 +311,6 @@
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
&.num-4 {
|
||||
}
|
||||
|
||||
&.num-5 {
|
||||
}
|
||||
|
||||
&.num-6 {
|
||||
}
|
||||
|
||||
&.status-done {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ class LinesView extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
handleResize(entries: any) {
|
||||
handleResize(entries: ResizeObserverEntry[]) {
|
||||
let linesElem = this.linesRef.current;
|
||||
if (linesElem == null) {
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,10 @@ let MagicLayout = {
|
||||
TermWidthBuffer: 15,
|
||||
|
||||
TabWidth: 175,
|
||||
|
||||
ScreenSidebarWidthPadding: 5,
|
||||
ScreenSidebarMinWidth: 200,
|
||||
ScreenSidebarHeaderHeight: 28,
|
||||
};
|
||||
|
||||
let m = MagicLayout;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
position: relative;
|
||||
font-size: 12.5px;
|
||||
line-height: 20px;
|
||||
backdrop-filter: blur(4px);
|
||||
|
||||
.title-bar-drag {
|
||||
-webkit-app-region: drag;
|
||||
|
||||
@@ -6,11 +6,68 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.screen-sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 0.5rem);
|
||||
overflow: hidden;
|
||||
margin-left: 5px;
|
||||
padding-left: 5px;
|
||||
overflow-y: auto;
|
||||
|
||||
.sidebar-header {
|
||||
/* sidebar-header height linked to MagicLayout.ScreenSidebarHeaderHeight */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 3px 5px;
|
||||
border-radius: 3px;
|
||||
margin: 3px 5px 0 5px;
|
||||
|
||||
i {
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.screen-sidebar-close {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.close-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.screen-sidebar-section {
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-sidebar {
|
||||
align-self: center;
|
||||
margin-top: 20%;
|
||||
|
||||
.sidebar-help-text {
|
||||
margin-top: 20px;
|
||||
padding: 5px 10px;
|
||||
background-color: #333;
|
||||
border-radius: 5px;
|
||||
font-family: @fixed-font;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.window-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: calc(100% - 0.5rem);
|
||||
overflow-x: hidden;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import dayjs from "dayjs";
|
||||
import { GlobalCommandRunner, TabColors, TabIcons } from "../../../model/model";
|
||||
import { GlobalCommandRunner, TabColors, TabIcons, ForwardLineContainer } from "../../../model/model";
|
||||
import type { LineType, RenderModeType, LineFactoryProps } from "../../../types/types";
|
||||
import * as T from "../../../types/types";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
@@ -26,9 +26,12 @@ import { ReactComponent as Check12Icon } from "../../assets/icons/check12.svg";
|
||||
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
|
||||
import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg";
|
||||
import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg";
|
||||
import { termWidthFromCols, termHeightFromRows } from "../../../util/textmeasure";
|
||||
import * as appconst from "../../appconst";
|
||||
|
||||
import "./screenview.less";
|
||||
import "./tabs.less";
|
||||
import { MagicLayout } from "../../magiclayout";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
@@ -36,15 +39,266 @@ type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class ScreenView extends React.Component<{ session: Session; screen: Screen }, {}> {
|
||||
rszObs: ResizeObserver;
|
||||
screenViewRef: React.RefObject<any> = React.createRef();
|
||||
width: OV<number> = mobx.observable.box(null, { name: "screenview-width" });
|
||||
handleResize_debounced: () => void;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.handleResize_debounced = debounce(100, this.handleResize.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
let elem = this.screenViewRef.current;
|
||||
if (elem != null) {
|
||||
this.rszObs = new ResizeObserver(this.handleResize_debounced);
|
||||
this.rszObs.observe(elem);
|
||||
this.handleResize();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.rszObs != null) {
|
||||
this.rszObs.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
let elem = this.screenViewRef.current;
|
||||
if (elem == null) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.width.set(elem.offsetWidth);
|
||||
})();
|
||||
}
|
||||
|
||||
render() {
|
||||
let { session, screen } = this.props;
|
||||
if (screen == null) {
|
||||
return <div className="screen-view">(no screen found)</div>;
|
||||
return (
|
||||
<div className="screen-view" ref={this.screenViewRef}>
|
||||
(no screen found)
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let screenWidth = this.width.get();
|
||||
if (screenWidth == null) {
|
||||
return <div className="screen-view" ref={this.screenViewRef}></div>;
|
||||
}
|
||||
let fontSize = GlobalModel.termFontSize.get();
|
||||
let viewOpts = screen.viewOpts.get();
|
||||
let hasSidebar = viewOpts?.sidebar?.open;
|
||||
let winWidth = "100%";
|
||||
let sidebarWidth = "0px";
|
||||
if (hasSidebar) {
|
||||
let targetWidth = viewOpts?.sidebar?.width;
|
||||
let realWidth = 0;
|
||||
if (util.isBlank(targetWidth) || screenWidth < (MagicLayout.ScreenSidebarMinWidth * 2)) {
|
||||
realWidth = Math.floor(screenWidth / 2) - MagicLayout.ScreenSidebarWidthPadding;
|
||||
} else if (targetWidth.indexOf("%") != -1) {
|
||||
let targetPercent = parseInt(targetWidth);
|
||||
if (targetPercent > 100) {
|
||||
targetPercent = 100;
|
||||
}
|
||||
let targetMul = targetPercent / 100;
|
||||
realWidth = Math.floor((screenWidth * targetPercent) / 100);
|
||||
realWidth = util.boundInt(realWidth, MagicLayout.ScreenSidebarMinWidth, screenWidth - MagicLayout.ScreenSidebarMinWidth);
|
||||
} else {
|
||||
// screen is at least 400px wide
|
||||
let targetWidthNum = parseInt(targetWidth);
|
||||
realWidth = util.boundInt(targetWidthNum, MagicLayout.ScreenSidebarMinWidth, screenWidth - MagicLayout.ScreenSidebarMinWidth);
|
||||
}
|
||||
winWidth = screenWidth - realWidth + "px";
|
||||
sidebarWidth = realWidth - MagicLayout.ScreenSidebarWidthPadding + "px";
|
||||
}
|
||||
return (
|
||||
<div className="screen-view" data-screenid={screen.screenId}>
|
||||
<ScreenWindowView key={screen.screenId + ":" + fontSize} session={session} screen={screen} />
|
||||
<div className="screen-view" data-screenid={screen.screenId} ref={this.screenViewRef}>
|
||||
<ScreenWindowView
|
||||
key={screen.screenId + ":" + fontSize}
|
||||
session={session}
|
||||
screen={screen}
|
||||
width={winWidth}
|
||||
/>
|
||||
<If condition={hasSidebar}>
|
||||
<ScreenSidebar screen={screen} width={sidebarWidth} />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type SidebarLineContainerPropsType = {
|
||||
screen: Screen;
|
||||
winSize: T.WindowSize;
|
||||
lineId: string;
|
||||
};
|
||||
|
||||
// note a new SidebarLineContainer will be made for every lineId (so lineId prop should never change)
|
||||
// implemented using a 'key' in parent
|
||||
@mobxReact.observer
|
||||
class SidebarLineContainer extends React.Component<SidebarLineContainerPropsType, {}> {
|
||||
container: ForwardLineContainer;
|
||||
overrideCollapsed: OV<boolean> = mobx.observable.box(false, { name: "overrideCollapsed" });
|
||||
visible: OV<boolean> = mobx.observable.box(true, { name: "visible" });
|
||||
ready: OV<boolean> = mobx.observable.box(false, { name: "ready" });
|
||||
|
||||
componentDidMount(): void {
|
||||
let { screen, winSize, lineId } = this.props;
|
||||
// TODO this is a hack for now to make the timing work out.
|
||||
setTimeout(() => {
|
||||
mobx.action(() => {
|
||||
this.container = new ForwardLineContainer(screen, winSize, appconst.LineContainer_Sidebar, lineId);
|
||||
this.ready.set(true);
|
||||
})();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleHeightChange() {}
|
||||
|
||||
componentDidUpdate(prevProps: SidebarLineContainerPropsType): void {
|
||||
let prevWinSize = prevProps.winSize;
|
||||
let winSize = this.props.winSize;
|
||||
if (prevWinSize.width != winSize.width || prevWinSize.height != winSize.height) {
|
||||
if (this.container != null) {
|
||||
this.container.screenSizeCallback(mobx.toJS(winSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.ready.get() || this.container == null) {
|
||||
return null;
|
||||
}
|
||||
let { screen, winSize, lineId } = this.props;
|
||||
let line = screen.getLineById(lineId);
|
||||
if (line == null) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Line
|
||||
screen={this.container}
|
||||
line={line}
|
||||
width={winSize.width}
|
||||
staticRender={false}
|
||||
visible={this.visible}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
overrideCollapsed={this.overrideCollapsed}
|
||||
topBorder={false}
|
||||
renderMode="normal"
|
||||
noSelect={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ScreenSidebar extends React.Component<{ screen: Screen; width: string }, {}> {
|
||||
rszObs: ResizeObserver;
|
||||
sidebarSize: OV<T.WindowSize> = mobx.observable.box({ height: 0, width: 0 }, { name: "sidebarSize" });
|
||||
sidebarRef: React.RefObject<any> = React.createRef();
|
||||
handleResize_debounced: (entries: ResizeObserverEntry[]) => void;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.handleResize_debounced = debounce(100, this.handleResize.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
let { screen } = this.props;
|
||||
let sidebarElem = this.sidebarRef.current;
|
||||
if (sidebarElem != null) {
|
||||
this.rszObs = new ResizeObserver(this.handleResize_debounced);
|
||||
this.rszObs.observe(sidebarElem);
|
||||
this.handleResize([]);
|
||||
}
|
||||
let size = this.sidebarSize.get();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.rszObs != null) {
|
||||
this.rszObs.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleResize(entries: ResizeObserverEntry[]): void {
|
||||
// dont use entries (just use the ref) -- we call it with an empty array in componentDidMount to initialize it
|
||||
let sidebarElem = this.sidebarRef.current;
|
||||
if (sidebarElem == null) {
|
||||
return;
|
||||
}
|
||||
let size = {
|
||||
width: sidebarElem.offsetWidth - MagicLayout.ScreenMaxContentWidthBuffer,
|
||||
height: sidebarElem.offsetHeight - MagicLayout.ScreenMaxContentHeightBuffer - MagicLayout.ScreenSidebarHeaderHeight,
|
||||
};
|
||||
mobx.action(() => this.sidebarSize.set(size))();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
sidebarClose(): void {
|
||||
GlobalCommandRunner.screenSidebarClose();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
sidebarOpenHalf(): void {
|
||||
GlobalCommandRunner.screenSidebarOpen("50%");
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
sidebarOpenPartial(): void {
|
||||
GlobalCommandRunner.screenSidebarOpen("500px");
|
||||
}
|
||||
|
||||
getSidebarConfig(): T.ScreenSidebarOptsType {
|
||||
let { screen } = this.props;
|
||||
let viewOpts = screen.viewOpts.get();
|
||||
return viewOpts?.sidebar;
|
||||
}
|
||||
|
||||
render() {
|
||||
let { screen, width } = this.props;
|
||||
let sidebarSize = this.sidebarSize.get();
|
||||
let sidebar = this.getSidebarConfig();
|
||||
let lineId = sidebar?.sidebarlineid;
|
||||
let sidebarOk = sidebarSize != null && sidebarSize.width > 0 && !util.isBlank(sidebar?.sidebarlineid);
|
||||
return (
|
||||
<div className="screen-sidebar" style={{ width: width }} ref={this.sidebarRef}>
|
||||
<div className="sidebar-header">
|
||||
<div className="flex-spacer" />
|
||||
<div onClick={this.sidebarOpenHalf} title="Set Sidebar Width to 50%">
|
||||
<i className="fa-sharp fa-solid fa-table-columns" />
|
||||
</div>
|
||||
<div onClick={this.sidebarOpenPartial} title="Set Sidebar Width to 500px">
|
||||
<i className="fa-sharp fa-solid fa-sidebar-flip" />
|
||||
</div>
|
||||
<div onClick={this.sidebarClose} style={{ marginLeft: 5 }}>
|
||||
<i className="fa-sharp fa-solid fa-xmark" />
|
||||
</div>
|
||||
</div>
|
||||
<If condition={!sidebarOk}>
|
||||
<div className="empty-sidebar">
|
||||
<div className="sidebar-main-text">No Sidebar Line Selected</div>
|
||||
<div className="sidebar-help-text">
|
||||
/sidebar:open [width=[50%|500px]]
|
||||
<br />
|
||||
/sidebar:close
|
||||
<br />
|
||||
/sidebar:add line=[linenum]
|
||||
<br />
|
||||
</div>
|
||||
<div onClick={this.sidebarClose} className="close-button-container">
|
||||
<Button theme="secondary" onClick={this.sidebarClose}>
|
||||
Close Sidebar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={sidebarOk}>
|
||||
<SidebarLineContainer key={lineId} screen={screen} winSize={sidebarSize} lineId={lineId} />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -244,8 +498,8 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
|
||||
// screen is not null
|
||||
@mobxReact.observer
|
||||
class ScreenWindowView extends React.Component<{ session: Session; screen: Screen }, {}> {
|
||||
rszObs: any;
|
||||
class ScreenWindowView extends React.Component<{ session: Session; screen: Screen; width: string }, {}> {
|
||||
rszObs: ResizeObserver;
|
||||
windowViewRef: React.RefObject<any>;
|
||||
|
||||
width: mobx.IObservableValue<number> = mobx.observable.box(0, { name: "sw-view-width" });
|
||||
@@ -325,7 +579,12 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
|
||||
renderError(message: string, fade: boolean) {
|
||||
let { screen } = this.props;
|
||||
return (
|
||||
<div className="window-view" ref={this.windowViewRef} data-screenid={screen.screenId}>
|
||||
<div
|
||||
className="window-view"
|
||||
ref={this.windowViewRef}
|
||||
data-screenid={screen.screenId}
|
||||
style={{ width: this.props.width }}
|
||||
>
|
||||
<div key="lines" className="lines"></div>
|
||||
<div key="window-empty" className={cn("window-empty", { "should-fade": fade })}>
|
||||
<div className="text-standard">{message}</div>
|
||||
@@ -404,7 +663,7 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
|
||||
let lines = this.determineVisibleLines(win);
|
||||
let renderMode = this.renderMode.get();
|
||||
return (
|
||||
<div className="window-view" ref={this.windowViewRef}>
|
||||
<div className="window-view" ref={this.windowViewRef} style={{ width: this.props.width }}>
|
||||
<div
|
||||
key="rendermode-tag"
|
||||
className={cn("rendermode-tag", { "is-active": isActive })}
|
||||
|
||||
+264
-34
@@ -78,7 +78,7 @@ import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil";
|
||||
import { MagicLayout } from "../app/magiclayout";
|
||||
import { modalsRegistry } from "../app/common/modals/modalsRegistry";
|
||||
import * as constants from "../app/appconst";
|
||||
import * as appconst from "../app/appconst";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(localizedFormat);
|
||||
@@ -130,6 +130,9 @@ type LineContainerModel = {
|
||||
setContentHeight: (context: RendererContext, height: number) => void;
|
||||
getMaxContentSize(): WindowSize;
|
||||
getIdealContentSize(): WindowSize;
|
||||
isSidebarOpen(): boolean;
|
||||
isLineIdInSidebar(lineId: string): boolean;
|
||||
getContainerType(): T.LineContainerStrs;
|
||||
};
|
||||
|
||||
type SWLinePtr = {
|
||||
@@ -341,6 +344,7 @@ class Screen {
|
||||
screenId: string;
|
||||
screenIdx: OV<number>;
|
||||
opts: OV<ScreenOptsType>;
|
||||
viewOpts: OV<T.ScreenViewOptsType>;
|
||||
name: OV<string>;
|
||||
archived: OV<boolean>;
|
||||
curRemote: OV<RemotePtrType>;
|
||||
@@ -368,6 +372,7 @@ class Screen {
|
||||
name: "screen-screenidx",
|
||||
});
|
||||
this.opts = mobx.observable.box(sdata.screenopts, { name: "screen-opts" });
|
||||
this.viewOpts = mobx.observable.box(sdata.screenviewopts, { name: "viewOpts" });
|
||||
this.archived = mobx.observable.box(!!sdata.archived, {
|
||||
name: "screen-archived",
|
||||
});
|
||||
@@ -405,6 +410,29 @@ class Screen {
|
||||
return this.shareMode.get() == "web" && this.webShareOpts.get() != null;
|
||||
}
|
||||
|
||||
isSidebarOpen(): boolean {
|
||||
let viewOpts = this.viewOpts.get();
|
||||
if (viewOpts == null) {
|
||||
return false;
|
||||
}
|
||||
return viewOpts.sidebar?.open;
|
||||
}
|
||||
|
||||
isLineIdInSidebar(lineId: string): boolean {
|
||||
let viewOpts = this.viewOpts.get();
|
||||
if (viewOpts == null) {
|
||||
return false;
|
||||
}
|
||||
if (!viewOpts.sidebar?.open) {
|
||||
return false;
|
||||
}
|
||||
return viewOpts?.sidebar?.sidebarlineid == lineId;
|
||||
}
|
||||
|
||||
getContainerType(): T.LineContainerStrs {
|
||||
return appconst.LineContainer_Main;
|
||||
}
|
||||
|
||||
getShareName(): string {
|
||||
if (!this.isWebShared()) {
|
||||
return null;
|
||||
@@ -441,6 +469,7 @@ class Screen {
|
||||
mobx.action(() => {
|
||||
this.screenIdx.set(data.screenidx);
|
||||
this.opts.set(data.screenopts);
|
||||
this.viewOpts.set(data.screenviewopts);
|
||||
this.name.set(data.name);
|
||||
this.nextLineNum.set(data.nextlinenum);
|
||||
this.archived.set(!!data.archived);
|
||||
@@ -468,6 +497,10 @@ class Screen {
|
||||
return GlobalModel.getCmd(line);
|
||||
}
|
||||
|
||||
getCmdById(lineId: string): Cmd {
|
||||
return GlobalModel.getCmdByScreenLine(this.screenId, lineId);
|
||||
}
|
||||
|
||||
getAnchorStr(): string {
|
||||
let anchor = this.anchor.get();
|
||||
if (anchor.anchorLine == null || anchor.anchorLine == 0) {
|
||||
@@ -590,6 +623,26 @@ class Screen {
|
||||
return null;
|
||||
}
|
||||
|
||||
getLineById(lineId: string): LineType {
|
||||
if (lineId == null) {
|
||||
return null;
|
||||
}
|
||||
let win = this.getScreenLines();
|
||||
if (win == null) {
|
||||
return null;
|
||||
}
|
||||
let lines = win.lines;
|
||||
if (lines == null || lines.length == 0) {
|
||||
return null;
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].lineid == lineId) {
|
||||
return lines[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getPresentLineNum(lineNum: number): number {
|
||||
let win = this.getScreenLines();
|
||||
if (win == null || !win.loaded.get()) {
|
||||
@@ -704,10 +757,16 @@ class Screen {
|
||||
}
|
||||
this.lastRows = rows;
|
||||
this.lastCols = cols;
|
||||
let exclude = [];
|
||||
for (let lineid in this.terminals) {
|
||||
this.terminals[lineid].resizeCols(cols);
|
||||
let inSidebar = this.isLineIdInSidebar(lineid);
|
||||
if (!inSidebar) {
|
||||
this.terminals[lineid].resizeCols(cols);
|
||||
} else {
|
||||
exclude.push(lineid);
|
||||
}
|
||||
}
|
||||
GlobalCommandRunner.resizeScreen(this.screenId, rows, cols);
|
||||
GlobalCommandRunner.resizeScreen(this.screenId, rows, cols, { exclude });
|
||||
}
|
||||
|
||||
getTermWrap(lineId: string): TermWrap {
|
||||
@@ -1779,23 +1838,142 @@ type LineFocusType = {
|
||||
screenid?: string;
|
||||
};
|
||||
|
||||
class SpecialHistoryViewLineContainer {
|
||||
historyItem: HistoryItem;
|
||||
type CmdFinder = {
|
||||
getCmdById(cmdId: string): Cmd;
|
||||
};
|
||||
|
||||
class ForwardLineContainer {
|
||||
winSize: T.WindowSize;
|
||||
screen: Screen;
|
||||
containerType: T.LineContainerStrs;
|
||||
lineId: string;
|
||||
|
||||
constructor(screen: Screen, winSize: T.WindowSize, containerType: T.LineContainerStrs, lineId: string) {
|
||||
this.screen = screen;
|
||||
this.winSize = winSize;
|
||||
this.containerType = containerType;
|
||||
this.lineId = lineId;
|
||||
}
|
||||
|
||||
screenSizeCallback(winSize: WindowSize): void {
|
||||
this.winSize = winSize;
|
||||
let termWrap = this.getTermWrap(this.lineId);
|
||||
if (termWrap != null) {
|
||||
let fontSize = GlobalModel.termFontSize.get();
|
||||
let cols = windowWidthToCols(winSize.width, fontSize);
|
||||
let rows = windowHeightToRows(winSize.height, fontSize);
|
||||
termWrap.resizeCols(cols);
|
||||
GlobalCommandRunner.resizeScreen(this.screen.screenId, rows, cols, { include: [this.lineId] });
|
||||
}
|
||||
}
|
||||
|
||||
getContainerType(): T.LineContainerStrs {
|
||||
return this.containerType;
|
||||
}
|
||||
|
||||
getCmd(line: LineType): Cmd {
|
||||
return this.screen.getCmd(line);
|
||||
}
|
||||
|
||||
isSidebarOpen(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isLineIdInSidebar(lineId: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
setLineFocus(lineNum: number, focus: boolean): void {
|
||||
this.screen.setLineFocus(lineNum, focus);
|
||||
}
|
||||
|
||||
setContentHeight(context: RendererContext, height: number): void {
|
||||
return;
|
||||
}
|
||||
|
||||
getMaxContentSize(): WindowSize {
|
||||
return this.winSize;
|
||||
}
|
||||
|
||||
getIdealContentSize(): WindowSize {
|
||||
return this.winSize;
|
||||
}
|
||||
|
||||
loadTerminalRenderer(elem: Element, line: LineType, cmd: Cmd, width: number): void {
|
||||
this.screen.loadTerminalRenderer(elem, line, cmd, width);
|
||||
}
|
||||
|
||||
registerRenderer(lineId: string, renderer: RendererModel): void {
|
||||
this.screen.registerRenderer(lineId, renderer);
|
||||
}
|
||||
|
||||
unloadRenderer(lineId: string): void {
|
||||
this.screen.unloadRenderer(lineId);
|
||||
}
|
||||
|
||||
getContentHeight(context: RendererContext): number {
|
||||
return this.screen.getContentHeight(context);
|
||||
}
|
||||
|
||||
getUsedRows(context: RendererContext, line: LineType, cmd: Cmd, width: number): number {
|
||||
return this.screen.getUsedRows(context, line, cmd, width);
|
||||
}
|
||||
|
||||
getIsFocused(lineNum: number): boolean {
|
||||
return this.screen.getIsFocused(lineNum);
|
||||
}
|
||||
|
||||
getRenderer(lineId: string): RendererModel {
|
||||
return this.screen.getRenderer(lineId);
|
||||
}
|
||||
|
||||
getTermWrap(lineId: string): TermWrap {
|
||||
return this.screen.getTermWrap(lineId);
|
||||
}
|
||||
|
||||
getFocusType(): FocusTypeStrs {
|
||||
return this.screen.getFocusType();
|
||||
}
|
||||
|
||||
getSelectedLine(): number {
|
||||
return this.screen.getSelectedLine();
|
||||
}
|
||||
}
|
||||
|
||||
class SpecialLineContainer {
|
||||
wsize: T.WindowSize;
|
||||
allowInput: boolean;
|
||||
terminal: TermWrap;
|
||||
renderer: RendererModel;
|
||||
cmd: Cmd;
|
||||
cmdFinder: CmdFinder;
|
||||
containerType: T.LineContainerStrs;
|
||||
|
||||
constructor(hitem: HistoryItem) {
|
||||
this.historyItem = hitem;
|
||||
constructor(cmdFinder: CmdFinder, wsize: T.WindowSize, allowInput: boolean, containerType: T.LineContainerStrs) {
|
||||
this.cmdFinder = cmdFinder;
|
||||
this.wsize = wsize;
|
||||
this.allowInput = allowInput;
|
||||
}
|
||||
|
||||
getCmd(line: LineType): Cmd {
|
||||
if (this.cmd == null) {
|
||||
this.cmd = GlobalModel.historyViewModel.getCmdById(line.lineid);
|
||||
this.cmd = this.cmdFinder.getCmdById(line.lineid);
|
||||
}
|
||||
return this.cmd;
|
||||
}
|
||||
|
||||
getContainerType(): T.LineContainerStrs {
|
||||
return this.containerType;
|
||||
}
|
||||
|
||||
isSidebarOpen(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isLineIdInSidebar(lineId: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
setLineFocus(lineNum: number, focus: boolean): void {
|
||||
return;
|
||||
}
|
||||
@@ -1805,15 +1983,11 @@ class SpecialHistoryViewLineContainer {
|
||||
}
|
||||
|
||||
getMaxContentSize(): WindowSize {
|
||||
let width = termWidthFromCols(80, GlobalModel.termFontSize.get());
|
||||
let height = termHeightFromRows(25, GlobalModel.termFontSize.get());
|
||||
return { width, height };
|
||||
return this.wsize;
|
||||
}
|
||||
|
||||
getIdealContentSize(): WindowSize {
|
||||
let width = termWidthFromCols(80, GlobalModel.termFontSize.get());
|
||||
let height = termHeightFromRows(25, GlobalModel.termFontSize.get());
|
||||
return { width, height };
|
||||
return this.wsize;
|
||||
}
|
||||
|
||||
loadTerminalRenderer(elem: Element, line: LineType, cmd: Cmd, width: number): void {
|
||||
@@ -1956,7 +2130,7 @@ class HistoryViewModel {
|
||||
historyItemLines: LineType[] = [];
|
||||
historyItemCmds: CmdDataType[] = [];
|
||||
|
||||
specialLineContainer: SpecialHistoryViewLineContainer;
|
||||
specialLineContainer: SpecialLineContainer;
|
||||
|
||||
constructor() {}
|
||||
|
||||
@@ -2015,7 +2189,14 @@ class HistoryViewModel {
|
||||
this.specialLineContainer = null;
|
||||
} else {
|
||||
this.activeItem.set(hitem.historyid);
|
||||
this.specialLineContainer = new SpecialHistoryViewLineContainer(hitem);
|
||||
let width = termWidthFromCols(80, GlobalModel.termFontSize.get());
|
||||
let height = termHeightFromRows(25, GlobalModel.termFontSize.get());
|
||||
this.specialLineContainer = new SpecialLineContainer(
|
||||
this,
|
||||
{ width, height },
|
||||
false,
|
||||
appconst.LineContainer_History
|
||||
);
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -2751,26 +2932,26 @@ class RemotesModel {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(remoteId);
|
||||
this.remoteEdit.set(null);
|
||||
GlobalModel.modalsModel.pushModal(constants.VIEW_REMOTE);
|
||||
GlobalModel.modalsModel.pushModal(appconst.VIEW_REMOTE);
|
||||
})();
|
||||
}
|
||||
|
||||
openAddModal(redit: RemoteEditType): void {
|
||||
mobx.action(() => {
|
||||
this.remoteEdit.set(redit);
|
||||
GlobalModel.modalsModel.pushModal(constants.CREATE_REMOTE);
|
||||
GlobalModel.modalsModel.pushModal(appconst.CREATE_REMOTE);
|
||||
})();
|
||||
}
|
||||
|
||||
openEditModal(redit?: RemoteEditType): void {
|
||||
if (redit == null) {
|
||||
this.startEditAuth();
|
||||
GlobalModel.modalsModel.pushModal(constants.EDIT_REMOTE);
|
||||
GlobalModel.modalsModel.pushModal(appconst.EDIT_REMOTE);
|
||||
} else {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(redit?.remoteid);
|
||||
this.remoteEdit.set(redit);
|
||||
GlobalModel.modalsModel.pushModal(constants.EDIT_REMOTE);
|
||||
GlobalModel.modalsModel.pushModal(appconst.EDIT_REMOTE);
|
||||
})();
|
||||
}
|
||||
}
|
||||
@@ -2996,9 +3177,10 @@ class Model {
|
||||
this.clientId = getApi().getId();
|
||||
this.isDev = getApi().getIsDev();
|
||||
this.authKey = getApi().getAuthKey();
|
||||
this.ws = new WSControl(this.getBaseWsHostPort(), this.clientId, this.authKey, (message: any) =>
|
||||
this.runUpdate(message, false)
|
||||
);
|
||||
this.ws = new WSControl(this.getBaseWsHostPort(), this.clientId, this.authKey, (message: any) => {
|
||||
let interactive = message?.interactive ?? false;
|
||||
this.runUpdate(message, interactive);
|
||||
});
|
||||
this.ws.reconnect();
|
||||
this.inputModel = new InputModel();
|
||||
this.pluginsModel = new PluginsModel();
|
||||
@@ -3096,7 +3278,7 @@ class Model {
|
||||
showAlert(alertMessage: AlertMessageType): Promise<boolean> {
|
||||
mobx.action(() => {
|
||||
this.alertMessage.set(alertMessage);
|
||||
GlobalModel.modalsModel.pushModal(constants.ALERT);
|
||||
GlobalModel.modalsModel.pushModal(appconst.ALERT);
|
||||
})();
|
||||
let prtn = new Promise<boolean>((resolve, reject) => {
|
||||
this.alertPromiseResolver = resolve;
|
||||
@@ -3219,6 +3401,23 @@ class Model {
|
||||
e.preventDefault();
|
||||
GlobalCommandRunner.bookmarksView();
|
||||
}
|
||||
if (
|
||||
this.activeMainView.get() == "session" &&
|
||||
e.code == "KeyS" &&
|
||||
e.getModifierState("Meta") &&
|
||||
e.getModifierState("Control")
|
||||
) {
|
||||
e.preventDefault();
|
||||
let activeScreen = this.getActiveScreen();
|
||||
if (activeScreen != null) {
|
||||
let isSidebarOpen = activeScreen.isSidebarOpen();
|
||||
if (isSidebarOpen) {
|
||||
GlobalCommandRunner.screenSidebarClose();
|
||||
} else {
|
||||
GlobalCommandRunner.screenSidebarOpen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearModals(): boolean {
|
||||
@@ -3375,7 +3574,7 @@ class Model {
|
||||
|
||||
onMenuItemAbout(): void {
|
||||
mobx.action(() => {
|
||||
this.modalsModel.pushModal(constants.ABOUT);
|
||||
this.modalsModel.pushModal(appconst.ABOUT);
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -4098,14 +4297,23 @@ class CommandRunner {
|
||||
GlobalModel.submitCommand("screen", "close", [screen], { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
resizeScreen(screenId: string, rows: number, cols: number) {
|
||||
GlobalModel.submitCommand(
|
||||
"screen",
|
||||
"resize",
|
||||
null,
|
||||
{ nohist: "1", screen: screenId, cols: String(cols), rows: String(rows) },
|
||||
false
|
||||
);
|
||||
// include is lineIds to include, exclude is lineIds to exclude
|
||||
// if include is given then it *only* does those ids. if exclude is given (or not),
|
||||
// it does all running commands in the screen except for excluded.
|
||||
resizeScreen(screenId: string, rows: number, cols: number, opts?: { include?: string[]; exclude?: string[] }) {
|
||||
let kwargs: Record<string, string> = {
|
||||
nohist: "1",
|
||||
screen: screenId,
|
||||
cols: String(cols),
|
||||
rows: String(rows),
|
||||
};
|
||||
if (opts?.include != null && opts?.include.length > 0) {
|
||||
kwargs.include = opts.include.join(",");
|
||||
}
|
||||
if (opts?.exclude != null && opts?.exclude.length > 0) {
|
||||
kwargs.exclude = opts.exclude.join(",");
|
||||
}
|
||||
GlobalModel.submitCommand("screen", "resize", null, kwargs, false);
|
||||
}
|
||||
|
||||
screenArchive(screenId: string, shouldArchive: boolean): Promise<CommandRtnType> {
|
||||
@@ -4380,6 +4588,26 @@ class CommandRunner {
|
||||
interactive
|
||||
);
|
||||
}
|
||||
|
||||
screenSidebarAddLine(lineId: string) {
|
||||
GlobalModel.submitCommand("sidebar", "add", null, { nohist: "1", line: lineId }, false);
|
||||
}
|
||||
|
||||
screenSidebarRemove() {
|
||||
GlobalModel.submitCommand("sidebar", "remove", null, { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
screenSidebarClose(): void {
|
||||
GlobalModel.submitCommand("sidebar", "close", null, { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
screenSidebarOpen(width?: string): void {
|
||||
let kwargs: Record<string, string> = { nohist: "1" };
|
||||
if (width != null) {
|
||||
kwargs.width = width;
|
||||
}
|
||||
GlobalModel.submitCommand("sidebar", "open", null, kwargs, false);
|
||||
}
|
||||
}
|
||||
|
||||
function cmdPacketString(pk: FeCmdPacketType): string {
|
||||
@@ -4467,6 +4695,8 @@ export {
|
||||
RemotesModel,
|
||||
MinFontSize,
|
||||
MaxFontSize,
|
||||
VERSION
|
||||
SpecialLineContainer,
|
||||
ForwardLineContainer,
|
||||
VERSION,
|
||||
};
|
||||
export type { LineContainerModel };
|
||||
|
||||
+16
-1
@@ -8,6 +8,7 @@ type ShareModeType = "local" | "web";
|
||||
type FocusTypeStrs = "input" | "cmd";
|
||||
type HistoryTypeStrs = "global" | "session" | "screen";
|
||||
type RemoteStatusTypeStrs = "connected" | "connecting" | "disconnected" | "error";
|
||||
type LineContainerStrs = "main" | "sidebar" | "history";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@@ -59,6 +60,16 @@ type WebShareOpts = {
|
||||
viewkey: string;
|
||||
};
|
||||
|
||||
type ScreenViewOptsType = {
|
||||
sidebar: ScreenSidebarOptsType;
|
||||
};
|
||||
|
||||
type ScreenSidebarOptsType = {
|
||||
open: boolean;
|
||||
width: string;
|
||||
sidebarlineid: string;
|
||||
};
|
||||
|
||||
type ScreenDataType = {
|
||||
sessionid: string;
|
||||
screenid: string;
|
||||
@@ -68,6 +79,7 @@ type ScreenDataType = {
|
||||
webshareopts?: WebShareOpts;
|
||||
archived?: boolean;
|
||||
screenopts: ScreenOptsType;
|
||||
screenviewopts: ScreenViewOptsType;
|
||||
curremote: RemotePtrType;
|
||||
nextlinenum: number;
|
||||
selectedline: number;
|
||||
@@ -458,7 +470,7 @@ type ClientOptsType = {
|
||||
acceptedtos: number;
|
||||
};
|
||||
|
||||
type ReleaseInfoType = {
|
||||
type ReleaseInfoType = {
|
||||
latestversion: string;
|
||||
};
|
||||
|
||||
@@ -660,6 +672,8 @@ export type {
|
||||
FeCmdPacketType,
|
||||
TermOptsType,
|
||||
CmdDataType,
|
||||
ScreenViewOptsType,
|
||||
ScreenSidebarOptsType,
|
||||
ScreenDataType,
|
||||
ScreenOptsType,
|
||||
PtyDataUpdateType,
|
||||
@@ -719,4 +733,5 @@ export type {
|
||||
FileInfoType,
|
||||
ExtBlob,
|
||||
ExtFile,
|
||||
LineContainerStrs,
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE screen DROP COLUMN screenviewopts;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE screen ADD COLUMN screenviewopts json DEFAULT '{}';
|
||||
@@ -63,6 +63,7 @@ const MaxSignalNum = 64
|
||||
const MaxEvalDepth = 5
|
||||
const MaxOpenAIAPITokenLen = 100
|
||||
const MaxOpenAIModelLen = 100
|
||||
const MaxSidebarSections = 5
|
||||
|
||||
const TermFontSizeMin = 8
|
||||
const TermFontSizeMax = 24
|
||||
@@ -209,6 +210,11 @@ func init() {
|
||||
registerCmdFn("client:notifyupdatewriter", ClientNotifyUpdateWriterCommand)
|
||||
registerCmdFn("client:accepttos", ClientAcceptTosCommand)
|
||||
|
||||
registerCmdFn("sidebar:open", SidebarOpenCommand)
|
||||
registerCmdFn("sidebar:close", SidebarCloseCommand)
|
||||
registerCmdFn("sidebar:add", SidebarAddCommand)
|
||||
registerCmdFn("sidebar:remove", SidebarRemoveCommand)
|
||||
|
||||
registerCmdFn("telemetry", TelemetryCommand)
|
||||
registerCmdFn("telemetry:on", TelemetryOnCommand)
|
||||
registerCmdFn("telemetry:off", TelemetryOffCommand)
|
||||
@@ -308,6 +314,20 @@ func argN(pk *scpacket.FeCommandPacketType, n int) string {
|
||||
return pk.Args[n]
|
||||
}
|
||||
|
||||
// will trim strings for whitespace
|
||||
func resolveCommaSepListToMap(arg string) map[string]bool {
|
||||
if arg == "" {
|
||||
return nil
|
||||
}
|
||||
rtn := make(map[string]bool)
|
||||
fields := strings.Split(arg, ",")
|
||||
for _, field := range fields {
|
||||
field = strings.TrimSpace(field)
|
||||
rtn[field] = true
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func resolveBool(arg string, def bool) bool {
|
||||
if arg == "" {
|
||||
return def
|
||||
@@ -578,10 +598,26 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U
|
||||
return nil, err
|
||||
}
|
||||
update.Interactive = pk.Interactive
|
||||
// this update is sent asynchronously for timing issues. the cmd update comes async as well
|
||||
// so if we return this directly it sometimes gets evaluated first. by pushing it on the MainBus
|
||||
// it ensures it happens after the command creation event.
|
||||
sstore.MainBus.SendScreenUpdate(ids.ScreenId, update)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func implementRunInSidebar(ctx context.Context, screenId string, lineId string) (*sstore.ScreenType, error) {
|
||||
screen, err := sidebarSetOpen(ctx, "run", screenId, true, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
screen.ScreenViewOpts.Sidebar.SidebarLineId = lineId
|
||||
err = sstore.ScreenUpdateViewOpts(ctx, screenId, screen.ScreenViewOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/run error updating screenviewopts: %v", err)
|
||||
}
|
||||
return screen, nil
|
||||
}
|
||||
|
||||
func addToHistory(ctx context.Context, pk *scpacket.FeCommandPacketType, historyContext historyContextType, isMetaCmd bool, hadError bool) error {
|
||||
cmdStr := firstArg(pk)
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
|
||||
@@ -641,12 +677,36 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.
|
||||
update, rtnErr = HandleCommand(ctxWithHistory, newPk)
|
||||
}
|
||||
if !resolveBool(pk.Kwargs["nohist"], false) {
|
||||
// TODO should this be "pk" or "newPk" (2nd arg)
|
||||
err := addToHistory(ctx, pk, historyContext, (newPk.MetaCmd != "run"), (rtnErr != nil))
|
||||
if err != nil {
|
||||
log.Printf("[error] adding to history: %v\n", err)
|
||||
// fall through (non-fatal error)
|
||||
}
|
||||
}
|
||||
var hasModelUpdate bool
|
||||
var modelUpdate *sstore.ModelUpdate
|
||||
if update == nil {
|
||||
hasModelUpdate = true
|
||||
modelUpdate = &sstore.ModelUpdate{}
|
||||
update = modelUpdate
|
||||
} else if mu, ok := update.(*sstore.ModelUpdate); ok {
|
||||
hasModelUpdate = true
|
||||
modelUpdate = mu
|
||||
}
|
||||
if resolveBool(newPk.Kwargs["sidebar"], false) && historyContext.LineId != "" && hasModelUpdate {
|
||||
ids, resolveErr := resolveUiIds(ctx, newPk, R_Session|R_Screen)
|
||||
// we are ignoring resolveErr (if not nil). obviously can't add to sidebar and
|
||||
// either another error already happened, or this command was never about the sidebar
|
||||
if resolveErr == nil {
|
||||
screen, sidebarErr := implementRunInSidebar(ctx, ids.ScreenId, historyContext.LineId)
|
||||
if sidebarErr == nil {
|
||||
modelUpdate.UpdateScreen(screen)
|
||||
} else {
|
||||
modelUpdate.AddInfoError(fmt.Sprintf("cannot move command to sidebar: %v", sidebarErr))
|
||||
}
|
||||
}
|
||||
}
|
||||
return update, rtnErr
|
||||
}
|
||||
|
||||
@@ -776,6 +836,8 @@ func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
||||
return update, nil
|
||||
}
|
||||
|
||||
var screenAnchorRe = regexp.MustCompile("^(\\d+)(?::(-?\\d+))?$")
|
||||
|
||||
func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
|
||||
if err != nil {
|
||||
@@ -908,7 +970,115 @@ func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
|
||||
return update, nil
|
||||
}
|
||||
|
||||
var screenAnchorRe = regexp.MustCompile("^(\\d+)(?::(-?\\d+))?$")
|
||||
var sidebarWidthRe = regexp.MustCompile("^\\d+(px|%)$")
|
||||
|
||||
func sidebarSetOpen(ctx context.Context, cmdStr string, screenId string, open bool, width string) (*sstore.ScreenType, error) {
|
||||
if width != "" && !sidebarWidthRe.MatchString(width) {
|
||||
return nil, fmt.Errorf("/%s invalid width specified, must be either a px value or a percent (e.g. '300px' or '50%%')", cmdStr)
|
||||
}
|
||||
if strings.HasSuffix(width, "%") {
|
||||
percentNum, _ := strconv.Atoi(width[:len(width)-1])
|
||||
if percentNum < 10 || percentNum > 90 {
|
||||
return nil, fmt.Errorf("/%s invalid width specified, percentage must be between 10%% and 90%%", cmdStr)
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(width, "px") {
|
||||
pxNum, _ := strconv.Atoi(width[:len(width)-2])
|
||||
if pxNum < 200 {
|
||||
return nil, fmt.Errorf("/%s invalid width specified, minimum sizebar width is 200px", cmdStr)
|
||||
}
|
||||
}
|
||||
screen, err := sstore.GetScreenById(ctx, screenId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/%s cannot get screen: %v", cmdStr, err)
|
||||
}
|
||||
if screen.ScreenViewOpts.Sidebar == nil {
|
||||
screen.ScreenViewOpts.Sidebar = &sstore.ScreenSidebarOptsType{}
|
||||
}
|
||||
screen.ScreenViewOpts.Sidebar.Open = open
|
||||
if width != "" {
|
||||
screen.ScreenViewOpts.Sidebar.Width = width
|
||||
}
|
||||
err = sstore.ScreenUpdateViewOpts(ctx, screenId, screen.ScreenViewOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/%s error updating screenviewopts: %v", cmdStr, err)
|
||||
}
|
||||
return screen, nil
|
||||
}
|
||||
|
||||
func SidebarOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Screen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
screen, err := sidebarSetOpen(ctx, GetCmdStr(pk), ids.ScreenId, true, pk.Kwargs["width"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil
|
||||
}
|
||||
|
||||
func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Screen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
screen, err := sidebarSetOpen(ctx, GetCmdStr(pk), ids.ScreenId, false, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil
|
||||
}
|
||||
|
||||
func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Screen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var addLineId string
|
||||
if lineArg, ok := pk.Kwargs["line"]; ok {
|
||||
lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error looking up lineid: %v", err)
|
||||
}
|
||||
addLineId = lineId
|
||||
}
|
||||
if addLineId == "" {
|
||||
return nil, fmt.Errorf("/%s must specify line=[lineid] to add to the sidebar", GetCmdStr(pk))
|
||||
}
|
||||
screen, err := sidebarSetOpen(ctx, GetCmdStr(pk), ids.ScreenId, true, pk.Kwargs["width"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
screen.ScreenViewOpts.Sidebar.SidebarLineId = addLineId
|
||||
err = sstore.ScreenUpdateViewOpts(ctx, ids.ScreenId, screen.ScreenViewOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err)
|
||||
}
|
||||
return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil
|
||||
}
|
||||
|
||||
func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Screen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
screen, err := sstore.GetScreenById(ctx, ids.ScreenId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/%s cannot get screeen: %v", GetCmdStr(pk), err)
|
||||
}
|
||||
sidebar := screen.ScreenViewOpts.Sidebar
|
||||
if sidebar == nil {
|
||||
return nil, nil
|
||||
}
|
||||
sidebar.SidebarLineId = ""
|
||||
sidebar.Open = false
|
||||
err = sstore.ScreenUpdateViewOpts(ctx, ids.ScreenId, screen.ScreenViewOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err)
|
||||
}
|
||||
return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil
|
||||
}
|
||||
|
||||
func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote)
|
||||
@@ -2629,7 +2799,15 @@ func ScreenResizeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
|
||||
if len(runningCmds) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
includeMap := resolveCommaSepListToMap(pk.Kwargs["include"])
|
||||
excludeMap := resolveCommaSepListToMap(pk.Kwargs["exclude"])
|
||||
for _, cmd := range runningCmds {
|
||||
if excludeMap[cmd.LineId] {
|
||||
continue
|
||||
}
|
||||
if len(includeMap) > 0 && !includeMap[cmd.LineId] {
|
||||
continue
|
||||
}
|
||||
if int(cmd.TermOpts.Cols) != cols {
|
||||
resizeRunningCommand(ctx, cmd, cols)
|
||||
}
|
||||
|
||||
@@ -299,6 +299,7 @@ func EvalMetaCommand(ctx context.Context, origPk *scpacket.FeCommandPacketType)
|
||||
rtnPk.Kwargs = make(map[string]string)
|
||||
rtnPk.UIContext = origPk.UIContext
|
||||
rtnPk.RawStr = origPk.RawStr
|
||||
rtnPk.Interactive = origPk.Interactive
|
||||
for key, val := range origPk.Kwargs {
|
||||
rtnPk.Kwargs[key] = val
|
||||
}
|
||||
|
||||
@@ -567,14 +567,14 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
update := ModelUpdate{
|
||||
update := &ModelUpdate{
|
||||
Sessions: []*SessionType{session},
|
||||
Screens: []*ScreenType{newScreen},
|
||||
}
|
||||
if activate {
|
||||
update.ActiveSessionId = newSessionId
|
||||
}
|
||||
return &update, nil
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func SetActiveSessionId(ctx context.Context, sessionId string) error {
|
||||
@@ -710,8 +710,8 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string,
|
||||
Archived: false,
|
||||
ArchivedTs: 0,
|
||||
}
|
||||
query = `INSERT INTO screen ( sessionid, screenid, name, screenidx, screenopts, ownerid, sharemode, webshareopts, curremoteownerid, curremoteid, curremotename, nextlinenum, selectedline, anchor, focustype, archived, archivedts)
|
||||
VALUES (:sessionid,:screenid,:name,:screenidx,:screenopts,:ownerid,:sharemode,:webshareopts,:curremoteownerid,:curremoteid,:curremotename,:nextlinenum,:selectedline,:anchor,:focustype,:archived,:archivedts)`
|
||||
query = `INSERT INTO screen ( sessionid, screenid, name, screenidx, screenopts, screenviewopts, ownerid, sharemode, webshareopts, curremoteownerid, curremoteid, curremotename, nextlinenum, selectedline, anchor, focustype, archived, archivedts)
|
||||
VALUES (:sessionid,:screenid,:name,:screenidx,:screenopts,:screenviewopts,:ownerid,:sharemode,:webshareopts,:curremoteownerid,:curremoteid,:curremotename,:nextlinenum,:selectedline,:anchor,:focustype,:archived,:archivedts)`
|
||||
tx.NamedExec(query, screen.ToMap())
|
||||
if activate {
|
||||
query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?`
|
||||
@@ -1150,7 +1150,7 @@ func PurgeScreen(ctx context.Context, screenId string, sessionDel bool) (UpdateP
|
||||
return nil, nil
|
||||
}
|
||||
update := &ModelUpdate{}
|
||||
update.Screens = []*ScreenType{&ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true}}
|
||||
update.Screens = []*ScreenType{{SessionId: sessionId, ScreenId: screenId, Remove: true}}
|
||||
if isActive {
|
||||
bareSession, err := GetBareSessionById(ctx, sessionId)
|
||||
if err != nil {
|
||||
@@ -1764,6 +1764,14 @@ func UpdateScreen(ctx context.Context, screenId string, editMap map[string]inter
|
||||
return GetScreenById(ctx, screenId)
|
||||
}
|
||||
|
||||
func ScreenUpdateViewOpts(ctx context.Context, screenId string, viewOpts ScreenViewOptsType) error {
|
||||
return WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `UPDATE screen SET screenviewopts = ? WHERE screenid = ?`
|
||||
tx.Exec(query, quickJson(viewOpts), screenId)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetLineResolveItems(ctx context.Context, screenId string) ([]ResolveItem, error) {
|
||||
var rtn []ResolveItem
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
)
|
||||
|
||||
const MaxMigration = 25
|
||||
const MaxMigration = 26
|
||||
const MigratePrimaryScreenVersion = 9
|
||||
const CmdScreenSpecialMigration = 13
|
||||
const CmdLineSpecialMigration = 20
|
||||
@@ -51,12 +51,12 @@ func copyFile(srcFile string, dstFile string, notFoundOk bool) error {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open %s: %v", err)
|
||||
return fmt.Errorf("cannot open %s: %v", srcFile, err)
|
||||
}
|
||||
defer srcFd.Close()
|
||||
dstFd, err := os.OpenFile(dstFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open destination file %s: %v", err)
|
||||
return fmt.Errorf("cannot open destination file %s: %v", dstFile, err)
|
||||
}
|
||||
_, err = io.Copy(dstFd, srcFd)
|
||||
if err != nil {
|
||||
|
||||
@@ -467,22 +467,35 @@ func (sco ScreenCreateOpts) HasCopy() bool {
|
||||
return sco.CopyRemote || sco.CopyCwd || sco.CopyEnv
|
||||
}
|
||||
|
||||
type ScreenSidebarOptsType struct {
|
||||
Open bool `json:"open,omitempty"`
|
||||
Width string `json:"width,omitempty"`
|
||||
|
||||
// this used to be more complicated (sections with types). simplified for this release
|
||||
SidebarLineId string `json:"sidebarlineid,omitempty"`
|
||||
}
|
||||
|
||||
type ScreenViewOptsType struct {
|
||||
Sidebar *ScreenSidebarOptsType `json:"sidebar,omitempty"`
|
||||
}
|
||||
|
||||
type ScreenType struct {
|
||||
SessionId string `json:"sessionid"`
|
||||
ScreenId string `json:"screenid"`
|
||||
Name string `json:"name"`
|
||||
ScreenIdx int64 `json:"screenidx"`
|
||||
ScreenOpts ScreenOptsType `json:"screenopts"`
|
||||
OwnerId string `json:"ownerid"`
|
||||
ShareMode string `json:"sharemode"`
|
||||
WebShareOpts *ScreenWebShareOpts `json:"webshareopts,omitempty"`
|
||||
CurRemote RemotePtrType `json:"curremote"`
|
||||
NextLineNum int64 `json:"nextlinenum"`
|
||||
SelectedLine int64 `json:"selectedline"`
|
||||
Anchor ScreenAnchorType `json:"anchor"`
|
||||
FocusType string `json:"focustype"`
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
ArchivedTs int64 `json:"archivedts,omitempty"`
|
||||
SessionId string `json:"sessionid"`
|
||||
ScreenId string `json:"screenid"`
|
||||
Name string `json:"name"`
|
||||
ScreenIdx int64 `json:"screenidx"`
|
||||
ScreenOpts ScreenOptsType `json:"screenopts"`
|
||||
ScreenViewOpts ScreenViewOptsType `json:"screenviewopts"`
|
||||
OwnerId string `json:"ownerid"`
|
||||
ShareMode string `json:"sharemode"`
|
||||
WebShareOpts *ScreenWebShareOpts `json:"webshareopts,omitempty"`
|
||||
CurRemote RemotePtrType `json:"curremote"`
|
||||
NextLineNum int64 `json:"nextlinenum"`
|
||||
SelectedLine int64 `json:"selectedline"`
|
||||
Anchor ScreenAnchorType `json:"anchor"`
|
||||
FocusType string `json:"focustype"`
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
ArchivedTs int64 `json:"archivedts,omitempty"`
|
||||
|
||||
// only for updates
|
||||
Full bool `json:"full,omitempty"`
|
||||
@@ -496,6 +509,7 @@ func (s *ScreenType) ToMap() map[string]interface{} {
|
||||
rtn["name"] = s.Name
|
||||
rtn["screenidx"] = s.ScreenIdx
|
||||
rtn["screenopts"] = quickJson(s.ScreenOpts)
|
||||
rtn["screenviewopts"] = quickJson(s.ScreenViewOpts)
|
||||
rtn["ownerid"] = s.OwnerId
|
||||
rtn["sharemode"] = s.ShareMode
|
||||
rtn["webshareopts"] = quickNullableJson(s.WebShareOpts)
|
||||
@@ -517,6 +531,7 @@ func (s *ScreenType) FromMap(m map[string]interface{}) bool {
|
||||
quickSetStr(&s.Name, m, "name")
|
||||
quickSetInt64(&s.ScreenIdx, m, "screenidx")
|
||||
quickSetJson(&s.ScreenOpts, m, "screenopts")
|
||||
quickSetJson(&s.ScreenViewOpts, m, "screenviewopts")
|
||||
quickSetStr(&s.OwnerId, m, "ownerid")
|
||||
quickSetStr(&s.ShareMode, m, "sharemode")
|
||||
quickSetNullableJson(&s.WebShareOpts, m, "webshareopts")
|
||||
|
||||
@@ -69,6 +69,29 @@ func (update *ModelUpdate) Clean() {
|
||||
update.ClientData = update.ClientData.Clean()
|
||||
}
|
||||
|
||||
func (update *ModelUpdate) UpdateScreen(newScreen *ScreenType) {
|
||||
if newScreen == nil {
|
||||
return
|
||||
}
|
||||
for idx, screen := range update.Screens {
|
||||
if screen.ScreenId == newScreen.ScreenId {
|
||||
update.Screens[idx] = newScreen
|
||||
return
|
||||
}
|
||||
}
|
||||
update.Screens = append(update.Screens, newScreen)
|
||||
}
|
||||
|
||||
// only sets InfoError if InfoError is not already set
|
||||
func (update *ModelUpdate) AddInfoError(errStr string) {
|
||||
if update.Info == nil {
|
||||
update.Info = &InfoMsgType{}
|
||||
}
|
||||
if update.Info.InfoError == "" {
|
||||
update.Info.InfoError = errStr
|
||||
}
|
||||
}
|
||||
|
||||
type RemoteViewType struct {
|
||||
RemoteShowAll bool `json:"remoteshowall,omitempty"`
|
||||
PtyRemoteId string `json:"ptyremoteid,omitempty"`
|
||||
@@ -219,7 +242,7 @@ func (bus *UpdateBus) SendScreenUpdate(screenId string, update UpdatePacket) {
|
||||
|
||||
func MakeSessionsUpdateForRemote(sessionId string, ri *RemoteInstance) []*SessionType {
|
||||
return []*SessionType{
|
||||
&SessionType{
|
||||
{
|
||||
SessionId: sessionId,
|
||||
Remotes: []*RemoteInstance{ri},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user