mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
Merge branch 'main' into evan/global-hotkey
This commit is contained in:
commit
4273b4e2a7
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1 +1 @@
|
|||||||
* text=lf
|
* text=auto
|
31
.github/ISSUE_TEMPLATE/bug-report.md
vendored
31
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug Report
|
|
||||||
about: Create a bug report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. MacOS/Linux, x64 or arm64]
|
|
||||||
- Version [e.g. v0.5.0]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
87
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
87
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Create a bug report to help us improve.
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug", "triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Bug description
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Current Behavior
|
||||||
|
description: A concise description of what you're experiencing.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: A concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps To Reproduce
|
||||||
|
description: Steps to reproduce the behavior.
|
||||||
|
placeholder: |
|
||||||
|
1. In this environment...
|
||||||
|
2. With this config...
|
||||||
|
3. Run '...'
|
||||||
|
4. See error...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Environment details
|
||||||
|
|
||||||
|
We require that you provide us the version of Wave you're running so we can track issues across versions. To find the Wave version, go to the app menu (this always visible on macOS, for Windows and Linux, click the `...` button) and navigate to `Wave -> About Wave Terminal`. This will bring up the About modal. Copy the client version and paste it below.
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Wave Version
|
||||||
|
description: The version of Wave you are running
|
||||||
|
placeholder: v0.8.8
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: OS
|
||||||
|
description: The name and version of the operating system of the computer where you are running Wave
|
||||||
|
placeholder: macOS 15.0
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Architecture
|
||||||
|
description: The architecture of the computer where you are running Wave
|
||||||
|
options:
|
||||||
|
- arm64
|
||||||
|
- x64
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Extra details
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: |
|
||||||
|
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||||
|
|
||||||
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Questionnaire
|
||||||
|
description: "If you feel up to the challenge, please check one of the boxes below:"
|
||||||
|
options:
|
||||||
|
- label: I'm interested in fixing this myself but don't know where to start
|
||||||
|
required: false
|
||||||
|
- label: I would like to fix and I have a solution
|
||||||
|
required: false
|
||||||
|
- label: I don't have time to fix this right now, but maybe later
|
||||||
|
required: false
|
14
.github/ISSUE_TEMPLATE/feature-request.md
vendored
14
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature Request
|
|
||||||
about: Suggest a new idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
26
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggest a new idea for this project.
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels: ["enhancement", "triage"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Feature description
|
||||||
|
description: Describe the issue in detail and why we should add it. To help us out, please poke through our issue tracker and make sure it's not a duplicate issue. Ex. As a user, I can do [...]
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Implementation Suggestion
|
||||||
|
description: If you have any suggestions on how to design this feature, list them here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: |
|
||||||
|
Links? References? Anything that will give us more context about how to deliver your feature!
|
||||||
|
|
||||||
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
|
validations:
|
||||||
|
required: false
|
@ -66,6 +66,7 @@ const config = {
|
|||||||
Keywords: "developer;terminal;emulator;",
|
Keywords: "developer;terminal;emulator;",
|
||||||
category: "Development;Utility;",
|
category: "Development;Utility;",
|
||||||
},
|
},
|
||||||
|
executableArgs: ["--enable-features", "UseOzonePlatform", "--ozone-platform-hint", "auto"], // Hint Electron to use Ozone abstraction layer for native Wayland support
|
||||||
},
|
},
|
||||||
deb: {
|
deb: {
|
||||||
afterInstall: "build/deb-postinstall.tpl",
|
afterInstall: "build/deb-postinstall.tpl",
|
||||||
|
@ -16,6 +16,13 @@ import * as jotai from "jotai";
|
|||||||
const simpleControlShiftAtom = jotai.atom(false);
|
const simpleControlShiftAtom = jotai.atom(false);
|
||||||
const globalKeyMap = new Map<string, (waveEvent: WaveKeyboardEvent) => boolean>();
|
const globalKeyMap = new Map<string, (waveEvent: WaveKeyboardEvent) => boolean>();
|
||||||
|
|
||||||
|
function getFocusedBlockInActiveTab() {
|
||||||
|
const activeTabId = globalStore.get(atoms.activeTabId);
|
||||||
|
const layoutModel = getLayoutModelForTabById(activeTabId);
|
||||||
|
const focusedNode = globalStore.get(layoutModel.focusedNode);
|
||||||
|
return focusedNode.data?.blockId;
|
||||||
|
}
|
||||||
|
|
||||||
function getSimpleControlShiftAtom() {
|
function getSimpleControlShiftAtom() {
|
||||||
return simpleControlShiftAtom;
|
return simpleControlShiftAtom;
|
||||||
}
|
}
|
||||||
@ -161,12 +168,6 @@ function appHandleKeyDown(waveEvent: WaveKeyboardEvent): boolean {
|
|||||||
const blockId = focusedNode?.data?.blockId;
|
const blockId = focusedNode?.data?.blockId;
|
||||||
if (blockId != null && shouldDispatchToBlock(waveEvent)) {
|
if (blockId != null && shouldDispatchToBlock(waveEvent)) {
|
||||||
const bcm = getBlockComponentModel(blockId);
|
const bcm = getBlockComponentModel(blockId);
|
||||||
if (bcm.openSwitchConnection != null) {
|
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Cmd:g")) {
|
|
||||||
bcm.openSwitchConnection();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const viewModel = bcm?.viewModel;
|
const viewModel = bcm?.viewModel;
|
||||||
if (viewModel?.keyDownHandler) {
|
if (viewModel?.keyDownHandler) {
|
||||||
const handledByBlock = viewModel.keyDownHandler(waveEvent);
|
const handledByBlock = viewModel.keyDownHandler(waveEvent);
|
||||||
@ -262,6 +263,13 @@ function registerGlobalKeys() {
|
|||||||
switchBlockInDirection(tabId, NavigateDirection.Right);
|
switchBlockInDirection(tabId, NavigateDirection.Right);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
globalKeyMap.set("Cmd:g", () => {
|
||||||
|
const bcm = getBlockComponentModel(getFocusedBlockInActiveTab());
|
||||||
|
if (bcm.openSwitchConnection != null) {
|
||||||
|
bcm.openSwitchConnection();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
for (let idx = 1; idx <= 9; idx++) {
|
for (let idx = 1; idx <= 9; idx++) {
|
||||||
globalKeyMap.set(`Cmd:${idx}`, () => {
|
globalKeyMap.set(`Cmd:${idx}`, () => {
|
||||||
switchTabAbs(idx);
|
switchTabAbs(idx);
|
||||||
@ -282,6 +290,11 @@ function registerGlobalKeys() {
|
|||||||
getApi().registerGlobalWebviewKeys(allKeys);
|
getApi().registerGlobalWebviewKeys(allKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAllGlobalKeyBindings(): string[] {
|
||||||
|
const allKeys = Array.from(globalKeyMap.keys());
|
||||||
|
return allKeys;
|
||||||
|
}
|
||||||
|
|
||||||
// these keyboard events happen *anywhere*, even if you have focus in an input or somewhere else.
|
// these keyboard events happen *anywhere*, even if you have focus in an input or somewhere else.
|
||||||
function handleGlobalWaveKeyboardEvents(waveEvent: WaveKeyboardEvent): boolean {
|
function handleGlobalWaveKeyboardEvents(waveEvent: WaveKeyboardEvent): boolean {
|
||||||
for (const key of globalKeyMap.keys()) {
|
for (const key of globalKeyMap.keys()) {
|
||||||
@ -297,6 +310,7 @@ function handleGlobalWaveKeyboardEvents(waveEvent: WaveKeyboardEvent): boolean {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
appHandleKeyDown,
|
appHandleKeyDown,
|
||||||
|
getAllGlobalKeyBindings,
|
||||||
getSimpleControlShiftAtom,
|
getSimpleControlShiftAtom,
|
||||||
registerControlShiftStateUpdateHandler,
|
registerControlShiftStateUpdateHandler,
|
||||||
registerElectronReinjectKeyHandler,
|
registerElectronReinjectKeyHandler,
|
||||||
|
@ -7,18 +7,38 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
--min-row-width: 35rem;
|
||||||
.dir-table {
|
.dir-table {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 600px;
|
width: 100%;
|
||||||
--col-size-size: 0.2rem;
|
--col-size-size: 0.2rem;
|
||||||
border-radius: 3px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font: var(--base-font);
|
|
||||||
|
&:not([data-scroll-height="0"]) .dir-table-head::after {
|
||||||
|
background: rgb(from var(--block-bg-color) r g b / 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-table-head::after {
|
||||||
|
content: "";
|
||||||
|
z-index: -1;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
.dir-table-head {
|
.dir-table-head {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
|
||||||
.dir-table-head-row {
|
.dir-table-head-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-bottom: 1px solid var(--border-color);
|
min-width: var(--min-row-width);
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
|
||||||
@ -68,10 +88,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dir-table-body {
|
.dir-table-body {
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
|
||||||
.dir-table-body-search-display {
|
.dir-table-body-search-display {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -94,6 +112,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
|
min-width: var(--min-row-width);
|
||||||
|
|
||||||
&.focused {
|
&.focused {
|
||||||
background-color: rgb(from var(--accent-color) r g b / 0.5);
|
background-color: rgb(from var(--accent-color) r g b / 0.5);
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
import { ContextMenuModel } from "@/app/store/contextmenu";
|
import { ContextMenuModel } from "@/app/store/contextmenu";
|
||||||
import { atoms, createBlock, getApi } from "@/app/store/global";
|
import { atoms, createBlock, getApi } from "@/app/store/global";
|
||||||
|
import { FileService } from "@/app/store/services";
|
||||||
import type { PreviewModel } from "@/app/view/preview/preview";
|
import type { PreviewModel } from "@/app/view/preview/preview";
|
||||||
import * as services from "@/store/services";
|
import { checkKeyPressed, isCharacterKeyEvent } from "@/util/keyutil";
|
||||||
import * as keyutil from "@/util/keyutil";
|
import { base64ToString, isBlank } from "@/util/util";
|
||||||
import * as util from "@/util/util";
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
Row,
|
Row,
|
||||||
@ -19,14 +19,11 @@ import {
|
|||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import * as jotai from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
|
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { quote as shellQuote } from "shell-quote";
|
import { quote as shellQuote } from "shell-quote";
|
||||||
|
import { debounce } from "throttle-debounce";
|
||||||
import { OverlayScrollbars } from "overlayscrollbars";
|
|
||||||
|
|
||||||
import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions";
|
|
||||||
import "./directorypreview.less";
|
import "./directorypreview.less";
|
||||||
|
|
||||||
interface DirectoryTableProps {
|
interface DirectoryTableProps {
|
||||||
@ -95,7 +92,7 @@ function getLastModifiedTime(unixMillis: number, column: Column<FileInfo, number
|
|||||||
const iconRegex = /^[a-z0-9- ]+$/;
|
const iconRegex = /^[a-z0-9- ]+$/;
|
||||||
|
|
||||||
function isIconValid(icon: string): boolean {
|
function isIconValid(icon: string): boolean {
|
||||||
if (util.isBlank(icon)) {
|
if (isBlank(icon)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return icon.match(iconRegex) != null;
|
return icon.match(iconRegex) != null;
|
||||||
@ -134,11 +131,11 @@ function DirectoryTable({
|
|||||||
setSelectedPath,
|
setSelectedPath,
|
||||||
setRefreshVersion,
|
setRefreshVersion,
|
||||||
}: DirectoryTableProps) {
|
}: DirectoryTableProps) {
|
||||||
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
|
const fullConfig = useAtomValue(atoms.fullConfigAtom);
|
||||||
const getIconFromMimeType = useCallback(
|
const getIconFromMimeType = useCallback(
|
||||||
(mimeType: string): string => {
|
(mimeType: string): string => {
|
||||||
while (mimeType.length > 0) {
|
while (mimeType.length > 0) {
|
||||||
let icon = fullConfig.mimetypes?.[mimeType]?.icon ?? null;
|
const icon = fullConfig.mimetypes?.[mimeType]?.icon ?? null;
|
||||||
if (isIconValid(icon)) {
|
if (isIconValid(icon)) {
|
||||||
return `fa fa-solid fa-${icon} fa-fw`;
|
return `fa fa-solid fa-${icon} fa-fw`;
|
||||||
}
|
}
|
||||||
@ -149,10 +146,7 @@ function DirectoryTable({
|
|||||||
[fullConfig.mimetypes]
|
[fullConfig.mimetypes]
|
||||||
);
|
);
|
||||||
const getIconColor = useCallback(
|
const getIconColor = useCallback(
|
||||||
(mimeType: string): string => {
|
(mimeType: string): string => fullConfig.mimetypes?.[mimeType]?.color ?? "inherit",
|
||||||
let iconColor = fullConfig.mimetypes?.[mimeType]?.color ?? "inherit";
|
|
||||||
return iconColor;
|
|
||||||
},
|
|
||||||
[fullConfig.mimetypes]
|
[fullConfig.mimetypes]
|
||||||
);
|
);
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
@ -261,8 +255,25 @@ function DirectoryTable({
|
|||||||
return colSizes;
|
return colSizes;
|
||||||
}, [table.getState().columnSizingInfo]);
|
}, [table.getState().columnSizingInfo]);
|
||||||
|
|
||||||
|
const osRef = useRef<OverlayScrollbarsComponentRef>();
|
||||||
|
const bodyRef = useRef<HTMLDivElement>();
|
||||||
|
const [scrollHeight, setScrollHeight] = useState(0);
|
||||||
|
|
||||||
|
const onScroll = useCallback(
|
||||||
|
debounce(2, () => {
|
||||||
|
setScrollHeight(osRef.current.osInstance().elements().viewport.scrollTop);
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className="dir-table" style={{ ...columnSizeVars }}>
|
<OverlayScrollbarsComponent
|
||||||
|
options={{ scrollbars: { autoHide: "leave" } }}
|
||||||
|
events={{ scroll: onScroll }}
|
||||||
|
className="dir-table"
|
||||||
|
style={{ ...columnSizeVars }}
|
||||||
|
ref={osRef}
|
||||||
|
data-scroll-height={scrollHeight}
|
||||||
|
>
|
||||||
<div className="dir-table-head">
|
<div className="dir-table-head">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<div className="dir-table-head-row" key={headerGroup.id}>
|
<div className="dir-table-head-row" key={headerGroup.id}>
|
||||||
@ -295,6 +306,7 @@ function DirectoryTable({
|
|||||||
</div>
|
</div>
|
||||||
{table.getState().columnSizingInfo.isResizingColumn ? (
|
{table.getState().columnSizingInfo.isResizingColumn ? (
|
||||||
<MemoizedTableBody
|
<MemoizedTableBody
|
||||||
|
bodyRef={bodyRef}
|
||||||
model={model}
|
model={model}
|
||||||
data={data}
|
data={data}
|
||||||
table={table}
|
table={table}
|
||||||
@ -304,9 +316,11 @@ function DirectoryTable({
|
|||||||
setSearch={setSearch}
|
setSearch={setSearch}
|
||||||
setSelectedPath={setSelectedPath}
|
setSelectedPath={setSelectedPath}
|
||||||
setRefreshVersion={setRefreshVersion}
|
setRefreshVersion={setRefreshVersion}
|
||||||
|
osRef={osRef.current}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TableBody
|
<TableBody
|
||||||
|
bodyRef={bodyRef}
|
||||||
model={model}
|
model={model}
|
||||||
data={data}
|
data={data}
|
||||||
table={table}
|
table={table}
|
||||||
@ -316,13 +330,15 @@ function DirectoryTable({
|
|||||||
setSearch={setSearch}
|
setSearch={setSearch}
|
||||||
setSelectedPath={setSelectedPath}
|
setSelectedPath={setSelectedPath}
|
||||||
setRefreshVersion={setRefreshVersion}
|
setRefreshVersion={setRefreshVersion}
|
||||||
|
osRef={osRef.current}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</OverlayScrollbarsComponent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TableBodyProps {
|
interface TableBodyProps {
|
||||||
|
bodyRef: React.RefObject<HTMLDivElement>;
|
||||||
model: PreviewModel;
|
model: PreviewModel;
|
||||||
data: Array<FileInfo>;
|
data: Array<FileInfo>;
|
||||||
table: Table<FileInfo>;
|
table: Table<FileInfo>;
|
||||||
@ -332,48 +348,32 @@ interface TableBodyProps {
|
|||||||
setSearch: (_: string) => void;
|
setSearch: (_: string) => void;
|
||||||
setSelectedPath: (_: string) => void;
|
setSelectedPath: (_: string) => void;
|
||||||
setRefreshVersion: React.Dispatch<React.SetStateAction<number>>;
|
setRefreshVersion: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
osRef: OverlayScrollbarsComponentRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableBody({
|
function TableBody({
|
||||||
|
bodyRef,
|
||||||
model,
|
model,
|
||||||
data,
|
|
||||||
table,
|
table,
|
||||||
search,
|
search,
|
||||||
focusIndex,
|
focusIndex,
|
||||||
setFocusIndex,
|
setFocusIndex,
|
||||||
setSearch,
|
setSearch,
|
||||||
setSelectedPath,
|
|
||||||
setRefreshVersion,
|
setRefreshVersion,
|
||||||
|
osRef,
|
||||||
}: TableBodyProps) {
|
}: TableBodyProps) {
|
||||||
const [bodyHeight, setBodyHeight] = useState(0);
|
const dummyLineRef = useRef<HTMLDivElement>();
|
||||||
|
const warningBoxRef = useRef<HTMLDivElement>();
|
||||||
const dummyLineRef = useRef<HTMLDivElement>(null);
|
|
||||||
const parentRef = useRef<HTMLDivElement>(null);
|
|
||||||
const warningBoxRef = useRef<HTMLDivElement>(null);
|
|
||||||
const osInstanceRef = useRef<OverlayScrollbars>(null);
|
|
||||||
const rowRefs = useRef<HTMLDivElement[]>([]);
|
const rowRefs = useRef<HTMLDivElement[]>([]);
|
||||||
const domRect = useDimensionsWithExistingRef(parentRef, 30);
|
const conn = useAtomValue(model.connection);
|
||||||
const parentHeight = domRect?.height ?? 0;
|
|
||||||
const conn = jotai.useAtomValue(model.connection);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dummyLineRef.current && data && parentRef.current) {
|
if (focusIndex !== null && rowRefs.current[focusIndex] && bodyRef.current && osRef) {
|
||||||
const rowHeight = dummyLineRef.current.offsetHeight;
|
const viewport = osRef.osInstance().elements().viewport;
|
||||||
const fullTBodyHeight = rowHeight * data.length;
|
|
||||||
const warningBoxHeight = warningBoxRef.current?.offsetHeight ?? 0;
|
|
||||||
const maxHeightLessHeader = parentHeight - warningBoxHeight;
|
|
||||||
const tbodyHeight = Math.min(maxHeightLessHeader, fullTBodyHeight);
|
|
||||||
setBodyHeight(tbodyHeight);
|
|
||||||
}
|
|
||||||
}, [data, parentHeight]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (focusIndex !== null && rowRefs.current[focusIndex] && parentRef.current) {
|
|
||||||
const viewport = osInstanceRef.current.elements().viewport;
|
|
||||||
const viewportHeight = viewport.offsetHeight;
|
const viewportHeight = viewport.offsetHeight;
|
||||||
const rowElement = rowRefs.current[focusIndex];
|
const rowElement = rowRefs.current[focusIndex];
|
||||||
const rowRect = rowElement.getBoundingClientRect();
|
const rowRect = rowElement.getBoundingClientRect();
|
||||||
const parentRect = parentRef.current.getBoundingClientRect();
|
const parentRect = bodyRef.current.getBoundingClientRect();
|
||||||
const viewportScrollTop = viewport.scrollTop;
|
const viewportScrollTop = viewport.scrollTop;
|
||||||
|
|
||||||
const rowTopRelativeToViewport = rowRect.top - parentRect.top + viewportScrollTop;
|
const rowTopRelativeToViewport = rowRect.top - parentRect.top + viewportScrollTop;
|
||||||
@ -387,7 +387,7 @@ function TableBody({
|
|||||||
viewport.scrollTo({ top: rowBottomRelativeToViewport - viewportHeight });
|
viewport.scrollTo({ top: rowBottomRelativeToViewport - viewportHeight });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [focusIndex, parentHeight]);
|
}, [focusIndex]);
|
||||||
|
|
||||||
const handleFileContextMenu = useCallback(
|
const handleFileContextMenu = useCallback(
|
||||||
(e: any, path: string, mimetype: string) => {
|
(e: any, path: string, mimetype: string) => {
|
||||||
@ -455,7 +455,7 @@ function TableBody({
|
|||||||
menu.push({
|
menu.push({
|
||||||
label: "Delete File",
|
label: "Delete File",
|
||||||
click: async () => {
|
click: async () => {
|
||||||
await services.FileService.DeleteFile(conn, path).catch((e) => console.log(e));
|
await FileService.DeleteFile(conn, path).catch((e) => console.log(e));
|
||||||
setRefreshVersion((current) => current + 1);
|
setRefreshVersion((current) => current + 1);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -492,12 +492,8 @@ function TableBody({
|
|||||||
[setSearch, handleFileContextMenu, setFocusIndex, focusIndex]
|
[setSearch, handleFileContextMenu, setFocusIndex, focusIndex]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleScrollbarInitialized = (instance) => {
|
|
||||||
osInstanceRef.current = instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dir-table-body" ref={parentRef}>
|
<div className="dir-table-body" ref={bodyRef}>
|
||||||
{search !== "" && (
|
{search !== "" && (
|
||||||
<div className="dir-table-body-search-display" ref={warningBoxRef}>
|
<div className="dir-table-body-search-display" ref={warningBoxRef}>
|
||||||
<span>Searching for "{search}"</span>
|
<span>Searching for "{search}"</span>
|
||||||
@ -507,18 +503,13 @@ function TableBody({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<OverlayScrollbarsComponent
|
<div className="dir-table-body-scroll-box">
|
||||||
options={{ scrollbars: { autoHide: "leave" } }}
|
<div className="dummy dir-table-body-row" ref={dummyLineRef}>
|
||||||
events={{ initialized: handleScrollbarInitialized }}
|
<div className="dir-table-body-cell">dummy-data</div>
|
||||||
>
|
|
||||||
<div className="dir-table-body-scroll-box" style={{ height: bodyHeight }}>
|
|
||||||
<div className="dummy dir-table-body-row" ref={dummyLineRef}>
|
|
||||||
<div className="dir-table-body-cell">dummy-data</div>
|
|
||||||
</div>
|
|
||||||
{table.getTopRows().map(displayRow)}
|
|
||||||
{table.getCenterRows().map((row, idx) => displayRow(row, idx + table.getTopRows().length))}
|
|
||||||
</div>
|
</div>
|
||||||
</OverlayScrollbarsComponent>
|
{table.getTopRows().map(displayRow)}
|
||||||
|
{table.getCenterRows().map((row, idx) => displayRow(row, idx + table.getTopRows().length))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -537,11 +528,11 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
|
|||||||
const [focusIndex, setFocusIndex] = useState(0);
|
const [focusIndex, setFocusIndex] = useState(0);
|
||||||
const [unfilteredData, setUnfilteredData] = useState<FileInfo[]>([]);
|
const [unfilteredData, setUnfilteredData] = useState<FileInfo[]>([]);
|
||||||
const [filteredData, setFilteredData] = useState<FileInfo[]>([]);
|
const [filteredData, setFilteredData] = useState<FileInfo[]>([]);
|
||||||
const fileName = jotai.useAtomValue(model.metaFilePath);
|
const fileName = useAtomValue(model.metaFilePath);
|
||||||
const showHiddenFiles = jotai.useAtomValue(model.showHiddenFiles);
|
const showHiddenFiles = useAtomValue(model.showHiddenFiles);
|
||||||
const [selectedPath, setSelectedPath] = useState("");
|
const [selectedPath, setSelectedPath] = useState("");
|
||||||
const [refreshVersion, setRefreshVersion] = jotai.useAtom(model.refreshVersion);
|
const [refreshVersion, setRefreshVersion] = useAtom(model.refreshVersion);
|
||||||
const conn = jotai.useAtomValue(model.connection);
|
const conn = useAtomValue(model.connection);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
model.refreshCallback = () => {
|
model.refreshCallback = () => {
|
||||||
@ -554,8 +545,8 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getContent = async () => {
|
const getContent = async () => {
|
||||||
const file = await services.FileService.ReadFile(conn, fileName);
|
const file = await FileService.ReadFile(conn, fileName);
|
||||||
const serializedContent = util.base64ToString(file?.data64);
|
const serializedContent = base64ToString(file?.data64);
|
||||||
const content: FileInfo[] = JSON.parse(serializedContent);
|
const content: FileInfo[] = JSON.parse(serializedContent);
|
||||||
setUnfilteredData(content);
|
setUnfilteredData(content);
|
||||||
};
|
};
|
||||||
@ -574,19 +565,19 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
model.directoryKeyDownHandler = (waveEvent: WaveKeyboardEvent): boolean => {
|
model.directoryKeyDownHandler = (waveEvent: WaveKeyboardEvent): boolean => {
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
|
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||||
setSearchText("");
|
setSearchText("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "ArrowUp")) {
|
if (checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||||
setFocusIndex((idx) => Math.max(idx - 1, 0));
|
setFocusIndex((idx) => Math.max(idx - 1, 0));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "ArrowDown")) {
|
if (checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||||
setFocusIndex((idx) => Math.min(idx + 1, filteredData.length - 1));
|
setFocusIndex((idx) => Math.min(idx + 1, filteredData.length - 1));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||||
if (filteredData.length == 0) {
|
if (filteredData.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -594,14 +585,14 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
|
|||||||
setSearchText("");
|
setSearchText("");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Backspace")) {
|
if (checkKeyPressed(waveEvent, "Backspace")) {
|
||||||
if (searchText.length == 0) {
|
if (searchText.length == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
setSearchText((current) => current.slice(0, -1));
|
setSearchText((current) => current.slice(0, -1));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyutil.isCharacterKeyEvent(waveEvent)) {
|
if (isCharacterKeyEvent(waveEvent)) {
|
||||||
setSearchText((current) => current + waveEvent.key);
|
setSearchText((current) => current + waveEvent.key);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -79,5 +79,5 @@
|
|||||||
|
|
||||||
.full-preview-content {
|
.full-preview-content {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow-y: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright 2024, Command Line Inc.
|
// Copyright 2024, Command Line Inc.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import { getAllGlobalKeyBindings } from "@/app/store/keymodel";
|
||||||
import { waveEventSubscribe } from "@/app/store/wps";
|
import { waveEventSubscribe } from "@/app/store/wps";
|
||||||
import { RpcApi } from "@/app/store/wshclientapi";
|
import { RpcApi } from "@/app/store/wshclientapi";
|
||||||
import { WindowRpcClient } from "@/app/store/wshrpcutil";
|
import { WindowRpcClient } from "@/app/store/wshrpcutil";
|
||||||
@ -265,6 +266,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
|||||||
if (waveEvent.type != "keydown") {
|
if (waveEvent.type != "keydown") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// deal with terminal specific keybindings
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Cmd:Escape")) {
|
if (keyutil.checkKeyPressed(waveEvent, "Cmd:Escape")) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -274,37 +276,20 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:ArrowLeft") ||
|
|
||||||
keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:ArrowRight") ||
|
|
||||||
keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:ArrowUp") ||
|
|
||||||
keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:ArrowDown")
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (let i = 1; i <= 9; i++) {
|
|
||||||
if (
|
|
||||||
keyutil.checkKeyPressed(waveEvent, `Ctrl:Shift:c{Digit${i}}`) ||
|
|
||||||
keyutil.checkKeyPressed(waveEvent, `Ctrl:Shift:c{Numpad${i}}`)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:v")) {
|
if (keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:v")) {
|
||||||
const p = navigator.clipboard.readText();
|
const p = navigator.clipboard.readText();
|
||||||
p.then((text) => {
|
p.then((text) => {
|
||||||
termRef.current?.terminal.paste(text);
|
termRef.current?.terminal.paste(text);
|
||||||
// termRef.current?.handleTermData(text);
|
|
||||||
});
|
});
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return true;
|
return false;
|
||||||
} else if (keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:c")) {
|
} else if (keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:c")) {
|
||||||
const sel = termRef.current?.terminal.getSelection();
|
const sel = termRef.current?.terminal.getSelection();
|
||||||
navigator.clipboard.writeText(sel);
|
navigator.clipboard.writeText(sel);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
if (shellProcStatusRef.current != "running" && keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
if (shellProcStatusRef.current != "running" && keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
||||||
// restart
|
// restart
|
||||||
@ -313,6 +298,12 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
|||||||
prtn.catch((e) => console.log("error controller resync (enter)", blockId, e));
|
prtn.catch((e) => console.log("error controller resync (enter)", blockId, e));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const globalKeys = getAllGlobalKeyBindings();
|
||||||
|
for (const key of globalKeys) {
|
||||||
|
if (keyutil.checkKeyPressed(waveEvent, key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
||||||
|
@ -41,8 +41,6 @@ function promptToMsg(prompt: OpenAIPromptMessageType): ChatMessageType {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
||||||
|
|
||||||
export class WaveAiModel implements ViewModel {
|
export class WaveAiModel implements ViewModel {
|
||||||
viewType: string;
|
viewType: string;
|
||||||
blockId: string;
|
blockId: string;
|
||||||
@ -102,14 +100,12 @@ export class WaveAiModel implements ViewModel {
|
|||||||
|
|
||||||
// Add a typing indicator
|
// Add a typing indicator
|
||||||
set(this.addMessageAtom, typingMessage);
|
set(this.addMessageAtom, typingMessage);
|
||||||
await sleep(1500);
|
|
||||||
const parts = userMessage.text.split(" ");
|
const parts = userMessage.text.split(" ");
|
||||||
let currentPart = 0;
|
let currentPart = 0;
|
||||||
while (currentPart < parts.length) {
|
while (currentPart < parts.length) {
|
||||||
const part = parts[currentPart] + " ";
|
const part = parts[currentPart] + " ";
|
||||||
set(this.updateLastMessageAtom, part, true);
|
set(this.updateLastMessageAtom, part, true);
|
||||||
currentPart++;
|
currentPart++;
|
||||||
await sleep(100);
|
|
||||||
}
|
}
|
||||||
set(this.updateLastMessageAtom, "", false);
|
set(this.updateLastMessageAtom, "", false);
|
||||||
});
|
});
|
||||||
@ -209,7 +205,6 @@ export class WaveAiModel implements ViewModel {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
await sleep(100);
|
|
||||||
}
|
}
|
||||||
globalStore.set(this.updateLastMessageAtom, "", false);
|
globalStore.set(this.updateLastMessageAtom, "", false);
|
||||||
if (fullMsg != "") {
|
if (fullMsg != "") {
|
||||||
|
@ -80,20 +80,6 @@ async function handleWidgetSelect(blockDef: BlockDef) {
|
|||||||
createBlock(blockDef);
|
createBlock(blockDef);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIconValid(icon: string): boolean {
|
|
||||||
if (util.isBlank(icon)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return icon.match(iconRegex) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIconClass(icon: string): string {
|
|
||||||
if (!isIconValid(icon)) {
|
|
||||||
return "fa fa-regular fa-browser fa-fw";
|
|
||||||
}
|
|
||||||
return `fa fa-solid fa-${icon} fa-fw`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Widget = React.memo(({ widget }: { widget: WidgetConfigType }) => {
|
const Widget = React.memo(({ widget }: { widget: WidgetConfigType }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -102,7 +88,7 @@ const Widget = React.memo(({ widget }: { widget: WidgetConfigType }) => {
|
|||||||
title={widget.description || widget.label}
|
title={widget.description || widget.label}
|
||||||
>
|
>
|
||||||
<div className="widget-icon" style={{ color: widget.color }}>
|
<div className="widget-icon" style={{ color: widget.color }}>
|
||||||
<i className={getIconClass(widget.icon)}></i>
|
<i className={util.makeIconClass(widget.icon, true, { defaultIcon: "browser" })}></i>
|
||||||
</div>
|
</div>
|
||||||
{!util.isBlank(widget.label) ? <div className="widget-label">{widget.label}</div> : null}
|
{!util.isBlank(widget.label) ? <div className="widget-label">{widget.label}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
|
2
frontend/types/gotypes.d.ts
vendored
2
frontend/types/gotypes.d.ts
vendored
@ -302,6 +302,7 @@ declare global {
|
|||||||
"term:mode"?: string;
|
"term:mode"?: string;
|
||||||
"term:theme"?: string;
|
"term:theme"?: string;
|
||||||
"term:localshellpath"?: string;
|
"term:localshellpath"?: string;
|
||||||
|
"term:localshellopts"?: string[];
|
||||||
count?: number;
|
count?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -419,6 +420,7 @@ declare global {
|
|||||||
"term:fontfamily"?: string;
|
"term:fontfamily"?: string;
|
||||||
"term:disablewebgl"?: boolean;
|
"term:disablewebgl"?: boolean;
|
||||||
"term:localshellpath"?: string;
|
"term:localshellpath"?: string;
|
||||||
|
"term:localshellopts"?: string[];
|
||||||
"editor:minimapenabled"?: boolean;
|
"editor:minimapenabled"?: boolean;
|
||||||
"editor:stickyscrollenabled"?: boolean;
|
"editor:stickyscrollenabled"?: boolean;
|
||||||
"web:*"?: boolean;
|
"web:*"?: boolean;
|
||||||
|
@ -47,16 +47,36 @@ function parseKeyDescription(keyDescription: string): KeyPressDecl {
|
|||||||
let keys = keyDescription.replace(/[()]/g, "").split(":");
|
let keys = keyDescription.replace(/[()]/g, "").split(":");
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
if (key == "Cmd") {
|
if (key == "Cmd") {
|
||||||
|
if (PLATFORM == PlatformMacOS) {
|
||||||
|
rtn.mods.Meta = true;
|
||||||
|
} else {
|
||||||
|
rtn.mods.Alt = true;
|
||||||
|
}
|
||||||
rtn.mods.Cmd = true;
|
rtn.mods.Cmd = true;
|
||||||
} else if (key == "Shift") {
|
} else if (key == "Shift") {
|
||||||
rtn.mods.Shift = true;
|
rtn.mods.Shift = true;
|
||||||
} else if (key == "Ctrl") {
|
} else if (key == "Ctrl") {
|
||||||
rtn.mods.Ctrl = true;
|
rtn.mods.Ctrl = true;
|
||||||
} else if (key == "Option") {
|
} else if (key == "Option") {
|
||||||
|
if (PLATFORM == PlatformMacOS) {
|
||||||
|
rtn.mods.Alt = true;
|
||||||
|
} else {
|
||||||
|
rtn.mods.Meta = true;
|
||||||
|
}
|
||||||
rtn.mods.Option = true;
|
rtn.mods.Option = true;
|
||||||
} else if (key == "Alt") {
|
} else if (key == "Alt") {
|
||||||
|
if (PLATFORM == PlatformMacOS) {
|
||||||
|
rtn.mods.Option = true;
|
||||||
|
} else {
|
||||||
|
rtn.mods.Cmd = true;
|
||||||
|
}
|
||||||
rtn.mods.Alt = true;
|
rtn.mods.Alt = true;
|
||||||
} else if (key == "Meta") {
|
} else if (key == "Meta") {
|
||||||
|
if (PLATFORM == PlatformMacOS) {
|
||||||
|
rtn.mods.Cmd = true;
|
||||||
|
} else {
|
||||||
|
rtn.mods.Option = true;
|
||||||
|
}
|
||||||
rtn.mods.Meta = true;
|
rtn.mods.Meta = true;
|
||||||
} else {
|
} else {
|
||||||
let { key: parsedKey, type: keyType } = parseKey(key);
|
let { key: parsedKey, type: keyType } = parseKey(key);
|
||||||
@ -138,10 +158,10 @@ function isInputEvent(event: WaveKeyboardEvent): boolean {
|
|||||||
|
|
||||||
function checkKeyPressed(event: WaveKeyboardEvent, keyDescription: string): boolean {
|
function checkKeyPressed(event: WaveKeyboardEvent, keyDescription: string): boolean {
|
||||||
let keyPress = parseKeyDescription(keyDescription);
|
let keyPress = parseKeyDescription(keyDescription);
|
||||||
if (!keyPress.mods.Alt && notMod(keyPress.mods.Option, event.option)) {
|
if (notMod(keyPress.mods.Option, event.option)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!keyPress.mods.Meta && notMod(keyPress.mods.Cmd, event.cmd)) {
|
if (notMod(keyPress.mods.Cmd, event.cmd)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (notMod(keyPress.mods.Shift, event.shift)) {
|
if (notMod(keyPress.mods.Shift, event.shift)) {
|
||||||
@ -150,10 +170,10 @@ function checkKeyPressed(event: WaveKeyboardEvent, keyDescription: string): bool
|
|||||||
if (notMod(keyPress.mods.Ctrl, event.control)) {
|
if (notMod(keyPress.mods.Ctrl, event.control)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (keyPress.mods.Alt && !event.alt) {
|
if (notMod(keyPress.mods.Alt, event.alt)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (keyPress.mods.Meta && !event.meta) {
|
if (notMod(keyPress.mods.Meta, event.meta)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let eventKey = "";
|
let eventKey = "";
|
||||||
|
@ -81,8 +81,11 @@ function jsonDeepEqual(v1: any, v2: any): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeIconClass(icon: string, fw: boolean, opts?: { spin: boolean }): string {
|
function makeIconClass(icon: string, fw: boolean, opts?: { spin?: boolean; defaultIcon?: string }): string {
|
||||||
if (icon == null) {
|
if (isBlank(icon)) {
|
||||||
|
if (opts?.defaultIcon != null) {
|
||||||
|
return makeIconClass(opts.defaultIcon, fw, { spin: opts?.spin });
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (icon.match(/^(solid@)?[a-z0-9-]+$/)) {
|
if (icon.match(/^(solid@)?[a-z0-9-]+$/)) {
|
||||||
@ -95,6 +98,14 @@ function makeIconClass(icon: string, fw: boolean, opts?: { spin: boolean }): str
|
|||||||
icon = icon.replace(/^regular@/, "");
|
icon = icon.replace(/^regular@/, "");
|
||||||
return clsx(`fa fa-sharp fa-regular fa-${icon}`, fw ? "fa-fw" : null, opts?.spin ? "fa-spin" : null);
|
return clsx(`fa fa-sharp fa-regular fa-${icon}`, fw ? "fa-fw" : null, opts?.spin ? "fa-spin" : null);
|
||||||
}
|
}
|
||||||
|
if (icon.match(/^brands@[a-z0-9-]+$/)) {
|
||||||
|
// strip off the "brands@" prefix if it exists
|
||||||
|
icon = icon.replace(/^brands@/, "");
|
||||||
|
return clsx(`fa fa-brands fa-${icon}`, fw ? "fa-fw" : null, opts?.spin ? "fa-spin" : null);
|
||||||
|
}
|
||||||
|
if (opts?.defaultIcon != null) {
|
||||||
|
return makeIconClass(opts.defaultIcon, fw, { spin: opts?.spin });
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"productName": "Wave",
|
"productName": "Wave",
|
||||||
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
|
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"version": "0.8.7-beta.1",
|
"version": "0.8.9-beta.0",
|
||||||
"homepage": "https://waveterm.dev",
|
"homepage": "https://waveterm.dev",
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "dev.commandline.waveterm"
|
"appId": "dev.commandline.waveterm"
|
||||||
|
@ -302,6 +302,12 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
|
|||||||
if blockMeta.GetString(waveobj.MetaKey_TermLocalShellPath, "") != "" {
|
if blockMeta.GetString(waveobj.MetaKey_TermLocalShellPath, "") != "" {
|
||||||
cmdOpts.ShellPath = blockMeta.GetString(waveobj.MetaKey_TermLocalShellPath, "")
|
cmdOpts.ShellPath = blockMeta.GetString(waveobj.MetaKey_TermLocalShellPath, "")
|
||||||
}
|
}
|
||||||
|
if len(settings.TermLocalShellOpts) > 0 {
|
||||||
|
cmdOpts.ShellOpts = append([]string{}, settings.TermLocalShellOpts...)
|
||||||
|
}
|
||||||
|
if len(blockMeta.GetStringList(waveobj.MetaKey_TermLocalShellOpts)) > 0 {
|
||||||
|
cmdOpts.ShellOpts = append([]string{}, blockMeta.GetStringList(waveobj.MetaKey_TermLocalShellOpts)...)
|
||||||
|
}
|
||||||
shellProc, err = shellexec.StartShellProc(rc.TermSize, cmdStr, cmdOpts)
|
shellProc, err = shellexec.StartShellProc(rc.TermSize, cmdStr, cmdOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -35,6 +35,7 @@ type CommandOptsType struct {
|
|||||||
Cwd string `json:"cwd,omitempty"`
|
Cwd string `json:"cwd,omitempty"`
|
||||||
Env map[string]string `json:"env,omitempty"`
|
Env map[string]string `json:"env,omitempty"`
|
||||||
ShellPath string `json:"shellPath,omitempty"`
|
ShellPath string `json:"shellPath,omitempty"`
|
||||||
|
ShellOpts []string `json:"shellOpts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShellProc struct {
|
type ShellProc struct {
|
||||||
@ -159,6 +160,7 @@ func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts Comm
|
|||||||
log.Printf("error installing rc files: %v", err)
|
log.Printf("error installing rc files: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
|
||||||
|
|
||||||
homeDir := remote.GetHomeDir(client)
|
homeDir := remote.GetHomeDir(client)
|
||||||
|
|
||||||
@ -280,6 +282,7 @@ func StartShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOpt
|
|||||||
if shellPath == "" {
|
if shellPath == "" {
|
||||||
shellPath = shellutil.DetectLocalShellPath()
|
shellPath = shellutil.DetectLocalShellPath()
|
||||||
}
|
}
|
||||||
|
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
|
||||||
if cmdStr == "" {
|
if cmdStr == "" {
|
||||||
if isBashShell(shellPath) {
|
if isBashShell(shellPath) {
|
||||||
// add --rcfile
|
// add --rcfile
|
||||||
|
@ -60,6 +60,7 @@ const (
|
|||||||
MetaKey_TermMode = "term:mode"
|
MetaKey_TermMode = "term:mode"
|
||||||
MetaKey_TermTheme = "term:theme"
|
MetaKey_TermTheme = "term:theme"
|
||||||
MetaKey_TermLocalShellPath = "term:localshellpath"
|
MetaKey_TermLocalShellPath = "term:localshellpath"
|
||||||
|
MetaKey_TermLocalShellOpts = "term:localshellopts"
|
||||||
|
|
||||||
MetaKey_Count = "count"
|
MetaKey_Count = "count"
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,24 @@ func (m MetaMapType) GetString(key string, def string) string {
|
|||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m MetaMapType) GetStringList(key string) []string {
|
||||||
|
v, ok := m[key]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
varr, ok := v.([]any)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rtn := make([]string, 0)
|
||||||
|
for _, varrVal := range varr {
|
||||||
|
if s, ok := varrVal.(string); ok {
|
||||||
|
rtn = append(rtn, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
func (m MetaMapType) GetBool(key string, def bool) bool {
|
func (m MetaMapType) GetBool(key string, def bool) bool {
|
||||||
if v, ok := m[key]; ok {
|
if v, ok := m[key]; ok {
|
||||||
if b, ok := v.(bool); ok {
|
if b, ok := v.(bool); ok {
|
||||||
|
@ -54,12 +54,13 @@ type MetaTSType struct {
|
|||||||
BgOpacity float64 `json:"bg:opacity,omitempty"`
|
BgOpacity float64 `json:"bg:opacity,omitempty"`
|
||||||
BgBlendMode string `json:"bg:blendmode,omitempty"`
|
BgBlendMode string `json:"bg:blendmode,omitempty"`
|
||||||
|
|
||||||
TermClear bool `json:"term:*,omitempty"`
|
TermClear bool `json:"term:*,omitempty"`
|
||||||
TermFontSize int `json:"term:fontsize,omitempty"`
|
TermFontSize int `json:"term:fontsize,omitempty"`
|
||||||
TermFontFamily string `json:"term:fontfamily,omitempty"`
|
TermFontFamily string `json:"term:fontfamily,omitempty"`
|
||||||
TermMode string `json:"term:mode,omitempty"`
|
TermMode string `json:"term:mode,omitempty"`
|
||||||
TermTheme string `json:"term:theme,omitempty"`
|
TermTheme string `json:"term:theme,omitempty"`
|
||||||
TermLocalShellPath string `json:"term:localshellpath,omitempty"` // matches settings
|
TermLocalShellPath string `json:"term:localshellpath,omitempty"` // matches settings
|
||||||
|
TermLocalShellOpts []string `json:"term:localshellopts,omitempty"` // matches settings
|
||||||
|
|
||||||
Count int `json:"count,omitempty"` // temp for cpu plot. will remove later
|
Count int `json:"count,omitempty"` // temp for cpu plot. will remove later
|
||||||
}
|
}
|
||||||
|
@ -6,27 +6,85 @@
|
|||||||
},
|
},
|
||||||
"bg@rainbow": {
|
"bg@rainbow": {
|
||||||
"display:name": "Rainbow",
|
"display:name": "Rainbow",
|
||||||
"display:order": 1,
|
"display:order": 2.1,
|
||||||
"bg:*": true,
|
"bg:*": true,
|
||||||
"bg": "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% )",
|
"bg": "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% )",
|
||||||
"bg:opacity": 0.3
|
"bg:opacity": 0.3
|
||||||
},
|
},
|
||||||
"bg@green": {
|
"bg@green": {
|
||||||
"display:name": "Green",
|
"display:name": "Green",
|
||||||
|
"display:order": 1.2,
|
||||||
"bg:*": true,
|
"bg:*": true,
|
||||||
"bg": "green",
|
"bg": "green",
|
||||||
"bg:opacity": 0.3
|
"bg:opacity": 0.3
|
||||||
},
|
},
|
||||||
"bg@blue": {
|
"bg@blue": {
|
||||||
"display:name": "Blue",
|
"display:name": "Blue",
|
||||||
|
"display:order": 1.1,
|
||||||
"bg:*": true,
|
"bg:*": true,
|
||||||
"bg": "blue",
|
"bg": "blue",
|
||||||
"bg:opacity": 0.3
|
"bg:opacity": 0.3
|
||||||
},
|
},
|
||||||
"bg@red": {
|
"bg@red": {
|
||||||
"display:name": "Red",
|
"display:name": "Red",
|
||||||
|
"display:order": 1.3,
|
||||||
"bg:*": true,
|
"bg:*": true,
|
||||||
"bg": "red",
|
"bg": "red",
|
||||||
"bg:opacity": 0.3
|
"bg:opacity": 0.3
|
||||||
|
},
|
||||||
|
"bg@ocean-depths": {
|
||||||
|
"display:name": "Ocean Depths",
|
||||||
|
"display:order": 2.2,
|
||||||
|
"bg:*": true,
|
||||||
|
"bg": "linear-gradient(135deg, purple, blue, teal)",
|
||||||
|
"bg:opacity": 0.7
|
||||||
|
},
|
||||||
|
"bg@aqua-horizon": {
|
||||||
|
"display:name": "Aqua Horizon",
|
||||||
|
"display:order": 2.3,
|
||||||
|
"bg:*": true,
|
||||||
|
"bg": "linear-gradient(135deg, rgba(15, 30, 50, 1) 0%, rgba(40, 90, 130, 0.85) 30%, rgba(20, 100, 150, 0.75) 60%, rgba(0, 120, 160, 0.65) 80%, rgba(0, 140, 180, 0.55) 100%), linear-gradient(135deg, rgba(100, 80, 255, 0.4), rgba(0, 180, 220, 0.4)), radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.05), transparent 70%)",
|
||||||
|
"bg:opacity": 0.85,
|
||||||
|
"bg:blendmode": "overlay"
|
||||||
|
},
|
||||||
|
"bg@sunset": {
|
||||||
|
"display:name": "Sunset",
|
||||||
|
"display:order": 2.4,
|
||||||
|
"bg:*": true,
|
||||||
|
"bg": "linear-gradient(135deg, rgba(128, 0, 0, 1), rgba(255, 69, 0, 0.8), rgba(75, 0, 130, 1))",
|
||||||
|
"bg:opacity": 0.8,
|
||||||
|
"bg:blendmode": "normal"
|
||||||
|
},
|
||||||
|
"bg@enchantedforest": {
|
||||||
|
"display:name": "Enchanted Forest",
|
||||||
|
"display:order": 2.7,
|
||||||
|
"bg:*": true,
|
||||||
|
"bg": "linear-gradient(145deg, rgba(0,50,0,1), rgba(34,139,34,0.7) 20%, rgba(0,100,0,0.5) 40%, rgba(0,200,100,0.3) 60%, rgba(34,139,34,0.8) 80%, rgba(0,50,0,1)), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 80%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 80%)",
|
||||||
|
"bg:opacity": 0.8,
|
||||||
|
"bg:blendmode": "soft-light"
|
||||||
|
},
|
||||||
|
"bg@twilight-mist": {
|
||||||
|
"display:name": "Twilight Mist",
|
||||||
|
"display:order": 2.9,
|
||||||
|
"bg:*": true,
|
||||||
|
"bg": "linear-gradient(180deg, rgba(60,60,90,1) 0%, rgba(90,110,140,0.8) 40%, rgba(120,140,160,0.6) 70%, rgba(60,60,90,1) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.15), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 70%)",
|
||||||
|
"bg:opacity": 0.9,
|
||||||
|
"bg:blendmode": "soft-light"
|
||||||
|
},
|
||||||
|
"bg@duskhorizon": {
|
||||||
|
"display:name": "Dusk Horizon",
|
||||||
|
"display:order": 3.1,
|
||||||
|
"bg:*": true,
|
||||||
|
"bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)",
|
||||||
|
"bg:opacity": 0.9,
|
||||||
|
"bg:blendmode": "overlay"
|
||||||
|
},
|
||||||
|
"bg@tropical-radiance": {
|
||||||
|
"display:name": "Tropical Radiance",
|
||||||
|
"display:order": 3.3,
|
||||||
|
"bg:*": true,
|
||||||
|
"bg": "linear-gradient(135deg, rgba(204, 51, 255, 0.9) 0%, rgba(255, 85, 153, 0.75) 30%, rgba(255, 51, 153, 0.65) 60%, rgba(204, 51, 255, 0.6) 80%, rgba(51, 102, 255, 0.5) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)",
|
||||||
|
"bg:opacity": 0.9,
|
||||||
|
"bg:blendmode": "overlay"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ const (
|
|||||||
ConfigKey_TermFontFamily = "term:fontfamily"
|
ConfigKey_TermFontFamily = "term:fontfamily"
|
||||||
ConfigKey_TermDisableWebGl = "term:disablewebgl"
|
ConfigKey_TermDisableWebGl = "term:disablewebgl"
|
||||||
ConfigKey_TermLocalShellPath = "term:localshellpath"
|
ConfigKey_TermLocalShellPath = "term:localshellpath"
|
||||||
|
ConfigKey_TermLocalShellOpts = "term:localshellopts"
|
||||||
|
|
||||||
ConfigKey_EditorMinimapEnabled = "editor:minimapenabled"
|
ConfigKey_EditorMinimapEnabled = "editor:minimapenabled"
|
||||||
ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled"
|
ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled"
|
||||||
|
@ -48,11 +48,12 @@ type SettingsType struct {
|
|||||||
AiMaxTokens float64 `json:"ai:maxtokens,omitempty"`
|
AiMaxTokens float64 `json:"ai:maxtokens,omitempty"`
|
||||||
AiTimeoutMs float64 `json:"ai:timeoutms,omitempty"`
|
AiTimeoutMs float64 `json:"ai:timeoutms,omitempty"`
|
||||||
|
|
||||||
TermClear bool `json:"term:*,omitempty"`
|
TermClear bool `json:"term:*,omitempty"`
|
||||||
TermFontSize float64 `json:"term:fontsize,omitempty"`
|
TermFontSize float64 `json:"term:fontsize,omitempty"`
|
||||||
TermFontFamily string `json:"term:fontfamily,omitempty"`
|
TermFontFamily string `json:"term:fontfamily,omitempty"`
|
||||||
TermDisableWebGl bool `json:"term:disablewebgl,omitempty"`
|
TermDisableWebGl bool `json:"term:disablewebgl,omitempty"`
|
||||||
TermLocalShellPath string `json:"term:localshellpath,omitempty"`
|
TermLocalShellPath string `json:"term:localshellpath,omitempty"`
|
||||||
|
TermLocalShellOpts []string `json:"term:localshellopts,omitempty"`
|
||||||
|
|
||||||
EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"`
|
EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"`
|
||||||
EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`
|
EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`
|
||||||
|
@ -3,7 +3,7 @@ module.exports = {
|
|||||||
plugins: ["prettier-plugin-jsdoc", "prettier-plugin-organize-imports"],
|
plugins: ["prettier-plugin-jsdoc", "prettier-plugin-organize-imports"],
|
||||||
printWidth: 120,
|
printWidth: 120,
|
||||||
trailingComma: "es5",
|
trailingComma: "es5",
|
||||||
useTabs: false,
|
useTabs: false,
|
||||||
jsdocVerticalAlignment: true,
|
jsdocVerticalAlignment: true,
|
||||||
jsdocSeparateReturnsFromParam: true,
|
jsdocSeparateReturnsFromParam: true,
|
||||||
jsdocSeparateTagGroups: true,
|
jsdocSeparateTagGroups: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user