From 58a8d02f9dcc61245d6e6e50fa93e58c3dea1b27 Mon Sep 17 00:00:00 2001 From: sawka Date: Thu, 2 Mar 2023 23:25:45 -0800 Subject: [PATCH] can preview lines inside of history view. also implement view in context --- src/history.tsx | 139 ++++++++++++++++++++++++++++++++++++++++++------ src/model.ts | 104 ++++++++++++++++++++++++++++++++---- src/sh2.less | 49 +++++++++++++++-- src/term.ts | 9 +++- src/types.ts | 4 +- 5 files changed, 272 insertions(+), 33 deletions(-) diff --git a/src/history.tsx b/src/history.tsx index c7040882..3aa7413f 100644 --- a/src/history.tsx +++ b/src/history.tsx @@ -5,15 +5,18 @@ import {If, For, When, Otherwise, Choose} from "tsx-control-statements/component import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import cn from "classnames"; -import {GlobalModel, GlobalCommandRunner} from "./model"; -import {HistoryItem, RemotePtrType} from "./types"; +import {GlobalModel, GlobalCommandRunner, Cmd} from "./model"; +import {HistoryItem, RemotePtrType, LineType, CmdDataType} from "./types"; import dayjs from "dayjs"; import localizedFormat from 'dayjs/plugin/localizedFormat'; import {Line} from "./linecomps"; dayjs.extend(localizedFormat) -const PageSize = 50; +type OV = mobx.IObservableValue; +type OArr = mobx.IObservableArray; +type OMap = mobx.ObservableMap; +type CV = mobx.IComputedValue; function isBlank(s : string) { return (s == null || s == ""); @@ -61,6 +64,10 @@ function formatSSName(snames : Record, scrnames : Record { + tableRef : React.RefObject = React.createRef(); + tableWidth : OV = mobx.observable.box(0, {name: "tableWidth"}); + tableRszObs : ResizeObserver; + @boundMethod clickCloseHandler() : void { GlobalModel.historyViewModel.closeView(); @@ -115,7 +122,7 @@ class HistoryView extends React.Component<{}, {}> { return; } else { - for (let i=0; i { GlobalModel.historyViewModel.setActiveItem(historyId); } } + + checkWidth() { + if (this.tableRef.current != null) { + mobx.action(() => { + this.tableWidth.set(this.tableRef.current.offsetWidth); + })(); + } + } + + @boundMethod + handleTableResize() { + this.checkWidth(); + } + + componentDidMount() { + if (this.tableRef.current != null) { + this.tableRszObs = new ResizeObserver(this.handleTableResize.bind(this)); + this.tableRszObs.observe(this.tableRef.current); + } + this.checkWidth(); + } + + componentWillUnmount() { + if (this.tableRszObs != null) { + this.tableRszObs.disconnect(); + } + } + + componentDidUpdate() { + this.checkWidth(); + } render() { let isHidden = (GlobalModel.activeMainView.get() != "history"); @@ -150,11 +188,7 @@ class HistoryView extends React.Component<{}, {}> { let snames = GlobalModel.getSessionNames(); let rnames = GlobalModel.getRemoteNames(); let scrnames = GlobalModel.getScreenNames(); - let hasMore = false; - if (items.length > PageSize) { - items = items.slice(0, PageSize); - hasMore = true; - } + let hasMore = hvm.hasMore.get(); let offset = hvm.offset.get(); let numSelected = hvm.selectedItems.size; let controlCheckboxIcon = "fa-sharp fa-regular fa-square"; @@ -164,7 +198,12 @@ class HistoryView extends React.Component<{}, {}> { if (numSelected > 0 && numSelected == items.length) { controlCheckboxIcon = "fa-sharp fa-regular fa-square-check"; } - let activeItem = hvm.activeItem.get(); + let activeItemId = hvm.activeItem.get(); + let activeItem = hvm.getHistoryItemById(activeItemId); + let activeLine : LineType = null; + if (activeItem != null) { + activeLine = hvm.getLineById(activeItem.lineid); + } return (
@@ -196,7 +235,7 @@ class HistoryView extends React.Component<{}, {}> {
- +
@@ -221,13 +260,13 @@ class HistoryView extends React.Component<{}, {}> { {formatRemoteName(rnames, item.remote)} - + - @@ -244,5 +283,75 @@ class HistoryView extends React.Component<{}, {}> { } } +class LineContainer extends React.Component<{historyId : string, width : number}, {}> { + line : LineType; + cmd : Cmd; + historyItem : HistoryItem; + visible : OV = mobx.observable.box(true); + overrideCollapsed : OV = mobx.observable.box(false); + + constructor(props : any) { + super(props); + let hvm = GlobalModel.historyViewModel; + this.historyItem = hvm.getHistoryItemById(props.historyId); + if (this.historyItem == null) { + return; + } + this.line = hvm.getLineById(this.historyItem.lineid); + this.cmd = hvm.getCmdById(this.historyItem.cmdid); + } + + @boundMethod + handleHeightChange(lineNum : number, newHeight : number, oldHeight : number) : void { + return; + } + + @boundMethod + viewInContext() { + let screen = GlobalModel.getScreenById(this.historyItem.sessionid, this.historyItem.screenid); + if (screen == null) { + return null; + } + GlobalModel.historyViewModel.closeView(); + GlobalCommandRunner.lineView(screen.sessionId, screen.screenId, this.line.linenum); + } + + render() { + let hvm = GlobalModel.historyViewModel; + if (this.historyItem == null || this.props.width == 0) { + return null; + } + if (this.line == null) { + return
[no line data]
; + } + let width = this.props.width; + width = width - 50; + if (width < 400) { + width = 400; + } + let session = GlobalModel.getSessionById(this.historyItem.sessionid); + let screen = GlobalModel.getScreenById(this.historyItem.sessionid, this.historyItem.screenid); + let ssStr = ""; + let canViewInContext = false; + if (session != null && screen != null) { + ssStr = sprintf("#%s[%s]", session.name.get(), screen.name.get()); + canViewInContext = true; + } + return ( +
+ +
+
{ssStr}
+
+
+ +
+ + +
+ ); + } +} + export {HistoryView}; diff --git a/src/model.ts b/src/model.ts index d7c27f5f..40e58663 100644 --- a/src/model.ts +++ b/src/model.ts @@ -163,7 +163,6 @@ class Cmd { remoteId : string; cmdId : string; data : OV; - watching : boolean = false; constructor(cmd : CmdDataType) { this.sessionId = cmd.sessionid; @@ -1639,17 +1638,47 @@ type LineFocusType = { class SpecialHistoryViewLineContainer { historyItem : HistoryItem; renderer : RendererModel; + cmd : Cmd; constructor(hitem : HistoryItem) { this.historyItem = hitem; } getCmd(line : LineType) : Cmd { - return null; + if (this.cmd == null) { + this.cmd = GlobalModel.historyViewModel.getCmdById(line.cmdid); + } + return this.cmd; } loadTerminalRenderer(elem : Element, line : LineType, cmd : Cmd, width : number) : void { this.unloadRenderer(null); + let cmdId = cmd.cmdId; + let termWrap = this.getRenderer(cmdId); + if (termWrap != null) { + console.log("term-wrap already exists for", line.windowid, cmdId); + return; + } + let cols = windowWidthToCols(width, GlobalModel.termFontSize.get()); + let usedRows = GlobalModel.getTUR(line.sessionid, cmdId, cols); + if (line.contentheight != null && line.contentheight != -1) { + usedRows = line.contentheight; + } + let termContext = {sessionId: line.sessionid, screenId: "(historyview)", windowId: line.windowid, cmdId: cmdId, lineId : line.lineid, lineNum: line.linenum}; + termWrap = new TermWrap(elem, { + termContext: termContext, + usedRows: usedRows, + termOpts: cmd.getTermOpts(), + winSize: {height: 0, width: width}, + dataHandler: null, + focusHandler: null, + isRunning: cmd.isRunning(), + customKeyHandler: null, + fontSize: GlobalModel.termFontSize.get(), + noSetTUR: true, + }); + this.renderer = termWrap; + return; } loadImageRenderer(imageDivElem : any, line : LineType, cmd : Cmd) : ImageRendererModel { @@ -1719,12 +1748,17 @@ const HistoryPageSize = 50; class HistoryViewModel { items : OArr = mobx.observable.array([], {name: "HistoryItems"}); + hasMore : OV = mobx.observable.box(false, {name: "historyview-hasmore"}); offset : OV = mobx.observable.box(0, {name: "historyview-offset"}); searchText : OV = mobx.observable.box("", {name: "historyview-searchtext"}); activeSearchText : string = null; selectedItems : OMap = mobx.observable.map({}, {name: "historyview-selectedItems"}); deleteActive : OV = mobx.observable.box(false, {name: "historyview-deleteActive"}); activeItem : OV = mobx.observable.box(null, {name: "historyview-activeItem"}); + + historyItemLines : LineType[] = []; + historyItemCmds : CmdDataType[] = []; + specialLineContainer : SpecialHistoryViewLineContainer; constructor() { @@ -1736,19 +1770,50 @@ class HistoryViewModel { })(); } + getLineById(lineId : string) : LineType { + if (isBlank(lineId)) { + return null; + } + for (let i=0; i { if (hitem == null) { this.activeItem.set(null); @@ -1795,9 +1860,12 @@ class HistoryViewModel { submitSearch() : void { mobx.action(() => { + this.hasMore.set(false); this.items.replace([]); this.offset.set(0); this.activeSearchText = this.searchText.get(); + this.historyItemLines = []; + this.historyItemCmds = []; })(); GlobalCommandRunner.historyView({offset: 0, searchText: this.activeSearchText}); } @@ -1813,8 +1881,11 @@ class HistoryViewModel { showHistoryView(data : HistoryViewDataType) : void { mobx.action(() => { GlobalModel.activeMainView.set("history"); + this.hasMore.set(data.hasmore); this.items.replace(data.items || []); this.offset.set(data.offset); + this.historyItemLines = (data.lines ?? []); + this.historyItemCmds = (data.cmds ?? []); this.selectedItems.clear(); })(); } @@ -2890,6 +2961,17 @@ class CommandRunner { GlobalModel.submitCommand("screen", null, [screen], {"nohist": "1"}, false); } + lineView(sessionId : string, screenId : string, lineNum : number) { + let screen = GlobalModel.getScreenById(sessionId, screenId); + if (screen != null) { + let sw = screen.getActiveSW(); + if (sw != null) { + sw.setAnchorFields(lineNum, 0, "line:view"); + } + } + GlobalModel.submitCommand("line", "view", [sessionId, screenId, String(lineNum)], {"nohist": "1"}, false); + } + createNewSession() { GlobalModel.submitCommand("session", "open", null, {"nohist": "1"}, false); } diff --git a/src/sh2.less b/src/sh2.less index 091a20bc..63a9ec5a 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -307,8 +307,44 @@ body::-webkit-scrollbar { tr.active-history-item { td { - padding: 10px; - background-color: blue; + padding-right: 10px; + + .line-container { + padding: 0px 10px 10px 10px; + overflow-x: auto; + background-color: black; + } + + .line-context { + .mono-font(12px); + margin-left: 20px; + margin-bottom: 10px; + margin-top: 10px; + display: flex; + flex-direction: row; + + .vic-btn { + cursor: pointer; + color: #ccc; + + &:hover { + color: white; + } + } + } + + .no-line-context { + height: 10px; + } + + .line-container.no-line { + .mono-font(12px); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 20px; + } } } @@ -400,13 +436,18 @@ body::-webkit-scrollbar { .mono-font(12px); color: white; background-color: black; - flex: 1 0 auto; + flex: 1 0 0; padding-left: 20px; border-radius: 3px; white-space: pre; max-height: 64px; - overflow-y: auto; cursor: pointer; + min-width: 300px; + overflow-y: auto; + + .cmdstr-content { + overflow-x: auto; + } } } } diff --git a/src/term.ts b/src/term.ts index e3707631..fbb964e6 100644 --- a/src/term.ts +++ b/src/term.ts @@ -29,7 +29,8 @@ type TermWrapOpts = { dataHandler? : (data : string, termWrap : TermWrap) => void, isRunning : boolean, customKeyHandler? : (event : any, termWrap : TermWrap) => boolean, - fontSize: number, + fontSize : number, + noSetTUR? : boolean, }; // cmd-instance @@ -50,6 +51,7 @@ class TermWrap { focusHandler : (focus : boolean) => void; isRunning : boolean; fontSize : number; + noSetTUR : boolean; constructor(elem : Element, opts : TermWrapOpts) { opts = opts ?? ({} as any); @@ -60,6 +62,7 @@ class TermWrap { this.focusHandler = opts.focusHandler; this.isRunning = opts.isRunning; this.fontSize = opts.fontSize; + this.noSetTUR = !!opts.noSetTUR; if (this.flexRows) { this.atRowMax = false; this.usedRows = mobx.observable.box(opts.usedRows ?? (opts.isRunning ? 1 : 0), {name: "term-usedrows"}); @@ -218,7 +221,9 @@ class TermWrap { return; } this.usedRows.set(tur); - GlobalModel.setTUR(termContext, this.termSize, tur); + if (!this.noSetTUR) { + GlobalModel.setTUR(termContext, this.termSize, tur); + } })(); } diff --git a/src/types.ts b/src/types.ts index 929cab52..143470c0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -290,9 +290,11 @@ type ModelUpdateType = { }; type HistoryViewDataType = { - totalcount : number, offset : number, items : HistoryItem[], + lines : LineType[], + cmds : CmdDataType[], + hasmore : boolean, }; type BookmarkType = {
this.activateItem(item.historyid)}> - {item.cmdstr} +
{item.cmdstr}
- + +