diff --git a/emain/emain.ts b/emain/emain.ts
index e02edd32..ee76d548 100644
--- a/emain/emain.ts
+++ b/emain/emain.ts
@@ -690,7 +690,10 @@ function getAppMenu() {
];
const appMenu: Electron.MenuItemConstructorOptions[] = [
{
- role: "about",
+ label: "About Wave Terminal",
+ click: (_, window) => {
+ window?.webContents.send("menu-item-about");
+ },
},
{
label: "Check for Updates",
diff --git a/emain/preload.ts b/emain/preload.ts
index 866f1e8f..77d44dc6 100644
--- a/emain/preload.ts
+++ b/emain/preload.ts
@@ -24,6 +24,7 @@ contextBridge.exposeInMainWorld("api", {
onUpdaterStatusChange: (callback) => ipcRenderer.on("app-update-status", (_event, status) => callback(status)),
getUpdaterStatus: () => ipcRenderer.sendSync("get-app-update-status"),
installAppUpdate: () => ipcRenderer.send("install-app-update"),
+ onMenuItemAbout: (callback) => ipcRenderer.on("menu-item-about", callback),
updateWindowControlsOverlay: (rect) => ipcRenderer.send("update-window-controls-overlay", rect),
});
diff --git a/frontend/app/element/button.less b/frontend/app/element/button.less
index 7186472d..03f49e8c 100644
--- a/frontend/app/element/button.less
+++ b/frontend/app/element/button.less
@@ -2,150 +2,102 @@
/* SPDX-License-Identifier: Apache-2.0 */
.button {
- background: var(--accent-color);
- border: none;
- cursor: pointer;
- outline: inherit;
- display: flex;
- padding: 6px 16px;
- align-items: center;
- gap: 4px;
- border-radius: 6px;
- height: auto;
- line-height: 1.5;
- white-space: nowrap;
- user-select: none;
- font-weight: 500;
+ padding: 1px;
+ border: 1px solid transparent;
+ border-radius: 8px;
- color: var(--main-text-color);
-
- i {
- fill: var(--main-text-color);
+ &:focus,
+ &.focus {
+ border: 1px solid var(--button-focus-border-color);
}
- &.primary,
- &.secondary {
- color: var(--main-text-color);
+ .button-inner {
background: var(--accent-color);
+ border: none;
+ cursor: pointer;
+ outline: inherit;
+ display: flex;
+ padding: 8px 20px;
+ align-items: center;
+ gap: 4px;
+ border-radius: 6px;
+ height: auto;
+ line-height: 16px;
+ white-space: nowrap;
+ user-select: none;
+ font-size: 14px;
+
+ color: var(--main-text-color);
i {
fill: var(--main-text-color);
}
- }
- &.primary.danger {
- background: var(--error-color);
- }
-
- &.primary.warning {
- background: #e6ba1e;
- color: #000000;
- }
-
- &.primary.outlined,
- &.primary.greyoutlined {
- background: none;
- border: 1px solid var(--accent-color);
-
- i {
- fill: var(--accent-color);
- }
- }
-
- &.primary.greyoutlined {
- border-color: var(--secondary-text-color);
-
- i {
- fill: var(--secondary-text-color);
- }
- }
-
- &.primary.outlined.danger {
- border-color: var(--error-color);
-
- i {
- fill: var(--error-color);
- }
- }
-
- &.primary.outlined,
- &.primary.greyoutlined {
- &.hover-danger:hover {
+ &.primary,
+ &.secondary {
color: var(--main-text-color);
- border-color: var(--error-color);
+ background: var(--accent-color);
+
+ i {
+ fill: var(--main-text-color);
+ }
+ }
+
+ &.primary.danger {
background: var(--error-color);
}
- }
- &.greytext {
- color: var(--secondary-text-color);
- }
+ &.primary.warning {
+ background: #e6ba1e;
+ color: #000000;
+ }
- &.primary.ghost {
- background: none;
+ &.primary.ghost {
+ background: none;
- i {
- fill: var(--accent-color);
+ i {
+ fill: var(--accent-color);
+ }
+ }
+
+ &.primary.ghost.danger {
+ i {
+ fill: var(--app-error-color);
+ }
+ }
+
+ &.secondary,
+ &.link-button {
+ background: var(--highlight-bg-color);
+ }
+
+ &.secondary.ghost {
+ background: none;
+ }
+
+ &.secondary.danger {
+ color: var(--error-color);
+ }
+
+ &.disabled {
+ opacity: 0.5;
}
}
- &.primary.ghost.danger {
- i {
- fill: var(--app-error-color);
- }
+ /*
+ * customs styles here
+ */
+ .border-radius-4 {
+ border-radius: 4px;
}
- &.secondary {
- background: var(--highlight-bg-color);
+ .vertical-padding-2 {
+ padding-top: 2px;
+ padding-bottom: 2px;
}
- &.secondary.outlined {
- background: none;
- border: 1px solid var(--main-text-color);
- }
-
- &.secondary.outlined.danger {
- border-color: var(--error-color);
- }
-
- &.secondary.ghost {
- background: none;
- }
-
- &.secondary.danger {
- color: var(--error-color);
- }
-
- &.small {
- padding: 4px 8px;
- font-size: 12px;
- border-radius: 3.6px;
- }
-
- &.term-inline {
- padding: 2px 8px;
- border-radius: 3px;
- }
-
- &.disabled {
- opacity: 0.5;
- }
-
- &.link {
- cursor: pointer;
+ .horizontal-padding-10 {
+ padding-left: 10px;
+ padding-right: 10px;
}
}
-
-.border-radius-4 {
- border-radius: 4px;
-}
-
-.vertical-padding-2 {
- padding-top: 2px;
- padding-bottom: 2px;
-}
-
-.horizontal-padding-10 {
- padding-left: 10px;
- padding-right: 10px;
-}
diff --git a/frontend/app/element/button.tsx b/frontend/app/element/button.tsx
index 4f3a0455..ef650d1c 100644
--- a/frontend/app/element/button.tsx
+++ b/frontend/app/element/button.tsx
@@ -14,16 +14,18 @@ const Button = React.memo(({ className = "primary", children, disabled, ...props
);
return (
-
+
+
+
);
});
diff --git a/frontend/app/element/linkbutton.less b/frontend/app/element/linkbutton.less
new file mode 100644
index 00000000..954d638f
--- /dev/null
+++ b/frontend/app/element/linkbutton.less
@@ -0,0 +1,12 @@
+// Copyright 2024, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+.button {
+ &.link-button {
+ text-decoration: none;
+
+ .button-inner {
+ padding: 8px 12px;
+ }
+ }
+}
diff --git a/frontend/app/element/linkbutton.tsx b/frontend/app/element/linkbutton.tsx
new file mode 100644
index 00000000..3343d15e
--- /dev/null
+++ b/frontend/app/element/linkbutton.tsx
@@ -0,0 +1,37 @@
+// Copyright 2024, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { clsx } from "clsx";
+import * as React from "react";
+
+import "./linkbutton.less";
+
+interface LinkButtonProps {
+ href: string;
+ rel?: string;
+ target?: string;
+ children: React.ReactNode;
+ disabled?: boolean;
+ leftIcon?: React.ReactNode;
+ rightIcon?: React.ReactNode;
+ style?: React.CSSProperties;
+ autoFocus?: boolean;
+ className?: string;
+ termInline?: boolean;
+ title?: string;
+ onClick?: (e: React.MouseEvent) => void;
+}
+
+const LinkButton = ({ leftIcon, rightIcon, children, className, ...rest }: LinkButtonProps) => {
+ return (
+
+
+ {leftIcon && {leftIcon}}
+ {children}
+ {rightIcon && {rightIcon}}
+
+
+ );
+};
+
+export { LinkButton };
diff --git a/frontend/app/modals/about.less b/frontend/app/modals/about.less
new file mode 100644
index 00000000..9b4a2b30
--- /dev/null
+++ b/frontend/app/modals/about.less
@@ -0,0 +1,61 @@
+// Copyright 2024, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+.about-modal {
+ width: 445px;
+ padding-bottom: 34px;
+
+ .modal-content {
+ margin-bottom: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 24px;
+
+ .section-wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 26px;
+ width: 100%;
+ }
+
+ .section {
+ align-items: center;
+ gap: 16px;
+ align-self: stretch;
+ width: 100%;
+ text-align: center;
+
+ &.logo-section {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ text-align: center;
+
+ .app-name {
+ font-size: 25px;
+ }
+
+ .text-standard {
+ line-height: 20px;
+ }
+ }
+ }
+
+ .section:nth-child(3) {
+ display: flex;
+ align-items: flex-start;
+ gap: 7px;
+
+ .wave-button-link {
+ display: flex;
+ align-items: center;
+
+ i {
+ font-size: 16px;
+ }
+ }
+ }
+ }
+}
diff --git a/frontend/app/modals/about.tsx b/frontend/app/modals/about.tsx
new file mode 100644
index 00000000..de4b8c92
--- /dev/null
+++ b/frontend/app/modals/about.tsx
@@ -0,0 +1,61 @@
+import Logo from "@/app/asset/logo.svg";
+import { LinkButton } from "@/app/element/linkbutton";
+import { modalsModel } from "@/app/store/modalmodel";
+import { Modal } from "./modal";
+
+import "./about.less";
+
+interface AboutModalProps {}
+
+const AboutModal = ({}: AboutModalProps) => {
+ const currentDate = new Date();
+
+ return (
+ modalsModel.popModal()}>
+
+
+
+
Wave Terminal
+
+ Open-Source AI-Native Terminal
+
+ Built for Seamless Workflows
+
+
+
Client Version 0.1.8 (20240615-002636)
+
+ }
+ >
+ Github
+
+ }
+ >
+ Website
+
+ }
+ >
+ Acknowledgements
+
+
+
© {currentDate.getFullYear()} Command Line Inc.
+
+
+ );
+};
+
+AboutModal.displayName = "AboutModal";
+
+export { AboutModal };
diff --git a/frontend/app/modals/modal.less b/frontend/app/modals/modal.less
index dc9eff73..fabac766 100644
--- a/frontend/app/modals/modal.less
+++ b/frontend/app/modals/modal.less
@@ -24,51 +24,61 @@
}
.modal {
+ position: relative;
z-index: var(--zindex-modal);
display: flex;
flex-direction: column;
align-items: flex-start;
- gap: 16px;
- border-radius: 10px;
+ gap: 32px;
+ padding: 24px 16px 16px;
+ border-radius: 8px;
+ border: 0.5px solid var(--modal-border-color);
background: var(--modal-bg-color);
- border: 1px solid var(--border-color);
- box-shadow: 0px 5px 5px 5px rgba(0, 0, 0, 0.1);
+ box-shadow: 0px 8px 32px 0px rgba(0, 0, 0, 0.25);
- .modal-header {
- width: 100%;
+ .header-content-wrapper {
display: flex;
- align-items: center;
- padding: 12px 14px 12px 20px;
- justify-content: space-between;
- line-height: 20px;
- border-bottom: 1px solid var(--modal-header-bottom-border-color);
- user-select: none;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
- .modal-title {
- color: var(--main-text-color);
- font-size: var(--title-font-size);
- }
+ .modal-header {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ user-select: none;
- button {
- padding-right: 4px !important;
- i {
- font-size: 18px;
+ .modal-title {
+ color: var(--main-text-color);
+ font-size: var(--title-font-size);
+ }
+
+ button {
+ position: absolute;
+ right: 8px;
+ top: 8px;
+ padding: 8px 16px;
+
+ i {
+ font-size: 18px;
+ }
}
}
- }
-
- .modal-body {
- width: 100%;
- padding: 0px 20px;
+ .modal-content {
+ width: 100%;
+ padding: 0px 20px;
+ }
}
.modal-footer {
display: flex;
justify-content: flex-end;
width: 100%;
- padding: 0 20px 20px;
+ padding-top: 16px;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
- button:last-child {
+ .button:last-child {
margin-left: 8px;
}
}
diff --git a/frontend/app/modals/modal.tsx b/frontend/app/modals/modal.tsx
index 6a891ad2..fca72e69 100644
--- a/frontend/app/modals/modal.tsx
+++ b/frontend/app/modals/modal.tsx
@@ -2,19 +2,18 @@
// SPDX-License-Identifier: Apache-2.0
import { Button } from "@/app/element/button";
+import clsx from "clsx";
import ReactDOM from "react-dom";
import "./modal.less";
interface ModalHeaderProps {
- title: React.ReactNode;
description?: string;
onClose?: () => void;
}
-const ModalHeader = ({ onClose, title, description }: ModalHeaderProps) => (
+const ModalHeader = ({ onClose, description }: ModalHeaderProps) => (
- {typeof title === "string" ? {title}
: title}
{description && {description}
}
{onClose && (