Compare commits

..

11 Commits

Author SHA1 Message Date
sawka db01ee90ee fix history keybinding, make mainview command consistent 2024-03-13 18:43:53 -07:00
sawka deee5b77a7 fix hide (use app.hid(), not window.hide() 2024-03-13 18:32:57 -07:00
MrStashley 7ee5cb58ea added hide label 2024-03-13 16:39:56 -07:00
MrStashley 3490dcbedb addressed feedback 2024-03-13 16:32:24 -07:00
MrStashley 8884d5f6d3 added new keybinding for minimize 2024-03-13 16:09:44 -07:00
MrStashley 031469bd22 demo idea of changing hide do cmd+m, can remove if we decide we don't like it 2024-03-13 16:04:09 -07:00
MrStashley 5737425463 addressed feedback 2024-03-13 16:01:53 -07:00
MrStashley 54295ff54d addressed review comments 2024-03-13 14:14:47 -07:00
MrStashley 5c9a8201b5 added focus cmd input, added sleep 2024-03-13 14:01:55 -07:00
MrStashley 4ece667aaa added mainview slashcommand 2024-03-13 13:42:39 -07:00
MrStashley dd24323296 first pass of slash commands 2024-03-12 18:40:43 -07:00
46 changed files with 1374 additions and 725 deletions
+1 -2
View File
@@ -41,7 +41,6 @@ jobs:
cd scripthaus;
go get ./...;
CGO_ENABLED=1 go build -o scripthaus cmd/main.go
echo $PWD >> $GITHUB_PATH
- uses: actions/setup-node@v4
with:
node-version: ${{env.NODE_VERSION}}
@@ -54,7 +53,7 @@ jobs:
- name: Install Yarn Dependencies
run: yarn --frozen-lockfile
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
run: scripthaus run ${{ matrix.scripthaus }}
run: ./scripthaus/scripthaus run ${{ matrix.scripthaus }}
env:
GOARCH: ${{ matrix.arch }}
CSC_LINK: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_CERTIFICATE}}
+1 -32
View File
@@ -47,32 +47,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Checkout Scripthaus (Go only)
if: matrix.language == 'go'
uses: actions/checkout@v4
with:
repository: scripthaus-dev/scripthaus
path: scripthaus
- name: Setup Go (Go only)
uses: actions/setup-go@v5
if: matrix.language == 'go'
with:
go-version: stable
cache-dependency-path: |
wavesrv/go.sum
waveshell/go.sum
scripthaus/go.sum
- name: Install Scripthaus (Go only)
if: matrix.language == 'go'
run: |
go work use ./scripthaus;
cd scripthaus;
go get ./...;
CGO_ENABLED=1 go build -o scripthaus cmd/main.go
echo $PWD >> $GITHUB_PATH
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
@@ -87,14 +61,9 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild (not Go)
if: matrix.language != 'go'
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Build (Go only)
if: matrix.language == 'go'
run: scripthaus run build-backend
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Binary file not shown.
Binary file not shown.
+2 -3
View File
@@ -3,7 +3,7 @@
:root {
/*
* term colors (16 + 6) form the base terminal theme
* term colors (16 + 5) form the base terminal theme
* for consistency these colors should be used by plugins/applications
*/
--term-black: #000000;
@@ -27,6 +27,5 @@
--term-cmdtext: #ffffff;
--term-foreground: #d3d7cf;
--term-background: #000000;
--term-selection-background: #ffffff90;
--term-cursor-accent: #000000;
--term-selection-background: #ffffff40;
}
+1 -2
View File
@@ -6,6 +6,5 @@
--term-foreground: #000000;
--term-background: #fefefe;
--term-cmdtext: #000000;
--term-selection-background: #00000040;
--term-cursor-accent: #000000;
--term-selection-background: #00000018;
}
+1 -1
View File
@@ -193,7 +193,7 @@ class BookmarksView extends React.Component<{}, {}> {
let bookmarks = GlobalModel.bookmarksModel.bookmarks;
let bookmark: BookmarkType = null;
return (
<MainView className="bookmarks-view" title="Bookmarks" onClose={this.handleClose}>
<MainView viewName="bookmarks" title="Bookmarks" onClose={this.handleClose}>
<div className="bookmarks-list">
<For index="idx" each="bookmark" of={bookmarks}>
<Bookmark key={bookmark.bookmarkid} bookmark={bookmark} />
@@ -87,7 +87,6 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
const availableFontFamilies: DropdownItem[] = [];
availableFontFamilies.push({ label: "JetBrains Mono", value: "JetBrains Mono" });
availableFontFamilies.push({ label: "Hack", value: "Hack" });
availableFontFamilies.push({ label: "Fira Code", value: "Fira Code" });
return availableFontFamilies;
}
+1 -1
View File
@@ -44,7 +44,7 @@ class AlertModal extends React.Component<{}, {}> {
<Markdown text={message?.message ?? ""} extraClassName="bottom-margin" />
</If>
<If condition={!message?.markdown}>{message?.message}</If>
<If condition={message?.confirmflag}>
<If condition={message.confirmflag}>
<Checkbox
onChange={this.handleDontShowAgain}
label={"Don't show me this again"}
+5 -14
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { GlobalModel } from "@/models";
import { Choose, When, If } from "tsx-control-statements/components";
import { Modal, PasswordField, TextField, Markdown, Checkbox } from "@/elements";
import { Modal, PasswordField, Markdown, Checkbox } from "@/elements";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
import "./userinput.less";
@@ -82,17 +82,8 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
</If>
<If condition={!userInputRequest.markdown}>{userInputRequest.querytext}</If>
</div>
<If condition={userInputRequest.responsetype == "text"}>
<If condition={userInputRequest.publictext}>
<TextField
onChange={setResponseText}
value={responseText}
maxLength={400}
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
</If>
<If condition={!userInputRequest.publictext}>
<Choose>
<When condition={userInputRequest.responsetype == "text"}>
<PasswordField
onChange={setResponseText}
value={responseText}
@@ -100,8 +91,8 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
</If>
</If>
</When>
</Choose>
</div>
<If condition={userInputRequest.checkboxmsg != ""}>
<Checkbox
-1
View File
@@ -126,7 +126,6 @@
&:hover .line-actions {
background-color: var(--line-actions-bg-color);
backdrop-filter: blur(8px);
.line-icon {
visibility: visible;
}
+276
View File
@@ -0,0 +1,276 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { debounce } from "throttle-debounce";
import * as util from "@/util/util";
import { GlobalModel } from "@/models";
class SimpleBlobRendererModel {
context: RendererContext;
opts: RendererOpts;
isDone: OV<boolean>;
api: RendererModelContainerApi;
savedHeight: number;
loading: OV<boolean>;
loadError: OV<string> = mobx.observable.box(null, {
name: "renderer-loadError",
});
lineState: LineStateType;
ptyData: PtyDataType;
ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>;
dataBlob: Blob;
readOnly: boolean;
notFound: boolean;
initialize(params: RendererModelInitializeParams): void {
this.loading = mobx.observable.box(true, { name: "renderer-loading" });
this.isDone = mobx.observable.box(params.isDone, {
name: "renderer-isDone",
});
this.context = params.context;
this.opts = params.opts;
this.api = params.api;
this.lineState = params.lineState;
this.savedHeight = params.savedHeight;
this.ptyDataSource = params.ptyDataSource;
if (this.isDone.get()) {
setTimeout(() => this.reload(0), 10);
}
}
dispose(): void {
return;
}
giveFocus(): void {
return;
}
updateOpts(update: RendererOptsUpdate): void {
Object.assign(this.opts, update);
}
updateHeight(newHeight: number): void {
if (this.savedHeight != newHeight) {
this.savedHeight = newHeight;
this.api.saveHeight(newHeight);
}
}
setIsDone(): void {
if (this.isDone.get()) {
return;
}
mobx.action(() => {
this.isDone.set(true);
})();
this.reload(0);
}
reload(delayMs: number): void {
mobx.action(() => {
this.loading.set(true);
})();
if (delayMs == 0) {
this.reload_noDelay();
} else {
setTimeout(() => {
this.reload_noDelay();
}, delayMs);
}
}
reload_noDelay(): void {
let source = this.lineState["prompt:source"] || "pty";
if (source == "pty") {
this.reloadPtyData();
} else if (source == "file") {
this.reloadFileData();
} else {
mobx.action(() => {
this.loadError.set("error: invalid load source: " + source);
})();
}
}
reloadFileData(): void {
// todo add file methods to API, so we don't have a GlobalModel dependency here!
let path = this.lineState["prompt:file"];
if (util.isBlank(path)) {
mobx.action(() => {
this.loadError.set("renderer has file source, but no prompt:file specified");
})();
return;
}
let rtnp = GlobalModel.readRemoteFile(this.context.screenId, this.context.lineId, path);
rtnp.then((file) => {
this.notFound = (file as any).notFound;
this.readOnly = (file as any).readOnly;
this.dataBlob = file;
mobx.action(() => {
this.loading.set(false);
this.loadError.set(null);
})();
}).catch((e) => {
mobx.action(() => {
this.loadError.set("error loading file data: " + e);
})();
});
}
reloadPtyData(): void {
this.readOnly = true;
let rtnp = this.ptyDataSource(this.context);
if (rtnp == null) {
console.log("no promise returned from ptyDataSource (simplerenderer)", this.context);
return;
}
rtnp.then((ptydata) => {
this.ptyData = ptydata;
this.dataBlob = new Blob([this.ptyData.data]);
mobx.action(() => {
this.loading.set(false);
this.loadError.set(null);
})();
}).catch((e) => {
mobx.action(() => {
this.loadError.set("error loading data: " + e);
})();
});
}
receiveData(pos: number, data: Uint8Array, reason?: string): void {
// this.dataBuf.receiveData(pos, data, reason);
}
}
@mobxReact.observer
class SimpleBlobRenderer extends React.Component<
{
rendererContainer: RendererContainerType;
lineId: string;
plugin: RendererPluginType;
onHeightChange: () => void;
initParams: RendererModelInitializeParams;
scrollToBringIntoViewport: () => void;
isSelected: boolean;
shouldFocus: boolean;
},
{}
> {
model: SimpleBlobRendererModel;
wrapperDivRef: React.RefObject<any> = React.createRef();
rszObs: ResizeObserver;
updateHeight_debounced: (newHeight: number) => void;
constructor(props: any) {
super(props);
let { rendererContainer, lineId, plugin, initParams } = this.props;
this.model = new SimpleBlobRendererModel();
this.model.initialize(initParams);
rendererContainer.registerRenderer(lineId, this.model);
this.updateHeight_debounced = debounce(1000, this.updateHeight.bind(this));
}
updateHeight(newHeight: number): void {
this.model.updateHeight(newHeight);
}
handleResize(entries: ResizeObserverEntry[]): void {
if (this.model.loading.get()) {
return;
}
if (this.props.onHeightChange) {
this.props.onHeightChange();
}
if (!this.model.loading.get() && this.wrapperDivRef.current != null) {
let height = this.wrapperDivRef.current.offsetHeight;
this.updateHeight_debounced(height);
}
}
checkRszObs() {
if (this.rszObs != null) {
return;
}
if (this.wrapperDivRef.current == null) {
return;
}
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
this.rszObs.observe(this.wrapperDivRef.current);
}
componentDidMount() {
this.checkRszObs();
}
componentWillUnmount() {
let { rendererContainer, lineId } = this.props;
rendererContainer.unloadRenderer(lineId);
if (this.rszObs != null) {
this.rszObs.disconnect();
this.rszObs = null;
}
}
componentDidUpdate() {
this.checkRszObs();
}
render() {
let { plugin } = this.props;
let model = this.model;
if (model.loadError.get() != null) {
let errorText = model.loadError.get();
let height = this.model.savedHeight;
return (
<div ref={this.wrapperDivRef} style={{ minHeight: height, fontSize: model.opts.termFontSize }}>
<div className="load-error-text">ERROR: {errorText}</div>
</div>
);
}
if (model.loading.get()) {
let height = this.model.savedHeight;
return (
<div
ref={this.wrapperDivRef}
className="renderer-loading"
style={{ minHeight: height, fontSize: model.opts.termFontSize }}
>
loading content <i className="fa fa-ellipsis fa-fade" />
</div>
);
}
let Comp = plugin.simpleComponent;
if (Comp == null) {
<div ref={this.wrapperDivRef}>(no component found in plugin)</div>;
}
let { festate, cmdstr, exitcode } = this.props.initParams.rawCmd;
return (
<div ref={this.wrapperDivRef} className="sr-wrapper">
<Comp
cwd={festate.cwd}
cmdstr={cmdstr}
exitcode={exitcode}
data={model.dataBlob as ExtBlob}
readOnly={model.readOnly}
notFound={model.notFound}
lineState={model.lineState}
context={model.context}
opts={model.opts}
savedHeight={model.savedHeight}
scrollToBringIntoViewport={this.props.scrollToBringIntoViewport}
isSelected={this.props.isSelected}
shouldFocus={this.props.shouldFocus}
rendererApi={model.api}
/>
</div>
);
}
}
export { SimpleBlobRendererModel, SimpleBlobRenderer };
+1 -6
View File
@@ -308,21 +308,16 @@ function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWillFrameNa
// only use this handler to process iframe events (non-iframe events go to shNavHandler)
return;
}
event.preventDefault();
const url = event.url;
console.log(`frame-navigation url=${url} frame=${event.frame.name}`);
if (event.frame.name == "webview") {
// "webview" links always open in new window
// this will *not* effect the initial load because srcdoc does not count as an electron navigation
console.log("open external, frameNav", url);
event.preventDefault();
electron.shell.openExternal(url);
return;
}
if (event.frame.name == "pdfview" && url.startsWith("blob:file:///")) {
// allowed
return;
}
event.preventDefault();
console.log("frame navigation canceled");
}
+1 -1
View File
@@ -206,7 +206,7 @@ class BookmarksModel {
}
handleDocKeyDown(e: any): void {
const waveEvent = adaptFromReactOrNativeKeyEvent(e);
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
if (this.editingBookmark.get() != null) {
-10
View File
@@ -3,7 +3,6 @@
import * as mobx from "mobx";
import { Model } from "./model";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
class ClientSettingsViewModel {
globalModel: Model;
@@ -22,15 +21,6 @@ class ClientSettingsViewModel {
this.globalModel.activeMainView.set("clientsettings");
})();
}
handleDocKeyDown(e: any): void {
const waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
this.closeView();
return;
}
}
}
export { ClientSettingsViewModel };
-4
View File
@@ -304,10 +304,6 @@ class CommandRunner {
GlobalModel.clientSettingsViewModel.showClientSettingsView();
}
syncShellState() {
GlobalModel.submitCommand("sync", null, null, { nohist: "1" }, false);
}
historyView(params: HistorySearchParams) {
let kwargs = { nohist: "1" };
kwargs["offset"] = String(params.offset);
-10
View File
@@ -3,7 +3,6 @@
import * as mobx from "mobx";
import { Model } from "./model";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
class ConnectionsViewModel {
globalModel: Model;
@@ -22,15 +21,6 @@ class ConnectionsViewModel {
this.globalModel.activeMainView.set("connections");
})();
}
handleDocKeyDown(e: any): void {
const waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
this.closeView();
return;
}
}
}
export { ConnectionsViewModel };
+1 -1
View File
@@ -291,7 +291,7 @@ class HistoryViewModel {
}
handleDocKeyDown(e: any): void {
const waveEvent = adaptFromReactOrNativeKeyEvent(e);
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
this.closeView();
+1 -2
View File
@@ -18,11 +18,10 @@ class ModalsModel {
}
}
popModal(callback?: () => void) {
popModal() {
mobx.action(() => {
this.store.pop();
})();
callback && callback();
}
}
+36 -15
View File
@@ -357,6 +357,7 @@ class Model {
cancelAlert(): void {
mobx.action(() => {
this.alertMessage.set(null);
this.modalsModel.popModal();
})();
if (this.alertPromiseResolver != null) {
this.alertPromiseResolver(false);
@@ -477,7 +478,7 @@ class Model {
if (this.alertMessage.get() != null) {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
this.modalsModel.popModal(() => this.cancelAlert());
this.cancelAlert();
return;
}
if (checkKeyPressed(waveEvent, "Enter")) {
@@ -487,10 +488,6 @@ class Model {
}
return;
}
if (checkKeyPressed(waveEvent, "Escape") && this.modalsModel.store.length > 0) {
this.modalsModel.popModal();
return;
}
if (this.activeMainView.get() == "bookmarks") {
this.bookmarksModel.handleDocKeyDown(e);
}
@@ -498,10 +495,10 @@ class Model {
this.historyViewModel.handleDocKeyDown(e);
}
if (this.activeMainView.get() == "connections") {
this.connectionViewModel.handleDocKeyDown(e);
this.historyViewModel.handleDocKeyDown(e);
}
if (this.activeMainView.get() == "clientsettings") {
this.clientSettingsViewModel.handleDocKeyDown(e);
this.historyViewModel.handleDocKeyDown(e);
} else {
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
@@ -509,6 +506,9 @@ class Model {
this.showSessionView();
return;
}
if (this.clearModals()) {
return;
}
const inputModel = this.inputModel;
inputModel.toggleInfoMsg();
if (inputModel.inputMode.get() != null) {
@@ -628,6 +628,33 @@ class Model {
return screen.getTermWrap(line.lineid);
}
clearModals(): boolean {
let didSomething = false;
mobx.action(() => {
if (this.screenSettingsModal.get()) {
this.screenSettingsModal.set(null);
didSomething = true;
}
if (this.sessionSettingsModal.get()) {
this.sessionSettingsModal.set(null);
didSomething = true;
}
if (this.screenSettingsModal.get()) {
this.screenSettingsModal.set(null);
didSomething = true;
}
if (this.clientSettingsModal.get()) {
this.clientSettingsModal.set(false);
didSomething = true;
}
if (this.lineSettingsModal.get()) {
this.lineSettingsModal.set(null);
didSomething = true;
}
})();
return didSomething;
}
restartWaveSrv(): void {
getApi().restartWaveSrv();
}
@@ -1050,9 +1077,6 @@ class Model {
this.activeMainView.set("session");
this.deactivateScreenLines();
this.ws.watchScreen(newActiveSessionId, newActiveScreenId);
setTimeout(() => {
GlobalCommandRunner.syncShellState();
}, 100);
}
} else {
console.warn("unknown update", genUpdate);
@@ -1550,15 +1574,12 @@ class Model {
return remote.remotecanonicalname;
}
readRemoteFile(screenId: string, lineId: string, path: string, mimetype?: string): Promise<ExtFile> {
const urlParams: Record<string, string> = {
readRemoteFile(screenId: string, lineId: string, path: string): Promise<ExtFile> {
const urlParams = {
screenid: screenId,
lineid: lineId,
path: path,
};
if (mimetype != null) {
urlParams["mimetype"] = mimetype;
}
const usp = new URLSearchParams(urlParams);
const url = new URL(this.getBaseHostPort() + "/api/read-file?" + usp.toString());
const fetchHeaders = this.getFetchHeaders();

Some files were not shown because too many files have changed in this diff Show More