Compare commits

...

14 Commits

Author SHA1 Message Date
wave-builder[bot] 63e487f6e9 chore: bump package version to 0.8.1 2024-09-24 05:41:55 +00:00
sawka 31d73ef976 beta.1 2024-09-23 20:48:27 -07:00
sawka 91a85e06c8 make no-telemetry request async 2024-09-23 20:47:39 -07:00
sawka 3ea8f45284 v0.8.1.beta.1 2024-09-23 19:03:24 -07:00
Sylvie Crowe 4ff247d2f2 Directory View Parent Directory Button (#805)
Add a button in the directory view to easily navigate to the parent of
the current directory.
2024-09-23 19:02:17 -07:00
Sylvie Crowe 7708db7060 Docs Site Link (#804)
Add a link to the docs site in the help block.
2024-09-23 18:58:46 -07:00
Evan Simkowitz 4907552379 Fix number parsing for MetaSettingsType (#806)
json.Unmarshal parses all numbers to float64, which breaks any integer
settings values. This PR changes MetaSettingsType.UnmarshalJSON to use
json.Decoder, which is capable of parsing into a meta-type json.Number,
which can be interpreted as a float or an integer. It also properly
handles pointer types.
2024-09-23 18:51:30 -07:00
sawka 96ad70ffdd log clientid 2024-09-23 13:59:55 -07:00
Mike Sawka 62cc9d8a6b telemetry fix, log no-telemetry (#802) 2024-09-23 13:33:39 -07:00
wave-builder[bot] de08cc8340 chore: bump package version to 0.8.0 2024-09-20 21:11:56 +00:00
wave-builder[bot] 190a53662c chore: bump package version to 0.8.0-beta.15 2024-09-20 20:49:55 +00:00
Evan Simkowitz b19bef2ffb remove vscode storybook task, move to taskfile 2024-09-20 13:39:06 -07:00
Evan Simkowitz af38ebab54 typo in last pr 2024-09-20 13:31:12 -07:00
Evan Simkowitz 38f07ae3f9 Dismiss update banner after 10s on error (#800) 2024-09-20 13:29:37 -07:00
16 changed files with 221 additions and 120 deletions
-18
View File
@@ -1,18 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Start Storybook",
"type": "shell",
"command": "yarn storybook",
"presentation": {
"reveal": "silent",
"panel": "shared"
},
"runOptions": {
"instanceLimit": 1,
"runOn": "folderOpen"
}
}
]
}
+15 -16
View File
@@ -17,8 +17,7 @@ vars:
tasks:
electron:dev:
desc: Run the Electron application via the Vite dev server (enables hot reloading).
cmds:
- yarn dev
cmd: yarn dev
deps:
- yarn
- build:backend
@@ -28,12 +27,15 @@ tasks:
electron:start:
desc: Run the Electron application directly.
cmds:
- yarn start
cmd: yarn start
deps:
- yarn
- build:backend
storybook:
desc: Start the Storybook server.
cmd: yarn storybook
package:
desc: Package the application for the current platform.
cmds:
@@ -98,12 +100,12 @@ tasks:
requires:
vars:
- ARCHS
cmds:
- cmd: CGO_ENABLED=1 GOARCH={{.GOARCH}} go build -tags "osusergo,netcgo,sqlite_omit_load_extension" -ldflags "{{.GO_LDFLAGS}} -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wavesrv.{{if eq .GOARCH "amd64"}}x64{{else}}{{.GOARCH}}{{end}}{{exeExt}} cmd/server/main-server.go
for:
var: ARCHS
split: ","
as: GOARCH
cmd:
cmd: CGO_ENABLED=1 GOARCH={{.GOARCH}} go build -tags "osusergo,netcgo,sqlite_omit_load_extension" -ldflags "{{.GO_LDFLAGS}} -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wavesrv.{{if eq .GOARCH "amd64"}}x64{{else}}{{.GOARCH}}{{end}}{{exeExt}} cmd/server/main-server.go
for:
var: ARCHS
split: ","
as: GOARCH
sources:
- "cmd/server/*.go"
- "pkg/**/*.go"
@@ -173,8 +175,7 @@ tasks:
- "pkg/**/*.go"
generates:
- dist/bin/wsh-{{.VERSION}}-{{.GOOS}}.{{.NORMALIZEDARCH}}{{.EXT}}
cmds:
- (CGO_ENABLED=0 GOOS={{.GOOS}} GOARCH={{.GOARCH}} go build -ldflags="-s -w -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wsh-{{.VERSION}}-{{.GOOS}}.{{.NORMALIZEDARCH}}{{.EXT}} cmd/wsh/main-wsh.go)
cmd: (CGO_ENABLED=0 GOOS={{.GOOS}} GOARCH={{.GOARCH}} go build -ldflags="-s -w -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wsh-{{.VERSION}}-{{.GOOS}}.{{.NORMALIZEDARCH}}{{.EXT}} cmd/wsh/main-wsh.go)
deps:
- go:mod:tidy
internal: true
@@ -251,8 +252,7 @@ tasks:
- yarn.lock
- package.json
- .yarnrc.yml
cmds:
- yarn
cmd: yarn
go:mod:tidy:
desc: Runs `go mod tidy`
@@ -261,5 +261,4 @@ tasks:
- go.sum
sources:
- go.mod
cmds:
- go mod tidy
cmd: go mod tidy
+2 -2
View File
@@ -42,8 +42,8 @@ import (
var WaveVersion = "0.0.0"
var BuildTime = "0"
const InitialTelemetryWait = 30 * time.Second
const TelemetryTick = 10 * time.Minute
const InitialTelemetryWait = 10 * time.Second
const TelemetryTick = 2 * time.Minute
const TelemetryInterval = 4 * time.Hour
const ReadySignalPidVarName = "WAVETERM_READY_SIGNAL_PID"
+8 -3
View File
@@ -47,11 +47,16 @@ func parseMetaSets(metaSets []string) (map[string]interface{}, error) {
}
meta[fields[0]] = val
} else {
fval, err := strconv.ParseFloat(setVal, 64)
ival, err := strconv.ParseInt(setVal, 0, 64)
if err == nil {
meta[fields[0]] = fval
meta[fields[0]] = ival
} else {
meta[fields[0]] = setVal
fval, err := strconv.ParseFloat(setVal, 64)
if err == nil {
meta[fields[0]] = fval
} else {
meta[fields[0]] = setVal
}
}
}
}
+1 -3
View File
@@ -12,8 +12,6 @@ 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";
@@ -34,7 +32,7 @@ const ModalPage1 = () => {
};
const setTelemetry = (value: boolean) => {
RpcApi.SetConfigCommand(WindowRpcClient, { "telemetry:enabled": value })
services.ClientService.TelemetryUpdate(value)
.then(() => {
setTelemetryEnabled(value);
})
+3
View File
@@ -47,6 +47,9 @@ class ClientServiceType {
MakeWindow(): Promise<WaveWindow> {
return WOS.callBackendService("client", "MakeWindow", Array.from(arguments))
}
TelemetryUpdate(arg2: boolean): Promise<void> {
return WOS.callBackendService("client", "TelemetryUpdate", Array.from(arguments))
}
}
export const ClientService = new ClientServiceType();
+1 -1
View File
@@ -11,7 +11,7 @@ import React, { createRef, useCallback, useEffect, useRef, useState } from "reac
import { debounce } from "throttle-debounce";
import { Tab } from "./tab";
import "./tabbar.less";
import { UpdateStatusBanner } from "./updatestatus";
import { UpdateStatusBanner } from "./updatebanner";
const TAB_DEFAULT_WIDTH = 130;
const TAB_MIN_WIDTH = 100;
+71
View File
@@ -0,0 +1,71 @@
import { Button } from "@/element/button";
import { atoms, getApi } from "@/store/global";
import { useAtomValue } from "jotai";
import { memo, useEffect, useState } from "react";
import "./updatebanner.less";
const UpdateStatusBannerComponent = ({ buttonRef }: { buttonRef: React.RefObject<HTMLButtonElement> }) => {
const appUpdateStatus = useAtomValue(atoms.updaterStatusAtom);
const [updateStatusMessage, setUpdateStatusMessage] = useState<string>();
const [dismissBannerTimeout, setDismissBannerTimeout] = useState<NodeJS.Timeout>();
useEffect(() => {
let message: string;
let dismissBanner = false;
switch (appUpdateStatus) {
case "ready":
message = "Update Available";
break;
case "downloading":
message = "Downloading Update";
break;
case "installing":
message = "Installing Update";
break;
case "error":
message = "Updater Error: Try Checking Again";
dismissBanner = true;
break;
default:
break;
}
setUpdateStatusMessage(message);
// Clear any existing timeout
if (dismissBannerTimeout) {
clearTimeout(dismissBannerTimeout);
}
// If we want to dismiss the banner, set the new timeout, otherwise clear the state
if (dismissBanner) {
setDismissBannerTimeout(
setTimeout(() => {
setUpdateStatusMessage(null);
setDismissBannerTimeout(null);
}, 10000)
);
} else {
setDismissBannerTimeout(null);
}
}, [appUpdateStatus]);
function onClick() {
getApi().installAppUpdate();
}
if (updateStatusMessage) {
return (
<Button
ref={buttonRef}
className="update-available-button"
title={appUpdateStatus === "ready" ? "Click to Install Update" : updateStatusMessage}
onClick={onClick}
disabled={appUpdateStatus !== "ready"}
>
{updateStatusMessage}
</Button>
);
}
};
export const UpdateStatusBanner = memo(UpdateStatusBannerComponent) as typeof UpdateStatusBannerComponent;
-49
View File
@@ -1,49 +0,0 @@
import { Button } from "@/element/button";
import { atoms, getApi } from "@/store/global";
import { useAtomValue } from "jotai";
import { memo } from "react";
import "./updatestatus.less";
const UpdateStatusBannerComponent = ({ buttonRef }: { buttonRef: React.RefObject<HTMLButtonElement> }) => {
const appUpdateStatus = useAtomValue(atoms.updaterStatusAtom);
function onClick() {
getApi().installAppUpdate();
}
let buttonText: string;
switch (appUpdateStatus) {
case "ready":
buttonText = "Update Available";
break;
case "checking":
buttonText = "Checking for Updates";
break;
case "downloading":
buttonText = "Downloading Update";
break;
case "installing":
buttonText = "Installing Update";
break;
case "error":
buttonText = "Updater Error: Try Checking Again";
break;
default:
break;
}
if (buttonText) {
return (
<Button
ref={buttonRef}
className="update-available-button"
title={appUpdateStatus === "ready" ? "Click to Install Update" : buttonText}
onClick={onClick}
disabled={appUpdateStatus !== "ready"}
>
{buttonText}
</Button>
);
}
};
export const UpdateStatusBanner = memo(UpdateStatusBannerComponent) as typeof UpdateStatusBannerComponent;
+3
View File
@@ -7,6 +7,9 @@ import { Atom, atom, PrimitiveAtom } from "jotai";
import "./helpview.less";
const helpText = `
For more up-to-date documentation, please visit [our docs site](http://betadocs.waveterm.dev)
## Blocks
Every individual Component is contained in its own block. These can be added, removed, moved and resized. Each block has its own header which can be right clicked to reveal more operations you can do with that block.
+16 -20
View File
@@ -287,7 +287,8 @@ export class PreviewModel implements ViewModel {
return null;
}
const mimeType = util.jotaiLoadableValue(get(this.fileMimeTypeLoadable), "");
if (mimeType == "directory") {
const metaPath = get(this.metaFilePath);
if (mimeType == "directory" && metaPath == "/") {
return null;
}
return {
@@ -492,29 +493,24 @@ export class PreviewModel implements ViewModel {
}
async goParentDirectory() {
const blockMeta = globalStore.get(this.blockAtom)?.meta;
const metaPath = globalStore.get(this.metaFilePath);
const fileInfo = await globalStore.get(this.statFile);
if (fileInfo == null) {
return;
this.updateOpenFileModalAndError(false);
return true;
}
let newPath: string = null;
if (!fileInfo.isdir) {
newPath = fileInfo.dir;
} else {
const lastSlash = fileInfo.dir.lastIndexOf("/");
newPath = fileInfo.dir.slice(0, lastSlash);
if (newPath.indexOf("/") == -1) {
return;
}
const conn = globalStore.get(this.connection);
try {
const newFileInfo = await RpcApi.RemoteFileJoinCommand(WindowRpcClient, [fileInfo.dir, ".."], {
route: makeConnRoute(conn),
});
console.log(newFileInfo.path);
this.updateOpenFileModalAndError(false);
this.goHistory(newFileInfo.path);
refocusNode(this.blockId);
} catch (e) {
globalStore.set(this.openFileError, e.message);
console.error("Error opening file", [fileInfo.dir, ".."], e);
}
const updateMeta = historyutil.goHistory("file", metaPath, newPath, blockMeta);
if (updateMeta == null) {
return;
}
updateMeta.edit = false;
const blockOref = WOS.makeORef("block", this.blockId);
services.ObjectService.UpdateObjectMeta(blockOref, updateMeta);
}
goHistoryBack() {
+1 -1
View File
@@ -7,7 +7,7 @@
"productName": "Wave",
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
"license": "Apache-2.0",
"version": "0.8.0-beta.14",
"version": "0.8.1",
"homepage": "https://waveterm.dev",
"build": {
"appId": "dev.commandline.waveterm"
@@ -6,11 +6,14 @@ package clientservice
import (
"context"
"fmt"
"log"
"time"
"github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wcloud"
"github.com/wavetermdev/waveterm/pkg/wconfig"
"github.com/wavetermdev/waveterm/pkg/wcore"
"github.com/wavetermdev/waveterm/pkg/wlayout"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
@@ -106,3 +109,34 @@ func (cs *ClientService) AgreeTos(ctx context.Context) (waveobj.UpdatesRtnType,
wlayout.BootstrapStarterLayout(ctx)
return waveobj.ContextGetUpdatesRtn(ctx), nil
}
func sendNoTelemetryUpdate(telemetryEnabled bool) {
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFn()
clientData, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
if err != nil {
log.Printf("telemetry update: error getting client data: %v\n", err)
return
}
if clientData == nil {
log.Printf("telemetry update: client data is nil\n")
return
}
err = wcloud.SendNoTelemetryUpdate(ctx, clientData.OID, !telemetryEnabled)
if err != nil {
log.Printf("[error] sending no-telemetry update: %v\n", err)
return
}
}
func (cs *ClientService) TelemetryUpdate(ctx context.Context, telemetryEnabled bool) error {
meta := waveobj.MetaMapType{
wconfig.ConfigKey_TelemetryEnabled: telemetryEnabled,
}
err := wconfig.SetBaseConfigValue(meta)
if err != nil {
return fmt.Errorf("error setting telemetry value: %w", err)
}
go sendNoTelemetryUpdate(telemetryEnabled)
return nil
}
+46 -7
View File
@@ -26,7 +26,9 @@ type MetaSettingsType struct {
func (m *MetaSettingsType) UnmarshalJSON(data []byte) error {
var metaMap waveobj.MetaMapType
if err := json.Unmarshal(data, &metaMap); err != nil {
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
if err := decoder.Decode(&metaMap); err != nil {
return err
}
*m = MetaSettingsType{MetaMapType: metaMap}
@@ -77,7 +79,7 @@ type SettingsType struct {
WindowOpacity *float64 `json:"window:opacity,omitempty"`
WindowBgColor string `json:"window:bgcolor,omitempty"`
WindowReducedMotion bool `json:"window:reducedmotion,omitempty"`
WindowTileGapSize *int8 `json:"window:tilegapsize,omitempty"`
WindowTileGapSize *int64 `json:"window:tilegapsize,omitempty"`
TelemetryClear bool `json:"telemetry:*,omitempty"`
TelemetryEnabled bool `json:"telemetry:enabled,omitempty"`
@@ -98,8 +100,6 @@ type FullConfigType struct {
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
}
var settingsAbsPath = filepath.Join(configDirAbsPath, SettingsFile)
func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.MetaMapType, []ConfigError) {
var cerrs []ConfigError
if readErr != nil && !os.IsNotExist(readErr) {
@@ -242,7 +242,7 @@ func reindentJson(barr []byte, indentStr string) []byte {
if barr[0] != '{' && barr[0] != '[' {
return barr
}
if bytes.Index(barr, []byte("\n")) == -1 {
if bytes.Contains(barr, []byte("\n")) {
return barr
}
outputLines := bytes.Split(barr, []byte("\n"))
@@ -286,6 +286,32 @@ func jsonMarshalConfigInOrder(m waveobj.MetaMapType) ([]byte, error) {
return buf.Bytes(), nil
}
var dummyNumber json.Number
func convertJsonNumber(num json.Number, ctype reflect.Type) (interface{}, error) {
// ctype might be int64, float64, string, *int64, *float64, *string
// switch on ctype first
if ctype.Kind() == reflect.Pointer {
ctype = ctype.Elem()
}
if reflect.Int64 == ctype.Kind() {
if ival, err := num.Int64(); err == nil {
return ival, nil
}
return nil, fmt.Errorf("invalid number for int64: %s", num)
}
if reflect.Float64 == ctype.Kind() {
if fval, err := num.Float64(); err == nil {
return fval, nil
}
return nil, fmt.Errorf("invalid number for float64: %s", num)
}
if reflect.String == ctype.Kind() {
return num.String(), nil
}
return nil, fmt.Errorf("cannot convert number to %s", ctype)
}
func SetBaseConfigValue(toMerge waveobj.MetaMapType) error {
m, cerrs := ReadWaveHomeConfigFile(SettingsFile)
if len(cerrs) > 0 {
@@ -302,8 +328,21 @@ func SetBaseConfigValue(toMerge waveobj.MetaMapType) error {
if val == nil {
delete(m, configKey)
} else {
if reflect.TypeOf(val) != ctype {
return fmt.Errorf("invalid value type for %s: %T", configKey, val)
rtype := reflect.TypeOf(val)
if rtype == reflect.TypeOf(dummyNumber) {
convertedVal, err := convertJsonNumber(val.(json.Number), ctype)
if err != nil {
return fmt.Errorf("cannot convert %s: %v", configKey, err)
}
val = convertedVal
rtype = reflect.TypeOf(val)
}
if rtype != ctype {
if ctype == reflect.PointerTo(rtype) {
m[configKey] = &val
} else {
return fmt.Errorf("invalid value type for %s: %T", configKey, val)
}
}
m[configKey] = val
}
+20
View File
@@ -7,10 +7,12 @@ package wcore
import (
"context"
"fmt"
"log"
"time"
"github.com/google/uuid"
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
"github.com/wavetermdev/waveterm/pkg/telemetry"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wps"
"github.com/wavetermdev/waveterm/pkg/wstore"
@@ -149,6 +151,7 @@ func EnsureInitialData() (*waveobj.Window, bool, error) {
}
firstRun = true
}
log.Printf("clientid: %s\n", client.OID)
if len(client.WindowIds) > 0 {
return nil, false, nil
}
@@ -172,9 +175,26 @@ func CreateClient(ctx context.Context) (*waveobj.Client, error) {
}
func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) {
if blockDef == nil {
return nil, fmt.Errorf("blockDef is nil")
}
if blockDef.Meta == nil || blockDef.Meta.GetString(waveobj.MetaKey_View, "") == "" {
return nil, fmt.Errorf("no view provided for new block")
}
blockData, err := wstore.CreateBlock(ctx, tabId, blockDef, rtOpts)
if err != nil {
return nil, fmt.Errorf("error creating block: %w", err)
}
go func() {
blockView := blockDef.Meta.GetString(waveobj.MetaKey_View, "")
if blockView == "" {
return
}
tctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
telemetry.UpdateActivity(tctx, telemetry.ActivityUpdate{
Renderers: map[string]int{blockView: 1},
})
}()
return blockData, nil
}