diff --git a/package-lock.json b/package-lock.json
index ed7a356c..72d37f2c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
- "name": "Prompt",
- "version": "0.3.1",
+ "name": "waveterm",
+ "version": "0.5.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2289,6 +2289,15 @@
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true
},
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
"emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -2306,6 +2315,40 @@
"strip-ansi": "^7.0.1"
}
},
+ "string-width-cjs": {
+ "version": "npm:string-width@4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
+ }
+ },
"strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
@@ -2315,6 +2358,23 @@
"ansi-regex": "^6.0.1"
}
},
+ "strip-ansi-cjs": {
+ "version": "npm:strip-ansi@6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ }
+ }
+ },
"wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -2325,6 +2385,60 @@
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
}
+ },
+ "wrap-ansi-cjs": {
+ "version": "npm:wrap-ansi@7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
+ }
}
}
},
@@ -4846,25 +4960,6 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"dev": true
},
- "electron": {
- "version": "25.4.0",
- "resolved": "https://registry.npmjs.org/electron/-/electron-25.4.0.tgz",
- "integrity": "sha512-VLTRxDhL4UvQbqM7pTNENnJo62cdAPZT92N+B7BZQ5Xfok1wuVPEewIjBot4K7U3EpLUuHn1veeLzho3ihiP+Q==",
- "dev": true,
- "requires": {
- "@electron/get": "^2.0.0",
- "@types/node": "^18.11.18",
- "extract-zip": "^2.0.1"
- },
- "dependencies": {
- "@types/node": {
- "version": "18.17.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz",
- "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==",
- "dev": true
- }
- }
- },
"electron-installer-common": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz",
@@ -8897,6 +8992,53 @@
}
}
},
+ "raw-loader": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
+ "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ }
+ }
+ }
+ },
"rcedit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-3.1.0.tgz",
@@ -9897,17 +10039,6 @@
"strip-ansi": "^6.0.1"
}
},
- "string-width-cjs": {
- "version": "npm:string-width@4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -9925,15 +10056,6 @@
"ansi-regex": "^5.0.1"
}
},
- "strip-ansi-cjs": {
- "version": "npm:strip-ansi@6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -11050,17 +11172,6 @@
"strip-ansi": "^6.0.0"
}
},
- "wrap-ansi-cjs": {
- "version": "npm:wrap-ansi@7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- }
- },
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/package.json b/package.json
index ec3e0e52..e9813da2 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,7 @@
"lodash-webpack-plugin": "^0.11.6",
"mini-css-extract-plugin": "^2.6.0",
"prettier": "^2.8.8",
+ "raw-loader": "^4.0.2",
"react-split-it": "^2.0.0",
"style-loader": "^3.3.1",
"typescript": "^4.7.3",
diff --git a/src/app/app.less b/src/app/app.less
index 68608e3b..f082b661 100644
--- a/src/app/app.less
+++ b/src/app/app.less
@@ -197,7 +197,8 @@ a.a-block {
height: 100%;
.history-view,
- .bookmarks-view {
+ .bookmarks-view,
+ .plugins-view {
flex-grow: 1;
display: flex;
flex-direction: column;
diff --git a/src/app/app.tsx b/src/app/app.tsx
index 003ad822..f60e6da2 100644
--- a/src/app/app.tsx
+++ b/src/app/app.tsx
@@ -11,6 +11,8 @@ import type { ContextMenuOpts } from "../types/types";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel } from "../model/model";
import { isBlank } from "../util/util";
+import { WorkspaceView } from "./workspace/workspaceview";
+import { PluginsView } from "./pluginsview/pluginsview";
import { BookmarksView } from "./bookmarks/bookmarks";
import { HistoryView } from "./history/history";
import {
@@ -21,7 +23,6 @@ import {
} from "./common/modals/settings";
import { RemotesModal } from "./connections/connections";
import { TosModal } from "./common/modals/modals";
-import { WorkspaceView } from "../app/workspace/workspaceview";
import { MainSideBar } from "./sidebar/MainSideBar";
import { DisconnectedModal, ClientStopModal, AlertModal, WelcomeModal } from "./common/modals/modals";
import { ErrorBoundary } from "./common/error/errorboundary";
@@ -107,11 +108,13 @@ class App extends React.Component<{}, {}> {
if (dcWait) {
setTimeout(() => this.updateDcWait(false), 0);
}
+ //console.log(`GlobalModel.activeMainView.get() = ${GlobalModel.activeMainView.get()}`); // @mike - if I remove this, I cant see plugins
return (
+
diff --git a/src/app/pluginsview/pluginsview.less b/src/app/pluginsview/pluginsview.less
new file mode 100644
index 00000000..e8233ee5
--- /dev/null
+++ b/src/app/pluginsview/pluginsview.less
@@ -0,0 +1,137 @@
+@import "../../app/common/themes/themes.less";
+
+.plugins-view {
+ background-color: @background-session;
+
+ .header {
+ margin: 1.5em 1.5em 0.5em;
+
+ .plugins-title {
+ margin-bottom: 0.5em;
+ font-weight: bold;
+ font-size: 1.5em;
+ }
+
+ .close-button {
+ position: absolute;
+ right: 1em;
+ top: 0.8em;
+ cursor: pointer;
+ width: 1.5em;
+ height: 1.5em;
+ border-radius: 50%;
+ svg {
+ width: 1.5em;
+ height: 1.5em;
+ fill: @base-color;
+ }
+ }
+ }
+
+ .body {
+ display: flex;
+ .plugins-list {
+ padding: 1em;
+ border-right: 1px solid @base-border;
+ min-height: calc(100vh - 6em);
+ max-height: calc(100vh - 6em);
+ overflow-y: auto;
+ flex: 0 0 auto;
+ .plugin-summary {
+ display: block;
+ padding: 10px;
+ max-width: 323px;
+ border-radius: 8px;
+ margin-bottom: 1em;
+ border: 1px solid transparent;
+ &.selected {
+ border-color: @prompt-green;
+ }
+ .plugin-summary-header {
+ display: flex;
+ .plugin-summary-icon {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ svg {
+ width: 32px;
+ height: 32px;
+ fill: white;
+ }
+ }
+ .plugin-summary-info {
+ flex: 1;
+ margin: 0 12px;
+ .plugin-summary-title {
+ }
+ .plugin-summary-vendor {
+ font-size: 0.8em;
+ }
+ }
+ }
+ .plugin-summary-body {
+ margin-top: 8px;
+ min-height: 54px;
+ max-height: 54px;
+ overflow-y: auto;
+ }
+ }
+ }
+ .plugins-details {
+ flex: 1 !important;
+ padding: 2em;
+ min-height: calc(100vh - 6em);
+ max-height: calc(100vh - 6em);
+ overflow-y: auto;
+ flex: 0 0 auto;
+ .plugin-label {
+ min-width: 100%;
+ margin-bottom: 1em;
+ }
+ .plugin-summary-header {
+ display: flex;
+ .plugin-summary-icon {
+ svg {
+ width: 48px;
+ height: 48px;
+ fill: white;
+ }
+ }
+ .plugin-summary-info {
+ flex: 1;
+ margin: 0 12px;
+ .plugin-summary-title {
+ font-size: 1.1em;
+ margin-top: 0.3em;
+ }
+ .plugin-summary-vendor {
+ font-size: 0.9em;
+ margin-top: 0.4em;
+ }
+ }
+ }
+ .plugin-summary-body {
+ margin-top: 1em;
+ }
+ .plugin-screenshots-container {
+ margin-top: 3em;
+ .plugin-screenshots {
+ display: flex;
+ overflow-x: auto;
+ width: 100%;
+ white-space: nowrap;
+ align-items: flex-start;
+ img {
+ flex-shrink: 0;
+ margin-right: 10px;
+ max-height: 50vh;
+ }
+ }
+ }
+ .plugin-readme {
+ margin-top: 3em;
+ }
+ }
+ }
+}
diff --git a/src/app/pluginsview/pluginsview.tsx b/src/app/pluginsview/pluginsview.tsx
new file mode 100644
index 00000000..94e1ceff
--- /dev/null
+++ b/src/app/pluginsview/pluginsview.tsx
@@ -0,0 +1,101 @@
+// Copyright 2023, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import * as React from "react";
+import * as mobxReact from "mobx-react";
+import * as mobx from "mobx";
+import { boundMethod } from "autobind-decorator";
+import { GlobalModel } from "../../model/model";
+import { PluginModel } from "../../plugins/plugins";
+import { ImageDisplay, Markdown } from "../common/common";
+
+import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg";
+
+import "./pluginsview.less";
+
+@mobxReact.observer
+class PluginsView extends React.Component<{}, {}> {
+ @boundMethod
+ closeView(): void {
+ GlobalModel.pluginsModel.closeView();
+ }
+
+ render() {
+ if (GlobalModel.activeMainView.get() !== "plugins") {
+ return <>>;
+ }
+ const { pluginsModel } = GlobalModel;
+ console.log(`rendering details for ${pluginsModel.selectedPlugin.get().name}`);
+ const PluginList = () => (
+
+ {PluginModel.allPlugins().map((plugin, i) => (
+
pluginsModel.setSelectedPlugin(plugin)}
+ >
+
+
{plugin.getIcon()}
+
+
{plugin.title}
+
{plugin.vendor}
+
+
+
{plugin.summary}
+
+ ))}
+
+ );
+
+ const PluginDetails = () => {
+ const plugin = pluginsModel.selectedPlugin.get();
+ return (
+
+
+
{plugin.getIcon()}
+
+
{plugin.title}
+
{plugin.vendor}
+
+
+
{plugin.summary}
+ {plugin.screenshots && plugin.screenshots.length > 0 && (
+
+
{"Screenshots"}
+
+ {plugin.screenshots.map((path, index) => (
+

+ ))}
+
+
+ )}
+ {plugin.readme && (
+
+ )}
+
+ );
+ };
+
+ return (
+
+ );
+ }
+}
+
+export { PluginsView };
diff --git a/src/app/sidebar/MainSideBar.tsx b/src/app/sidebar/MainSideBar.tsx
index 13144253..c5a17173 100644
--- a/src/app/sidebar/MainSideBar.tsx
+++ b/src/app/sidebar/MainSideBar.tsx
@@ -76,12 +76,19 @@ class MainSideBar extends React.Component<{}, {}> {
GlobalCommandRunner.showRemote(remote.remoteid);
}
+ @boundMethod
+ handlePluginsClick(): void {
+ if (GlobalModel.activeMainView.get() == "plugins") {
+ GlobalModel.showSessionView();
+ return;
+ }
+ GlobalModel.pluginsModel.showPluginsView();
+ }
+
@boundMethod
handleHistoryClick(): void {
if (GlobalModel.activeMainView.get() == "history") {
- mobx.action(() => {
- GlobalModel.activeMainView.set("session");
- })();
+ GlobalModel.showSessionView();
return;
}
GlobalModel.historyViewModel.reSearch();
@@ -150,7 +157,6 @@ class MainSideBar extends React.Component<{}, {}> {
}
return sessionList.map((session, index) => {
const isActive = GlobalModel.activeMainView.get() == "session" && activeSessionId == session.sessionId;
- /** @TODO: Handle archived sessions and talk to Mike about session settings */
return (
{
- {/*
History
diff --git a/src/model/model.ts b/src/model/model.ts
index f658fb68..5484fc75 100644
--- a/src/model/model.ts
+++ b/src/model/model.ts
@@ -15,6 +15,7 @@ import {
isModKeyPress,
} from "../util/util";
import { TermWrap } from "../plugins/terminal/term";
+import { PluginModel } from "../plugins/plugins";
import type {
SessionDataType,
LineType,
@@ -2423,7 +2424,36 @@ class BookmarksModel {
return;
}
}
- return;
+}
+
+class PluginsModel {
+ selectedPlugin: OV
= mobx.observable.box(null, { name: "selectedPlugin" });
+
+ showPluginsView(): void {
+ mobx.action(() => {
+ this.reset();
+ GlobalModel.activeMainView.set("plugins");
+ const allPlugins = PluginModel.allPlugins();
+ this.selectedPlugin.set(allPlugins.length > 0 ? allPlugins[0] : null);
+ })();
+ }
+
+ setSelectedPlugin(plugin: RendererPluginType): void {
+ mobx.action(() => {
+ this.selectedPlugin.set(plugin);
+ })();
+ }
+
+ reset(): void {
+ mobx.action(() => {
+ this.selectedPlugin.set(null);
+ })();
+ }
+
+ closeView(): void {
+ GlobalModel.showSessionView();
+ setTimeout(() => GlobalModel.inputModel.giveFocus(), 50);
+ }
}
class RemotesModalModel {
@@ -2658,7 +2688,7 @@ class Model {
authKey: string;
isDev: boolean;
platform: string;
- activeMainView: OV<"session" | "history" | "bookmarks" | "webshare"> = mobx.observable.box("session", {
+ activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare"> = mobx.observable.box("session", {
name: "activeMainView",
});
termFontSize: CV;
@@ -2684,6 +2714,7 @@ class Model {
remotesModalModel: RemotesModalModel;
inputModel: InputModel;
+ pluginsModel: PluginsModel;
bookmarksModel: BookmarksModel;
historyViewModel: HistoryViewModel;
clientData: OV = mobx.observable.box(null, {
@@ -2702,6 +2733,7 @@ class Model {
);
this.ws.reconnect();
this.inputModel = new InputModel();
+ this.pluginsModel = new PluginsModel();
this.bookmarksModel = new BookmarksModel();
this.historyViewModel = new HistoryViewModel();
this.remotesModalModel = new RemotesModalModel();
@@ -3203,7 +3235,9 @@ class Model {
this.updateRemotes(update.remotes);
}
if ("mainview" in update) {
- if (update.mainview == "bookmarks") {
+ if (update.mainview == "plugins") {
+ this.pluginsModel.showPluginsView();
+ } else if (update.mainview == "bookmarks") {
this.bookmarksModel.showBookmarksView(update.bookmarks, update.selectedbookmark);
} else if (update.mainview == "session") {
this.activeMainView.set("session");
diff --git a/src/plugins/code/icon.svg b/src/plugins/code/icon.svg
new file mode 100644
index 00000000..be5696e3
--- /dev/null
+++ b/src/plugins/code/icon.svg
@@ -0,0 +1,12 @@
+
diff --git a/src/plugins/code/meta.json b/src/plugins/code/meta.json
new file mode 100644
index 00000000..baf22987
--- /dev/null
+++ b/src/plugins/code/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "Code Viewer",
+ "vendor": "Wave",
+ "summary": "View and Edit source code with syntax highlightng and code completion."
+}
diff --git a/src/plugins/code/readme.md b/src/plugins/code/readme.md
new file mode 100644
index 00000000..8319f338
--- /dev/null
+++ b/src/plugins/code/readme.md
@@ -0,0 +1,8 @@
+# Code Editor for Wave Terminal
+
+These instructions are for setting up the build on MacOS.
+If you're developing on Linux please use the [Linux Build Instructions](./build-linux.md).
+
+## Running the Development Version of Wave
+
+If you install the production version of Wave, you'll see a semi-transparent sidebar, and the data for Wave is stored in the directory ~/prompt. The development version has a red/brown sidebar and stores its data in ~/prompt-dev. This allows the production and development versions to be run simultaneously with no conflicts. If the dev database is corrupted by development bugs, or the schema changes in development it will not affect the production copy.
diff --git a/src/plugins/code/screenshots/1.png b/src/plugins/code/screenshots/1.png
new file mode 100644
index 00000000..9743341d
Binary files /dev/null and b/src/plugins/code/screenshots/1.png differ
diff --git a/src/plugins/csv/icon.svg b/src/plugins/csv/icon.svg
new file mode 100644
index 00000000..5377e8f8
--- /dev/null
+++ b/src/plugins/csv/icon.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/plugins/csv/meta.json b/src/plugins/csv/meta.json
new file mode 100644
index 00000000..63d87873
--- /dev/null
+++ b/src/plugins/csv/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "CSV Viewer",
+ "vendor": "Wave",
+ "summary": "View CSV files inline from within the terminal. I am now trying an animated gif :)"
+}
diff --git a/src/plugins/csv/screenshots/csvview.gif b/src/plugins/csv/screenshots/csvview.gif
new file mode 100644
index 00000000..98c6c8a3
Binary files /dev/null and b/src/plugins/csv/screenshots/csvview.gif differ
diff --git a/src/plugins/image/icon.svg b/src/plugins/image/icon.svg
new file mode 100644
index 00000000..c1637e15
--- /dev/null
+++ b/src/plugins/image/icon.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/plugins/image/meta.json b/src/plugins/image/meta.json
new file mode 100644
index 00000000..7b54c93e
--- /dev/null
+++ b/src/plugins/image/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "ImageViewer",
+ "vendor": "Wave",
+ "summary": "View Images inline in the terminal."
+}
diff --git a/src/plugins/image/screenshots/imagviewlion.gif b/src/plugins/image/screenshots/imagviewlion.gif
new file mode 100644
index 00000000..786a6bc3
Binary files /dev/null and b/src/plugins/image/screenshots/imagviewlion.gif differ
diff --git a/src/plugins/markdown/icon.svg b/src/plugins/markdown/icon.svg
new file mode 100644
index 00000000..2d4ebcd0
--- /dev/null
+++ b/src/plugins/markdown/icon.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/plugins/markdown/meta.json b/src/plugins/markdown/meta.json
new file mode 100644
index 00000000..6442c41b
--- /dev/null
+++ b/src/plugins/markdown/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "Markdown",
+ "vendor": "Wave",
+ "summary": "Markdown is a lightweight markup language for creating formatted text using a plain-text editor."
+}
diff --git a/src/plugins/mustache/icon.svg b/src/plugins/mustache/icon.svg
new file mode 100644
index 00000000..4e11dd0b
--- /dev/null
+++ b/src/plugins/mustache/icon.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/plugins/mustache/meta.json b/src/plugins/mustache/meta.json
new file mode 100644
index 00000000..dc4ac3a7
--- /dev/null
+++ b/src/plugins/mustache/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "Mustache",
+ "vendor": "Wave",
+ "summary": "Mustache templates help create html files."
+}
diff --git a/src/plugins/openai/icon.svg b/src/plugins/openai/icon.svg
new file mode 100644
index 00000000..be5696e3
--- /dev/null
+++ b/src/plugins/openai/icon.svg
@@ -0,0 +1,12 @@
+
diff --git a/src/plugins/openai/meta.json b/src/plugins/openai/meta.json
new file mode 100644
index 00000000..eec8cb52
--- /dev/null
+++ b/src/plugins/openai/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "OpenAI",
+ "vendor": "Wave",
+ "summary": "OpenAI plugin allows chatting with OpenAI APIs."
+}
diff --git a/src/plugins/plugins.ts b/src/plugins/plugins.ts
index 0bc4c7a6..a9a8809c 100644
--- a/src/plugins/plugins.ts
+++ b/src/plugins/plugins.ts
@@ -11,89 +11,156 @@ import { OpenAIRenderer, OpenAIRendererModel } from "./openai/openai";
import { isBlank } from "../util/util";
import { sprintf } from "sprintf-js";
-const ImagePlugin: RendererPluginType = {
- name: "image",
- rendererType: "simple",
- heightType: "pixels",
- dataType: "blob",
- collapseType: "hide",
- globalCss: null,
- mimeTypes: ["image/*"],
- simpleComponent: SimpleImageRenderer,
-};
-
-const MarkdownPlugin: RendererPluginType = {
- name: "markdown",
- rendererType: "simple",
- heightType: "pixels",
- dataType: "blob",
- collapseType: "hide",
- globalCss: null,
- mimeTypes: ["text/markdown"],
- simpleComponent: SimpleMarkdownRenderer,
-};
-
-const MustachePlugin: RendererPluginType = {
- name: "mustache",
- rendererType: "simple",
- heightType: "pixels",
- dataType: "blob",
- collapseType: "hide",
- globalCss: null,
- mimeTypes: ["text/plain"],
- simpleComponent: SimpleMustacheRenderer,
-};
-
-const CodePlugin: RendererPluginType = {
- name: "code",
- rendererType: "simple",
- heightType: "pixels",
- dataType: "blob",
- collapseType: "hide",
- globalCss: null,
- mimeTypes: ["text/plain"],
- simpleComponent: SourceCodeRenderer,
-};
-
-const OpenAIPlugin: RendererPluginType = {
- name: "openai",
- rendererType: "full",
- heightType: "pixels",
- dataType: "model",
- collapseType: "remove",
- hidePrompt: true,
- globalCss: null,
- mimeTypes: ["application/json"],
- fullComponent: OpenAIRenderer,
- modelCtor: () => new OpenAIRendererModel(),
-};
-
-const CSVPlugin: RendererPluginType = {
- name: "csv",
- rendererType: "simple",
- heightType: "pixels",
- dataType: "blob",
- collapseType: "hide",
- globalCss: null,
- mimeTypes: ["text/csv"],
- simpleComponent: CSVRenderer,
-};
+// TODO: @mike - I did refactoring with the though that I can move config out of this plugins.ts file to a
+// plugins.json file. This way, adding a new plugin would reuire adding an entry to the json config. At a later
+// stage, a plugin can become a self-contained-bundle, which would have my_plugin.json into it. it will be easy to
+// merge this my_plugin.json into the big plugins.json. I got stuck while defining 'simpleComponent: SimpleImageRenderer'
+// in a json definition (something like Java.Reflection can be used to compose a class from its name. will try later)
+const PluginConfigs: RendererPluginType[] = [
+ {
+ name: "markdown",
+ rendererType: "simple",
+ heightType: "pixels",
+ dataType: "blob",
+ collapseType: "hide",
+ globalCss: null,
+ mimeTypes: ["text/markdown"],
+ simpleComponent: SimpleMarkdownRenderer,
+ },
+ {
+ name: "mustache",
+ rendererType: "simple",
+ heightType: "pixels",
+ dataType: "blob",
+ collapseType: "hide",
+ globalCss: null,
+ mimeTypes: ["text/plain"],
+ simpleComponent: SimpleMustacheRenderer,
+ },
+ {
+ name: "code",
+ rendererType: "simple",
+ heightType: "pixels",
+ dataType: "blob",
+ collapseType: "hide",
+ globalCss: null,
+ mimeTypes: ["text/plain"],
+ simpleComponent: SourceCodeRenderer,
+ },
+ {
+ name: "openai",
+ rendererType: "full",
+ heightType: "pixels",
+ dataType: "model",
+ collapseType: "remove",
+ hidePrompt: true,
+ globalCss: null,
+ mimeTypes: ["application/json"],
+ fullComponent: OpenAIRenderer,
+ modelCtor: () => new OpenAIRendererModel(),
+ },
+ {
+ name: "csv",
+ rendererType: "simple",
+ heightType: "pixels",
+ dataType: "blob",
+ collapseType: "hide",
+ globalCss: null,
+ mimeTypes: ["text/csv"],
+ simpleComponent: CSVRenderer,
+ },
+ {
+ name: "image",
+ rendererType: "simple",
+ heightType: "pixels",
+ dataType: "blob",
+ collapseType: "hide",
+ globalCss: null,
+ mimeTypes: ["image/*"],
+ simpleComponent: SimpleImageRenderer,
+ },
+];
class PluginModelClass {
rendererPlugins: RendererPluginType[] = [];
- registerRendererPlugin(plugin: RendererPluginType) {
- if (isBlank(plugin.name)) {
- throw new Error("invalid plugin, no name");
+ constructor(pluginConfigs: RendererPluginType[]) {
+ this.rendererPlugins = pluginConfigs.map((plugin: RendererPluginType): RendererPluginType => {
+ if (isBlank(plugin.name)) {
+ throw new Error("invalid plugin, no name");
+ }
+ if (plugin.name == "terminal" || plugin.name == "none") {
+ throw new Error(sprintf("invalid plugin, name '%s' is reserved", plugin.name));
+ }
+ let existingPlugin = this.getRendererPluginByName(plugin.name);
+ if (existingPlugin != null) {
+ throw new Error(sprintf("plugin with name %s already registered", plugin.name));
+ }
+ this.rendererPlugins.push(plugin);
+ this.loadPluginResources(plugin);
+ return plugin;
+ });
+ }
+
+ // attach all screenshots. webpack doesnt allow dynamic paths, hence, we have to put static paths for each plugin
+ attachScreenshots(plugin) {
+ let screenshotsContext;
+ let imagePaths = [];
+ try {
+ switch (plugin.name) {
+ case "image":
+ screenshotsContext = require.context(`../plugins/image/screenshots`, false, /\.(png|jpe?g|gif)$/);
+ break;
+ case "markdown":
+ screenshotsContext = require.context(
+ `../plugins/markdown/screenshots`,
+ false,
+ /\.(png|jpe?g|gif)$/
+ );
+ break;
+ case "mustache":
+ screenshotsContext = require.context(
+ `../plugins/mustache/screenshots`,
+ false,
+ /\.(png|jpe?g|gif)$/
+ );
+ break;
+ case "code":
+ screenshotsContext = require.context(`../plugins/code/screenshots`, false, /\.(png|jpe?g|gif)$/);
+ break;
+ case "openai":
+ screenshotsContext = require.context(`../plugins/openai/screenshots`, false, /\.(png|jpe?g|gif)$/);
+ break;
+ case "csv":
+ screenshotsContext = require.context(`../plugins/csv/screenshots`, false, /\.(png|jpe?g|gif)$/);
+ break;
+ default:
+ return;
+ }
+
+ imagePaths = screenshotsContext.keys().map(screenshotsContext);
+ } catch (error) {
+ console.error(`Failed to load screenshots for plugin ${plugin.name}`);
}
- if (plugin.name == "terminal" || plugin.name == "none") {
- throw new Error(sprintf("invalid plugin, name '%s' is reserved", plugin.name));
- }
- let existingPlugin = this.getRendererPluginByName(plugin.name);
- if (existingPlugin != null) {
- throw new Error(sprintf("plugin with name %s already registered", plugin.name));
- }
- this.rendererPlugins.push(plugin);
+ plugin.screenshots = imagePaths.map((path) => path.default);
+ }
+
+ // use dynamic import to attach the icon etc. ensure that the 'name' matches the dir the plugin is in
+ async loadPluginResources(plugin) {
+ this.attachScreenshots(plugin);
+ // attach other resources
+ const handleImportError = (error, resourceType) =>
+ console.error(`Failed to load ${resourceType} for plugin ${plugin.name}`);
+ const iconPromise = import(`../plugins/${plugin.name}/icon.svg`)
+ .then((icon) => (plugin.getIcon = icon.ReactComponent))
+ .catch((error) => handleImportError(error, "icon"));
+ const readmePromise = import(`../plugins/${plugin.name}/readme.md`)
+ .then((content) => (plugin.readme = content.default))
+ .catch((error) => handleImportError(error, "readme"));
+ const metaPromise = import(`../plugins/${plugin.name}/meta.json`)
+ .then((json) => Object.assign(plugin, json))
+ .catch((error) => handleImportError(error, "meta"));
+ return Promise.allSettled([iconPromise, readmePromise, metaPromise]);
}
getRendererPluginByName(name: string): RendererPluginType {
@@ -105,17 +172,15 @@ class PluginModelClass {
}
return null;
}
+
+ allPlugins() {
+ return this.rendererPlugins;
+ }
}
let PluginModel: PluginModelClass = null;
if ((window as any).PluginModel == null) {
- PluginModel = new PluginModelClass();
- PluginModel.registerRendererPlugin(ImagePlugin);
- PluginModel.registerRendererPlugin(MarkdownPlugin);
- PluginModel.registerRendererPlugin(CodePlugin);
- PluginModel.registerRendererPlugin(OpenAIPlugin);
- PluginModel.registerRendererPlugin(MustachePlugin);
- PluginModel.registerRendererPlugin(CSVPlugin);
+ PluginModel = new PluginModelClass(PluginConfigs);
(window as any).PluginModel = PluginModel;
}
diff --git a/webpack/webpack.web.js b/webpack/webpack.web.js
index 8313a0ab..c6478a67 100644
--- a/webpack/webpack.web.js
+++ b/webpack/webpack.web.js
@@ -65,7 +65,15 @@ var webCommon = {
},
{
test: /\.svg$/,
- use: ["@svgr/webpack", "file-loader"],
+ use: [{ loader: "@svgr/webpack", options: { icon: true, svgo: false } }, "file-loader"],
+ },
+ {
+ test: /\.md$/,
+ use: "raw-loader",
+ },
+ {
+ test: /\.(png|jpe?g|gif)$/i,
+ use: "file-loader",
},
],
},
@@ -128,4 +136,4 @@ if (BundleAnalyzerPlugin != null) {
webProd.plugins.push(new BundleAnalyzerPlugin());
}
-module.exports = {webDev: webDev, webProd: webProd};
+module.exports = { webDev: webDev, webProd: webProd };
diff --git a/yarn.lock b/yarn.lock
index 81ddceb9..5c60c64c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6427,6 +6427,14 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
+raw-loader@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6"
+ integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==
+ dependencies:
+ loader-utils "^2.0.0"
+ schema-utils "^3.0.0"
+
rcedit@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-3.1.0.tgz#1563ec7a5663de639f94c5dc85429db1da364b3e"