tos page 2 -- quick tips (#387)

This commit is contained in:
Mike Sawka 2024-09-16 23:45:47 -07:00 committed by GitHub
parent 87d3f8d88d
commit 46eb164778
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 510 additions and 113 deletions

View File

@ -0,0 +1,83 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
.tips-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
.tips-section {
display: flex;
flex-direction: column;
flex-grow: 1;
gap: 5px;
.tip-section-header {
font-weight: bold;
margin-bottom: 5px;
margin-top: 10px;
font-size: 16px;
&:first-child {
margin-top: 0;
}
}
.tip {
display: flex;
flex-direction: row;
align-items: center;
code {
padding: 0.1em 0.4em;
background-color: var(--highlight-bg-color);
}
.keybinding-group {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 5px;
margin-right: 5px;
&:first-child {
margin-left: 0;
}
}
.keybinding {
display: inline-block;
padding: 0.1em 0.4em;
margin: 0 0.1em;
font: var(--fixed-font);
font-size: 0.85em;
color: var(--keybinding-color);
background-color: var(--keybinding-bg-color);
border-radius: 4px;
border: 1px solid var(--keybinding-border-color);
box-shadow: none;
}
.icon-wrap {
background-color: var(--highlight-bg-color);
padding: 2px;
color: var(--secondary-text-color);
font-size: 12px;
border-radius: 2px;
margin-right: 5px;
svg {
position: relative;
top: 3px;
left: 1px;
height: 13px;
#arrow1,
#arrow2 {
fill: var(--main-text-color);
}
}
}
}
}
}

View File

@ -0,0 +1,152 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { MagnifyIcon } from "@/app/element/magnify";
import { PLATFORM } from "@/app/store/global";
import "./quicktips.less";
const KeyBinding = ({ keyDecl }: { keyDecl: string }) => {
const parts = keyDecl.split(":");
const elems: React.ReactNode[] = [];
for (let part of parts) {
if (part === "Cmd") {
if (PLATFORM === "darwin") {
elems.push(
<div key="cmd" className="keybinding">
Cmd
</div>
);
} else {
elems.push(
<div key="alt" className="keybinding">
Alt
</div>
);
}
continue;
}
if (part == "Ctrl") {
elems.push(
<div key="ctrl" className="keybinding">
^ Ctrl
</div>
);
continue;
}
if (part == "Shift") {
elems.push(
<div key="shift" className="keybinding">
Shift
</div>
);
continue;
}
if (part == "Arrows") {
elems.push(
<div key="arrows1" className="keybinding">
</div>
);
elems.push(
<div key="arrows2" className="keybinding">
</div>
);
elems.push(
<div key="arrows3" className="keybinding">
</div>
);
elems.push(
<div key="arrows4" className="keybinding">
</div>
);
continue;
}
if (part == "Digit") {
elems.push(
<div key="digit" className="keybinding">
Number (1-9)
</div>
);
continue;
}
elems.push(
<div key={part} className="keybinding">
{part.toUpperCase()}
</div>
);
}
return <div className="keybinding-group">{elems}</div>;
};
const QuickTips = () => {
return (
<div className="tips-wrapper">
<div className="tips-section">
<div className="tip-section-header">Header Icons</div>
<div className="tip">
<div className="icon-wrap">
<i className="fa-solid fa-sharp fa-laptop fa-fw" />
</div>
Connect to a remote server
<KeyBinding keyDecl="Cmd:g" />
</div>
<div className="tip">
<div className="icon-wrap">
<MagnifyIcon enabled={false} />
</div>
Magnify a Block <KeyBinding keyDecl="Cmd:m" />
</div>
<div className="tip">
<div className="icon-wrap">
<i className="fa-solid fa-sharp fa-cog fa-fw" />
</div>
Block Settings
</div>
<div className="tip">
<div className="icon-wrap">
<i className="fa-solid fa-sharp fa-xmark-large fa-fw" />
</div>
Close Block <KeyBinding keyDecl="Cmd:w" />
</div>
<div className="tip-section-header">Important Keybindings</div>
<div className="tip">
<KeyBinding keyDecl="Cmd:t" />
New Tab
</div>
<div className="tip">
<KeyBinding keyDecl="Cmd:n" />
New Terminal Block
</div>
<div className="tip">
<KeyBinding keyDecl="Ctrl:Shift:Arrows" />
Navigate Between Blocks
</div>
<div className="tip">
<KeyBinding keyDecl="Ctrl:Shift:Digit" />
Focus Nth Block
</div>
<div className="tip">
<KeyBinding keyDecl="Cmd:Digit" />
Switch To Nth Tab
</div>
<div className="tip-section-header">wsh commands</div>
<div className="tip">
<div>
<code>wsh view [filename|url]</code>
<div style={{ marginTop: 5 }}>
Run this command in the terminal to preview a file, directory, or web URL.
</div>
</div>
</div>
</div>
</div>
);
};
export { KeyBinding, QuickTips };

View File

@ -10,6 +10,7 @@ import { TosModal } from "./tos";
const ModalsRenderer = () => {
const clientData = jotai.useAtomValue(atoms.client);
const [tosOpen, setTosOpen] = jotai.useAtom(modalsModel.tosOpen);
const [modals] = jotai.useAtom(modalsModel.modalsAtom);
const rtn: JSX.Element[] = [];
for (const modal of modals) {
@ -18,12 +19,18 @@ const ModalsRenderer = () => {
rtn.push(<ModalComponent key={modal.displayName} {...modal.props} />);
}
}
if (!clientData.tosagreed) {
if (tosOpen) {
rtn.push(<TosModal key={TosModal.displayName} />);
}
useEffect(() => {
if (!clientData.tosagreed) {
setTosOpen(true);
}
}, [clientData]);
useEffect(() => {
globalStore.set(atoms.modalOpen, rtn.length > 0);
});
}, [rtn]);
return <>{rtn}</>;
};

View File

@ -11,6 +11,7 @@
flex-direction: column;
overflow-y: auto;
padding: 30px;
width: 100%;
header.tos-header {
flex-direction: column;
@ -18,6 +19,7 @@
border-bottom: none;
padding: 0;
margin-bottom: 36px;
width: 100%;
.logo {
margin-bottom: 10px;
@ -55,6 +57,106 @@
color: var(--secondary-text-color);
}
.tips-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
.tips-section {
display: flex;
flex-direction: column;
flex-grow: 1;
gap: 5px;
.tip-section-header {
font-weight: bold;
margin-bottom: 5px;
margin-top: 10px;
&:first-child {
margin-top: 0;
}
}
.tip {
display: flex;
flex-direction: row;
align-items: center;
.keybinding2 {
font: var(--fixed-font);
background-color: var(--highlight-bg-color);
color: var(--main-text-color);
padding: 2px 8px;
border-radius: 4px;
}
.keybinding-group {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 5px;
margin-right: 5px;
&:first-child {
margin-left: 0;
}
}
.keybinding {
display: inline-block;
padding: 0.1em 0.4em;
margin: 0 0.1em;
font-family: "SF Pro Text", "Segoe UI", sans-serif;
font-size: 0.85em;
color: #e0e0e0;
background-color: #333;
border-radius: 4px;
border: 1px solid #444;
box-shadow: none;
}
.keybinding3 {
color: black;
display: inline-block;
padding: 0.2em 0.4em;
min-width: 24px;
height: 22px;
margin: 0 0.1em;
font-family: "SF Pro Text", "Segoe UI", sans-serif;
font-size: 0.9em;
border: 1px solid #aaa;
border-radius: 4px;
background-color: #ddd;
color: var(--invert-text-color);
box-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.1);
text-align: center;
}
.icon-wrap {
background-color: var(--highlight-bg-color);
padding: 2px;
color: var(--secondary-text-color);
font-size: 12px;
border-radius: 2px;
margin-right: 5px;
svg {
position: relative;
top: 3px;
left: 1px;
height: 13px;
#arrow1,
#arrow2 {
fill: var(--main-text-color);
}
}
}
}
}
}
.content-section {
display: flex;
width: 100%;

View File

@ -9,13 +9,156 @@ import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import { useEffect, useRef, useState } from "react";
import { FlexiModal } from "./modal";
import { QuickTips } from "@/app/element/quicktips";
import { atoms } from "@/app/store/global";
import { modalsModel } from "@/app/store/modalmodel";
import { RpcApi } from "@/app/store/wshclientapi";
import { WindowRpcClient } from "@/app/store/wshrpcutil";
import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai";
import "./tos.less";
const pageNumAtom: PrimitiveAtom<number> = atom<number>(1);
const ModalPage1 = () => {
const settings = useAtomValue(atoms.settingsAtom);
const clientData = useAtomValue(atoms.client);
const [tosOpen, setTosOpen] = useAtom(modalsModel.tosOpen);
const [telemetryEnabled, setTelemetryEnabled] = useState<boolean>(!!settings["telemetry:enabled"]);
const setPageNum = useSetAtom(pageNumAtom);
const acceptTos = () => {
if (!clientData.tosagreed) {
services.ClientService.AgreeTos();
}
setPageNum(2);
};
const setTelemetry = (value: boolean) => {
RpcApi.SetConfigCommand(WindowRpcClient, { "telemetry:enabled": value })
.then(() => {
setTelemetryEnabled(value);
})
.catch((error) => {
console.error("failed to set telemetry:", error);
});
};
const label = telemetryEnabled ? "Telemetry Enabled" : "Telemetry Disabled";
return (
<>
<header className="modal-header tos-header unselectable">
<div className="logo">
<Logo />
</div>
<div className="modal-title">Welcome to Wave Terminal</div>
</header>
<div className="modal-content tos-content unselectable">
<div className="content-section">
<div className="icon-wrapper">
<a target="_blank" href="https://github.com/wavetermdev/waveterm" rel={"noopener"}>
<i className="icon fa-brands fa-github"></i>
</a>
</div>
<div className="content-section-inner">
<div className="content-section-title">Support us on GitHub</div>
<div className="content-section-text">
We're <i>open source</i> and committed to providing a free terminal for individual users.
Please show your support by giving us a star on{" "}
<a target="_blank" href="https://github.com/wavetermdev/waveterm" rel={"noopener"}>
Github&nbsp;(wavetermdev/waveterm)
</a>
</div>
</div>
</div>
<div className="content-section">
<div className="icon-wrapper">
<a target="_blank" href="https://discord.gg/XfvZ334gwU" rel={"noopener"}>
<i className="icon fa-solid fa-people-group"></i>
</a>
</div>
<div className="content-section-inner">
<div className="content-section-title">Join our Community</div>
<div className="content-section-text">
Get help, submit feature requests, report bugs, or just chat with fellow terminal
enthusiasts.
<br />
<a target="_blank" href="https://discord.gg/XfvZ334gwU" rel={"noopener"}>
Join the Wave&nbsp;Discord&nbsp;Channel
</a>
</div>
</div>
</div>
<div className="content-section">
<div className="icon-wrapper">
<i className="icon fa-solid fa-chart-line"></i>
</div>
<div className="content-section-inner">
<div className="content-section-title">Telemetry</div>
<div className="content-section-text">
We collect minimal anonymous{" "}
<a target="_blank" href="https://docs.waveterm.dev/reference/telemetry" rel={"noopener"}>
telemetry data
</a>{" "}
to help us understand how people are using Wave (
<a
className="plain-link"
target="_blank"
href="https://waveterm.dev/privacy"
rel="noopener"
>
Privacy Policy
</a>
).
</div>
<Toggle checked={telemetryEnabled} onChange={setTelemetry} label={label} />
</div>
</div>
</div>
<footer className="unselectable">
<div className="button-wrapper">
<Button className="font-weight-600 primary" onClick={acceptTos}>
Continue
</Button>
</div>
</footer>
</>
);
};
const ModalPage2 = () => {
const [tosOpen, setTosOpen] = useAtom(modalsModel.tosOpen);
const handleGetStarted = () => {
setTosOpen(false);
};
return (
<>
<header className="modal-header tos-header unselectable">
<div className="logo">
<Logo />
</div>
<div className="modal-title">Icons and Keybindings</div>
</header>
<div className="modal-content tos-content unselectable">
<QuickTips />
</div>
<footer className="unselectable">
<div className="button-wrapper">
<Button className="font-weight-600 primary" onClick={handleGetStarted}>
Get Started
</Button>
</div>
</footer>
</>
);
};
const TosModal = () => {
const [telemetryEnabled, setTelemetryEnabled] = useState<boolean>(true);
const modalRef = useRef<HTMLDivElement | null>(null);
const [pageNum, setPageNum] = useAtom(pageNumAtom);
const clientData = useAtomValue(atoms.client);
const updateModalHeight = () => {
const windowHeight = window.innerHeight;
@ -30,6 +173,16 @@ const TosModal = () => {
}
};
useEffect(() => {
// on unmount, always reset pagenum
if (clientData.tosagreed) {
setPageNum(2);
}
return () => {
setPageNum(1);
};
}, []);
useEffect(() => {
updateModalHeight(); // Run on initial render
@ -39,118 +192,10 @@ const TosModal = () => {
};
}, []);
const acceptTos = () => {
services.ClientService.AgreeTos();
};
const setTelemetry = (value: boolean) => {
RpcApi.SetConfigCommand(WindowRpcClient, { "telemetry:enabled": value })
.then(() => {
setTelemetryEnabled(value);
})
.catch((error) => {
console.error("failed to set telemetry:", error);
});
};
useEffect(() => {
services.FileService.GetFullConfig()
.then((data) => {
if ("telemetry:enabled" in data.settings) {
setTelemetryEnabled(true);
} else {
setTelemetryEnabled(false);
}
})
.catch((error) => {
console.error("failed to get config:", error);
});
}, []);
const label = telemetryEnabled ? "Telemetry Enabled" : "Telemetry Disabled";
return (
<FlexiModal className="tos-modal" ref={modalRef}>
<OverlayScrollbarsComponent className="modal-inner" options={{ scrollbars: { autoHide: "leave" } }}>
<header className="modal-header tos-header unselectable">
<div className="logo">
<Logo />
</div>
<div className="modal-title">Welcome to Wave Terminal</div>
</header>
<div className="modal-content tos-content unselectable">
<div className="content-section">
<div className="icon-wrapper">
<a target="_blank" href="https://github.com/wavetermdev/waveterm" rel={"noopener"}>
<i className="icon fa-brands fa-github"></i>
</a>
</div>
<div className="content-section-inner">
<div className="content-section-title">Support us on GitHub</div>
<div className="content-section-text">
We're <i>open source</i> and committed to providing a free terminal for individual
users. Please show your support by giving us a star on{" "}
<a target="_blank" href="https://github.com/wavetermdev/waveterm" rel={"noopener"}>
Github&nbsp;(wavetermdev/waveterm)
</a>
</div>
</div>
</div>
<div className="content-section">
<div className="icon-wrapper">
<a target="_blank" href="https://discord.gg/XfvZ334gwU" rel={"noopener"}>
<i className="icon fa-solid fa-people-group"></i>
</a>
</div>
<div className="content-section-inner">
<div className="content-section-title">Join our Community</div>
<div className="content-section-text">
Get help, submit feature requests, report bugs, or just chat with fellow terminal
enthusiasts.
<br />
<a target="_blank" href="https://discord.gg/XfvZ334gwU" rel={"noopener"}>
Join the Wave&nbsp;Discord&nbsp;Channel
</a>
</div>
</div>
</div>
<div className="content-section">
<div className="icon-wrapper">
<i className="icon fa-solid fa-chart-line"></i>
</div>
<div className="content-section-inner">
<div className="content-section-title">Telemetry</div>
<div className="content-section-text">
We collect minimal anonymous{" "}
<a
target="_blank"
href="https://docs.waveterm.dev/reference/telemetry"
rel={"noopener"}
>
telemetry data
</a>{" "}
to help us understand how people are using Wave (
<a
className="plain-link"
target="_blank"
href="https://waveterm.dev/privacy"
rel="noopener"
>
Privacy Policy
</a>
).
</div>
<Toggle checked={telemetryEnabled} onChange={setTelemetry} label={label} />
</div>
</div>
</div>
<footer className="unselectable">
<div className="button-wrapper">
<Button className="font-weight-600" onClick={acceptTos}>
Get Started
</Button>
</div>
</footer>
{pageNum === 1 ? <ModalPage1 /> : <ModalPage2 />}
</OverlayScrollbarsComponent>
</FlexiModal>
);

View File

@ -6,8 +6,10 @@ import { globalStore } from "./global";
class ModalsModel {
modalsAtom: jotai.PrimitiveAtom<Array<{ displayName: string; props?: any }>>;
tosOpen: jotai.PrimitiveAtom<boolean>;
constructor() {
this.tosOpen = jotai.atom(false);
this.modalsAtom = jotai.atom([]);
}

View File

@ -26,6 +26,10 @@
--block-bg-solid-color: rgb(0, 0, 0);
--block-border-radius: 8px;
--keybinding-color: #e0e0e0;
--keybinding-bg-color: #333;
--keybinding-border-color: #444;
/* scrollbar colors */
--scrollbar-background-color: transparent;
--scrollbar-thumb-color: rgba(255, 255, 255, 0.15);

View File

@ -148,7 +148,7 @@ a codeedit block which you can use to quickly edit the file using Wave's embedde
### edit
\`\`\`
wsh edit [path]
wsh editor [path]
\`\`\`
This will open up codeedit for the specified file. This is useful for quickly editing files on a local or remote machine in our graphical editor. This command will wait until the file is closed before exiting (unlike \`view\`) so you can set your \`$EDITOR\` to \`wsh edit\` for a seamless experience. You can combine this with a \`-m\` flag to open the editor in magnified mode.

View File

@ -7,6 +7,7 @@ import {
registerElectronReinjectKeyHandler,
registerGlobalKeys,
} from "@/app/store/keymodel";
import { modalsModel } from "@/app/store/modalmodel";
import { FileService, ObjectService } from "@/app/store/services";
import { RpcApi } from "@/app/store/wshclientapi";
import { initWshrpc, WindowRpcClient } from "@/app/store/wshrpcutil";
@ -52,6 +53,7 @@ loadFonts();
(window as any).countersClear = countersClear;
(window as any).getLayoutModelForActiveTab = getLayoutModelForActiveTab;
(window as any).pushFlashError = pushFlashError;
(window as any).modalsModel = modalsModel;
document.title = `The Next Wave (${windowId.substring(0, 8)})`;