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;",
|
||||
category: "Development;Utility;",
|
||||
},
|
||||
executableArgs: ["--enable-features", "UseOzonePlatform", "--ozone-platform-hint", "auto"], // Hint Electron to use Ozone abstraction layer for native Wayland support
|
||||
},
|
||||
deb: {
|
||||
afterInstall: "build/deb-postinstall.tpl",
|
||||
|
@ -16,6 +16,13 @@ import * as jotai from "jotai";
|
||||
const simpleControlShiftAtom = jotai.atom(false);
|
||||
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() {
|
||||
return simpleControlShiftAtom;
|
||||
}
|
||||
@ -161,12 +168,6 @@ function appHandleKeyDown(waveEvent: WaveKeyboardEvent): boolean {
|
||||
const blockId = focusedNode?.data?.blockId;
|
||||
if (blockId != null && shouldDispatchToBlock(waveEvent)) {
|
||||
const bcm = getBlockComponentModel(blockId);
|
||||
if (bcm.openSwitchConnection != null) {
|
||||
if (keyutil.checkKeyPressed(waveEvent, "Cmd:g")) {
|
||||
bcm.openSwitchConnection();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const viewModel = bcm?.viewModel;
|
||||
if (viewModel?.keyDownHandler) {
|
||||
const handledByBlock = viewModel.keyDownHandler(waveEvent);
|
||||
@ -262,6 +263,13 @@ function registerGlobalKeys() {
|
||||
switchBlockInDirection(tabId, NavigateDirection.Right);
|
||||
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++) {
|
||||
globalKeyMap.set(`Cmd:${idx}`, () => {
|
||||
switchTabAbs(idx);
|
||||
@ -282,6 +290,11 @@ function registerGlobalKeys() {
|
||||
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.
|
||||
function handleGlobalWaveKeyboardEvents(waveEvent: WaveKeyboardEvent): boolean {
|
||||
for (const key of globalKeyMap.keys()) {
|
||||
@ -297,6 +310,7 @@ function handleGlobalWaveKeyboardEvents(waveEvent: WaveKeyboardEvent): boolean {
|
||||
|
||||
export {
|
||||
appHandleKeyDown,
|
||||
getAllGlobalKeyBindings,
|
||||
getSimpleControlShiftAtom,
|
||||
registerControlShiftStateUpdateHandler,
|
||||
registerElectronReinjectKeyHandler,
|
||||
|
@ -7,18 +7,38 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
--min-row-width: 35rem;
|
||||
.dir-table {
|
||||
height: 100%;
|
||||
min-width: 600px;
|
||||
width: 100%;
|
||||
--col-size-size: 0.2rem;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
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 {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
.dir-table-head-row {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
min-width: var(--min-row-width);
|
||||
padding: 4px 6px;
|
||||
font-size: 0.75rem;
|
||||
|
||||
@ -68,10 +88,8 @@
|
||||
}
|
||||
|
||||
.dir-table-body {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
.dir-table-body-search-display {
|
||||
display: flex;
|
||||
border-radius: 3px;
|
||||
@ -94,6 +112,7 @@
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
padding: 0 6px;
|
||||
min-width: var(--min-row-width);
|
||||
|
||||
&.focused {
|
||||
background-color: rgb(from var(--accent-color) r g b / 0.5);
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
import { ContextMenuModel } from "@/app/store/contextmenu";
|
||||
import { atoms, createBlock, getApi } from "@/app/store/global";
|
||||
import { FileService } from "@/app/store/services";
|
||||
import type { PreviewModel } from "@/app/view/preview/preview";
|
||||
import * as services from "@/store/services";
|
||||
import * as keyutil from "@/util/keyutil";
|
||||
import * as util from "@/util/util";
|
||||
import { checkKeyPressed, isCharacterKeyEvent } from "@/util/keyutil";
|
||||
import { base64ToString, isBlank } from "@/util/util";
|
||||
import {
|
||||
Column,
|
||||
Row,
|
||||
@ -19,14 +19,11 @@ import {
|
||||
} from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import * as jotai from "jotai";
|
||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { quote as shellQuote } from "shell-quote";
|
||||
|
||||
import { OverlayScrollbars } from "overlayscrollbars";
|
||||
|
||||
import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import "./directorypreview.less";
|
||||
|
||||
interface DirectoryTableProps {
|
||||
@ -95,7 +92,7 @@ function getLastModifiedTime(unixMillis: number, column: Column<FileInfo, number
|
||||
const iconRegex = /^[a-z0-9- ]+$/;
|
||||
|
||||
function isIconValid(icon: string): boolean {
|
||||
if (util.isBlank(icon)) {
|
||||
if (isBlank(icon)) {
|
||||
return false;
|
||||
}
|
||||
return icon.match(iconRegex) != null;
|
||||
@ -134,11 +131,11 @@ function DirectoryTable({
|
||||
setSelectedPath,
|
||||
setRefreshVersion,
|
||||
}: DirectoryTableProps) {
|
||||
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
|
||||
const fullConfig = useAtomValue(atoms.fullConfigAtom);
|
||||
const getIconFromMimeType = useCallback(
|
||||
(mimeType: string): string => {
|
||||
while (mimeType.length > 0) {
|
||||
let icon = fullConfig.mimetypes?.[mimeType]?.icon ?? null;
|
||||
const icon = fullConfig.mimetypes?.[mimeType]?.icon ?? null;
|
||||
if (isIconValid(icon)) {
|
||||
return `fa fa-solid fa-${icon} fa-fw`;
|
||||
}
|
||||
@ -149,10 +146,7 @@ function DirectoryTable({
|
||||
[fullConfig.mimetypes]
|
||||
);
|
||||
const getIconColor = useCallback(
|
||||
(mimeType: string): string => {
|
||||
let iconColor = fullConfig.mimetypes?.[mimeType]?.color ?? "inherit";
|
||||
return iconColor;
|
||||
},
|
||||
(mimeType: string): string => fullConfig.mimetypes?.[mimeType]?.color ?? "inherit",
|
||||
[fullConfig.mimetypes]
|
||||
);
|
||||
const columns = useMemo(
|
||||
@ -261,8 +255,25 @@ function DirectoryTable({
|
||||
return colSizes;
|
||||
}, [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 (
|
||||
<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">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<div className="dir-table-head-row" key={headerGroup.id}>
|
||||
@ -295,6 +306,7 @@ function DirectoryTable({
|
||||
</div>
|
||||
{table.getState().columnSizingInfo.isResizingColumn ? (
|
||||
<MemoizedTableBody
|
||||
bodyRef={bodyRef}
|
||||
model={model}
|
||||
data={data}
|
||||
table={table}
|
||||
@ -304,9 +316,11 @@ function DirectoryTable({
|
||||
setSearch={setSearch}
|
||||
setSelectedPath={setSelectedPath}
|
||||
setRefreshVersion={setRefreshVersion}
|
||||
osRef={osRef.current}
|
||||
/>
|
||||
) : (
|
||||
<TableBody
|
||||
bodyRef={bodyRef}
|
||||
model={model}
|
||||
data={data}
|
||||
table={table}
|
||||
@ -316,13 +330,15 @@ function DirectoryTable({
|
||||
setSearch={setSearch}
|
||||
setSelectedPath={setSelectedPath}
|
||||
setRefreshVersion={setRefreshVersion}
|
||||
osRef={osRef.current}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
);
|
||||
}
|
||||
|
||||
interface TableBodyProps {
|
||||
bodyRef: React.RefObject<HTMLDivElement>;
|
||||
model: PreviewModel;
|
||||
data: Array<FileInfo>;
|
||||
table: Table<FileInfo>;
|
||||
@ -332,48 +348,32 @@ interface TableBodyProps {
|
||||
setSearch: (_: string) => void;
|
||||
setSelectedPath: (_: string) => void;
|
||||
setRefreshVersion: React.Dispatch<React.SetStateAction<number>>;
|
||||
osRef: OverlayScrollbarsComponentRef;
|
||||
}
|
||||
|
||||
function TableBody({
|
||||
bodyRef,
|
||||
model,
|
||||
data,
|
||||
table,
|
||||
search,
|
||||
focusIndex,
|
||||
setFocusIndex,
|
||||
setSearch,
|
||||
setSelectedPath,
|
||||
setRefreshVersion,
|
||||
osRef,
|
||||
}: TableBodyProps) {
|
||||
const [bodyHeight, setBodyHeight] = useState(0);
|
||||
|
||||
const dummyLineRef = useRef<HTMLDivElement>(null);
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const warningBoxRef = useRef<HTMLDivElement>(null);
|
||||
const osInstanceRef = useRef<OverlayScrollbars>(null);
|
||||
const dummyLineRef = useRef<HTMLDivElement>();
|
||||
const warningBoxRef = useRef<HTMLDivElement>();
|
||||
const rowRefs = useRef<HTMLDivElement[]>([]);
|
||||
const domRect = useDimensionsWithExistingRef(parentRef, 30);
|
||||
const parentHeight = domRect?.height ?? 0;
|
||||
const conn = jotai.useAtomValue(model.connection);
|
||||
const conn = useAtomValue(model.connection);
|
||||
|
||||
useEffect(() => {
|
||||
if (dummyLineRef.current && data && parentRef.current) {
|
||||
const rowHeight = dummyLineRef.current.offsetHeight;
|
||||
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;
|
||||
if (focusIndex !== null && rowRefs.current[focusIndex] && bodyRef.current && osRef) {
|
||||
const viewport = osRef.osInstance().elements().viewport;
|
||||
const viewportHeight = viewport.offsetHeight;
|
||||
const rowElement = rowRefs.current[focusIndex];
|
||||
const rowRect = rowElement.getBoundingClientRect();
|
||||
const parentRect = parentRef.current.getBoundingClientRect();
|
||||
const parentRect = bodyRef.current.getBoundingClientRect();
|
||||
const viewportScrollTop = viewport.scrollTop;
|
||||
|
||||
const rowTopRelativeToViewport = rowRect.top - parentRect.top + viewportScrollTop;
|
||||
@ -387,7 +387,7 @@ function TableBody({
|
||||
viewport.scrollTo({ top: rowBottomRelativeToViewport - viewportHeight });
|
||||
}
|
||||
}
|
||||
}, [focusIndex, parentHeight]);
|
||||
}, [focusIndex]);
|
||||
|
||||
const handleFileContextMenu = useCallback(
|
||||
(e: any, path: string, mimetype: string) => {
|
||||
@ -455,7 +455,7 @@ function TableBody({
|
||||
menu.push({
|
||||
label: "Delete File",
|
||||
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);
|
||||
},
|
||||
});
|
||||
@ -492,12 +492,8 @@ function TableBody({
|
||||
[setSearch, handleFileContextMenu, setFocusIndex, focusIndex]
|
||||
);
|
||||
|
||||
const handleScrollbarInitialized = (instance) => {
|
||||
osInstanceRef.current = instance;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dir-table-body" ref={parentRef}>
|
||||
<div className="dir-table-body" ref={bodyRef}>
|
||||
{search !== "" && (
|
||||
<div className="dir-table-body-search-display" ref={warningBoxRef}>
|
||||
<span>Searching for "{search}"</span>
|
||||
@ -507,18 +503,13 @@ function TableBody({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<OverlayScrollbarsComponent
|
||||
options={{ scrollbars: { autoHide: "leave" } }}
|
||||
events={{ initialized: handleScrollbarInitialized }}
|
||||
>
|
||||
<div className="dir-table-body-scroll-box" style={{ height: bodyHeight }}>
|
||||
<div className="dir-table-body-scroll-box">
|
||||
<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>
|
||||
</OverlayScrollbarsComponent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -537,11 +528,11 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
|
||||
const [focusIndex, setFocusIndex] = useState(0);
|
||||
const [unfilteredData, setUnfilteredData] = useState<FileInfo[]>([]);
|
||||
const [filteredData, setFilteredData] = useState<FileInfo[]>([]);
|
||||
const fileName = jotai.useAtomValue(model.metaFilePath);
|
||||
const showHiddenFiles = jotai.useAtomValue(model.showHiddenFiles);
|
||||
const fileName = useAtomValue(model.metaFilePath);
|
||||
const showHiddenFiles = useAtomValue(model.showHiddenFiles);
|
||||
const [selectedPath, setSelectedPath] = useState("");
|
||||
const [refreshVersion, setRefreshVersion] = jotai.useAtom(model.refreshVersion);
|
||||
const conn = jotai.useAtomValue(model.connection);
|
||||
const [refreshVersion, setRefreshVersion] = useAtom(model.refreshVersion);
|
||||
const conn = useAtomValue(model.connection);
|
||||
|
||||
useEffect(() => {
|
||||
model.refreshCallback = () => {
|
||||
@ -554,8 +545,8 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
|
||||
|
||||
useEffect(() => {
|
||||
const getContent = async () => {
|
||||
const file = await services.FileService.ReadFile(conn, fileName);
|
||||
const serializedContent = util.base64ToString(file?.data64);
|
||||
const file = await FileService.ReadFile(conn, fileName);
|
||||
const serializedContent = base64ToString(file?.data64);
|
||||
const content: FileInfo[] = JSON.parse(serializedContent);
|
||||
setUnfilteredData(content);
|
||||
};
|
||||
@ -574,19 +565,19 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
|
||||
|
||||
useEffect(() => {
|
||||
model.directoryKeyDownHandler = (waveEvent: WaveKeyboardEvent): boolean => {
|
||||
if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
setSearchText("");
|
||||
return;
|
||||
}
|
||||
if (keyutil.checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||
setFocusIndex((idx) => Math.max(idx - 1, 0));
|
||||
return true;
|
||||
}
|
||||
if (keyutil.checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
if (checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
setFocusIndex((idx) => Math.min(idx + 1, filteredData.length - 1));
|
||||
return true;
|
||||
}
|
||||
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
if (filteredData.length == 0) {
|
||||
return;
|
||||
}
|
||||
@ -594,14 +585,14 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
|
||||
setSearchText("");
|
||||
return true;
|
||||
}
|
||||
if (keyutil.checkKeyPressed(waveEvent, "Backspace")) {
|
||||
if (checkKeyPressed(waveEvent, "Backspace")) {
|
||||
if (searchText.length == 0) {
|
||||
return true;
|
||||
}
|
||||
setSearchText((current) => current.slice(0, -1));
|
||||
return true;
|
||||
}
|
||||
if (keyutil.isCharacterKeyEvent(waveEvent)) {
|
||||
if (isCharacterKeyEvent(waveEvent)) {
|
||||
setSearchText((current) => current + waveEvent.key);
|
||||
return true;
|
||||
}
|
||||
|
@ -79,5 +79,5 @@
|
||||
|
||||
.full-preview-content {
|
||||
flex-grow: 1;
|
||||
overflow-y: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { getAllGlobalKeyBindings } from "@/app/store/keymodel";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { WindowRpcClient } from "@/app/store/wshrpcutil";
|
||||
@ -265,6 +266,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
||||
if (waveEvent.type != "keydown") {
|
||||
return true;
|
||||
}
|
||||
// deal with terminal specific keybindings
|
||||
if (keyutil.checkKeyPressed(waveEvent, "Cmd:Escape")) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@ -274,37 +276,20 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
||||
});
|
||||
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")) {
|
||||
const p = navigator.clipboard.readText();
|
||||
p.then((text) => {
|
||||
termRef.current?.terminal.paste(text);
|
||||
// termRef.current?.handleTermData(text);
|
||||
});
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
return false;
|
||||
} else if (keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:c")) {
|
||||
const sel = termRef.current?.terminal.getSelection();
|
||||
navigator.clipboard.writeText(sel);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if (shellProcStatusRef.current != "running" && keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
||||
// restart
|
||||
@ -313,6 +298,12 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
||||
prtn.catch((e) => console.log("error controller resync (enter)", blockId, e));
|
||||
return false;
|
||||
}
|
||||
const globalKeys = getAllGlobalKeyBindings();
|
||||
for (const key of globalKeys) {
|
||||
if (keyutil.checkKeyPressed(waveEvent, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
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 {
|
||||
viewType: string;
|
||||
blockId: string;
|
||||
@ -102,14 +100,12 @@ export class WaveAiModel implements ViewModel {
|
||||
|
||||
// Add a typing indicator
|
||||
set(this.addMessageAtom, typingMessage);
|
||||
await sleep(1500);
|
||||
const parts = userMessage.text.split(" ");
|
||||
let currentPart = 0;
|
||||
while (currentPart < parts.length) {
|
||||
const part = parts[currentPart] + " ";
|
||||
set(this.updateLastMessageAtom, part, true);
|
||||
currentPart++;
|
||||
await sleep(100);
|
||||
}
|
||||
set(this.updateLastMessageAtom, "", false);
|
||||
});
|
||||
@ -209,7 +205,6 @@ export class WaveAiModel implements ViewModel {
|
||||
}
|
||||
break;
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
globalStore.set(this.updateLastMessageAtom, "", false);
|
||||
if (fullMsg != "") {
|
||||
|
@ -80,20 +80,6 @@ async function handleWidgetSelect(blockDef: 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 }) => {
|
||||
return (
|
||||
<div
|
||||
@ -102,7 +88,7 @@ const Widget = React.memo(({ widget }: { widget: WidgetConfigType }) => {
|
||||
title={widget.description || widget.label}
|
||||
>
|
||||
<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>
|
||||
{!util.isBlank(widget.label) ? <div className="widget-label">{widget.label}</div> : null}
|
||||
</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:theme"?: string;
|
||||
"term:localshellpath"?: string;
|
||||
"term:localshellopts"?: string[];
|
||||
count?: number;
|
||||
};
|
||||
|
||||
@ -419,6 +420,7 @@ declare global {
|
||||
"term:fontfamily"?: string;
|
||||
"term:disablewebgl"?: boolean;
|
||||
"term:localshellpath"?: string;
|
||||
"term:localshellopts"?: string[];
|
||||
"editor:minimapenabled"?: boolean;
|
||||
"editor:stickyscrollenabled"?: boolean;
|
||||
"web:*"?: boolean;
|
||||
|
@ -47,16 +47,36 @@ function parseKeyDescription(keyDescription: string): KeyPressDecl {
|
||||
let keys = keyDescription.replace(/[()]/g, "").split(":");
|
||||
for (let key of keys) {
|
||||
if (key == "Cmd") {
|
||||
if (PLATFORM == PlatformMacOS) {
|
||||
rtn.mods.Meta = true;
|
||||
} else {
|
||||
rtn.mods.Alt = true;
|
||||
}
|
||||
rtn.mods.Cmd = true;
|
||||
} else if (key == "Shift") {
|
||||
rtn.mods.Shift = true;
|
||||
} else if (key == "Ctrl") {
|
||||
rtn.mods.Ctrl = true;
|
||||
} else if (key == "Option") {
|
||||
if (PLATFORM == PlatformMacOS) {
|
||||
rtn.mods.Alt = true;
|
||||
} else {
|
||||
rtn.mods.Meta = true;
|
||||
}
|
||||
rtn.mods.Option = true;
|
||||
} else if (key == "Alt") {
|
||||
if (PLATFORM == PlatformMacOS) {
|
||||
rtn.mods.Option = true;
|
||||
} else {
|
||||
rtn.mods.Cmd = true;
|
||||
}
|
||||
rtn.mods.Alt = true;
|
||||
} else if (key == "Meta") {
|
||||
if (PLATFORM == PlatformMacOS) {
|
||||
rtn.mods.Cmd = true;
|
||||
} else {
|
||||
rtn.mods.Option = true;
|
||||
}
|
||||
rtn.mods.Meta = true;
|
||||
} else {
|
||||
let { key: parsedKey, type: keyType } = parseKey(key);
|
||||
@ -138,10 +158,10 @@ function isInputEvent(event: WaveKeyboardEvent): boolean {
|
||||
|
||||
function checkKeyPressed(event: WaveKeyboardEvent, keyDescription: string): boolean {
|
||||
let keyPress = parseKeyDescription(keyDescription);
|
||||
if (!keyPress.mods.Alt && notMod(keyPress.mods.Option, event.option)) {
|
||||
if (notMod(keyPress.mods.Option, event.option)) {
|
||||
return false;
|
||||
}
|
||||
if (!keyPress.mods.Meta && notMod(keyPress.mods.Cmd, event.cmd)) {
|
||||
if (notMod(keyPress.mods.Cmd, event.cmd)) {
|
||||
return false;
|
||||
}
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
if (keyPress.mods.Alt && !event.alt) {
|
||||
if (notMod(keyPress.mods.Alt, event.alt)) {
|
||||
return false;
|
||||
}
|
||||
if (keyPress.mods.Meta && !event.meta) {
|
||||
if (notMod(keyPress.mods.Meta, event.meta)) {
|
||||
return false;
|
||||
}
|
||||
let eventKey = "";
|
||||
|
@ -81,8 +81,11 @@ function jsonDeepEqual(v1: any, v2: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function makeIconClass(icon: string, fw: boolean, opts?: { spin: boolean }): string {
|
||||
if (icon == null) {
|
||||
function makeIconClass(icon: string, fw: boolean, opts?: { spin?: boolean; defaultIcon?: string }): string {
|
||||
if (isBlank(icon)) {
|
||||
if (opts?.defaultIcon != null) {
|
||||
return makeIconClass(opts.defaultIcon, fw, { spin: opts?.spin });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
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@/, "");
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
"productName": "Wave",
|
||||
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.8.7-beta.1",
|
||||
"version": "0.8.9-beta.0",
|
||||
"homepage": "https://waveterm.dev",
|
||||
"build": {
|
||||
"appId": "dev.commandline.waveterm"
|
||||
|
@ -302,6 +302,12 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
|
||||
if 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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -35,6 +35,7 @@ type CommandOptsType struct {
|
||||
Cwd string `json:"cwd,omitempty"`
|
||||
Env map[string]string `json:"env,omitempty"`
|
||||
ShellPath string `json:"shellPath,omitempty"`
|
||||
ShellOpts []string `json:"shellOpts,omitempty"`
|
||||
}
|
||||
|
||||
type ShellProc struct {
|
||||
@ -159,6 +160,7 @@ func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts Comm
|
||||
log.Printf("error installing rc files: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
|
||||
|
||||
homeDir := remote.GetHomeDir(client)
|
||||
|
||||
@ -280,6 +282,7 @@ func StartShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOpt
|
||||
if shellPath == "" {
|
||||
shellPath = shellutil.DetectLocalShellPath()
|
||||
}
|
||||
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
|
||||
if cmdStr == "" {
|
||||
if isBashShell(shellPath) {
|
||||
// add --rcfile
|
||||
|
@ -60,6 +60,7 @@ const (
|
||||
MetaKey_TermMode = "term:mode"
|
||||
MetaKey_TermTheme = "term:theme"
|
||||
MetaKey_TermLocalShellPath = "term:localshellpath"
|
||||
MetaKey_TermLocalShellOpts = "term:localshellopts"
|
||||
|
||||
MetaKey_Count = "count"
|
||||
)
|
||||
|
@ -14,6 +14,24 @@ func (m MetaMapType) GetString(key string, def string) string {
|
||||
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 {
|
||||
if v, ok := m[key]; ok {
|
||||
if b, ok := v.(bool); ok {
|
||||
|
@ -60,6 +60,7 @@ type MetaTSType struct {
|
||||
TermMode string `json:"term:mode,omitempty"`
|
||||
TermTheme string `json:"term:theme,omitempty"`
|
||||
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
|
||||
}
|
||||
|
@ -6,27 +6,85 @@
|
||||
},
|
||||
"bg@rainbow": {
|
||||
"display:name": "Rainbow",
|
||||
"display:order": 1,
|
||||
"display:order": 2.1,
|
||||
"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:opacity": 0.3
|
||||
},
|
||||
"bg@green": {
|
||||
"display:name": "Green",
|
||||
"display:order": 1.2,
|
||||
"bg:*": true,
|
||||
"bg": "green",
|
||||
"bg:opacity": 0.3
|
||||
},
|
||||
"bg@blue": {
|
||||
"display:name": "Blue",
|
||||
"display:order": 1.1,
|
||||
"bg:*": true,
|
||||
"bg": "blue",
|
||||
"bg:opacity": 0.3
|
||||
},
|
||||
"bg@red": {
|
||||
"display:name": "Red",
|
||||
"display:order": 1.3,
|
||||
"bg:*": true,
|
||||
"bg": "red",
|
||||
"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_TermDisableWebGl = "term:disablewebgl"
|
||||
ConfigKey_TermLocalShellPath = "term:localshellpath"
|
||||
ConfigKey_TermLocalShellOpts = "term:localshellopts"
|
||||
|
||||
ConfigKey_EditorMinimapEnabled = "editor:minimapenabled"
|
||||
ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled"
|
||||
|
@ -53,6 +53,7 @@ type SettingsType struct {
|
||||
TermFontFamily string `json:"term:fontfamily,omitempty"`
|
||||
TermDisableWebGl bool `json:"term:disablewebgl,omitempty"`
|
||||
TermLocalShellPath string `json:"term:localshellpath,omitempty"`
|
||||
TermLocalShellOpts []string `json:"term:localshellopts,omitempty"`
|
||||
|
||||
EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"`
|
||||
EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`
|
||||
|
Loading…
Reference in New Issue
Block a user