From 8001278252651d32a310ac6e332a827fe259884c Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 3 May 2024 00:14:24 +0300 Subject: [PATCH 001/221] add copyright info --- wails.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wails.json b/wails.json index 4f93630..950ce85 100644 --- a/wails.json +++ b/wails.json @@ -12,6 +12,7 @@ }, "info": { "productName": "Desktop Manager", - "productVersion": "0.1.0" + "productVersion": "0.1.0", + "copyright": "Copyright © 2024 Bedirhan Yenilmez" } } From 5c152c28ee19271c37735017977b6e16f326d9fc Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sat, 11 May 2024 01:27:16 +0300 Subject: [PATCH 002/221] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 98b0d2e..7474f6e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Desktop Manager -Icon and description editor, profile creation tool for your desktop. (WIP) +Desktop icon and description manager, with a profile creation tool. ## Screenshots @@ -18,6 +18,7 @@ Icon and description editor, profile creation tool for your desktop. (WIP) - Share profiles as files - Folder and .url support - Sync desktop with profile +- .png and .jpg support for icon selection ## Built With From e299ae67db4a3aa0ce0cabb85850385428c8e074 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 23 Jun 2024 19:18:28 +0300 Subject: [PATCH 003/221] update gitignore --- .gitignore | 18 +- frontend/wailsjs/go/main/App.d.ts | 25 --- frontend/wailsjs/go/main/App.js | 47 ------ frontend/wailsjs/go/models.ts | 83 --------- frontend/wailsjs/runtime/package.json | 24 --- frontend/wailsjs/runtime/runtime.d.ts | 235 -------------------------- frontend/wailsjs/runtime/runtime.js | 202 ---------------------- go.mod | 10 +- go.sum | 20 +-- 9 files changed, 22 insertions(+), 642 deletions(-) delete mode 100644 frontend/wailsjs/go/main/App.d.ts delete mode 100644 frontend/wailsjs/go/main/App.js delete mode 100644 frontend/wailsjs/go/models.ts delete mode 100644 frontend/wailsjs/runtime/package.json delete mode 100644 frontend/wailsjs/runtime/runtime.d.ts delete mode 100644 frontend/wailsjs/runtime/runtime.js diff --git a/.gitignore b/.gitignore index 58f282c..fc8e9b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,6 @@ # Logs logs *.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local # Editor directories and files .vscode/* @@ -25,4 +15,10 @@ dist-ssr # wails *.exe~ -./build/bin/* \ No newline at end of file +*.exe +*.syso +node_modules +/build/bin +/frontend/dist +/frontend/wailsjs +/frontend/package.json.md5 diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts deleted file mode 100644 index 4eb0406..0000000 --- a/frontend/wailsjs/go/main/App.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {main} from '../models'; - -export function AddProfile(arg1:string):Promise; - -export function GetDesktopIcons():Promise>; - -export function GetFileInfo(arg1:string):Promise; - -export function GetIcon(arg1:string,arg2:string):Promise; - -export function GetProfile(arg1:string):Promise; - -export function GetProfiles():Promise>; - -export function RemoveProfile(arg1:string):Promise; - -export function RunProfile(arg1:string,arg2:Array):Promise; - -export function SaveIcon(arg1:string,arg2:main.fileInfo):Promise; - -export function SaveProfile(arg1:string,arg2:string):Promise; - -export function SyncDesktop(arg1:string,arg2:boolean):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js deleted file mode 100644 index 7516a4d..0000000 --- a/frontend/wailsjs/go/main/App.js +++ /dev/null @@ -1,47 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function AddProfile(arg1) { - return window['go']['main']['App']['AddProfile'](arg1); -} - -export function GetDesktopIcons() { - return window['go']['main']['App']['GetDesktopIcons'](); -} - -export function GetFileInfo(arg1) { - return window['go']['main']['App']['GetFileInfo'](arg1); -} - -export function GetIcon(arg1, arg2) { - return window['go']['main']['App']['GetIcon'](arg1, arg2); -} - -export function GetProfile(arg1) { - return window['go']['main']['App']['GetProfile'](arg1); -} - -export function GetProfiles() { - return window['go']['main']['App']['GetProfiles'](); -} - -export function RemoveProfile(arg1) { - return window['go']['main']['App']['RemoveProfile'](arg1); -} - -export function RunProfile(arg1, arg2) { - return window['go']['main']['App']['RunProfile'](arg1, arg2); -} - -export function SaveIcon(arg1, arg2) { - return window['go']['main']['App']['SaveIcon'](arg1, arg2); -} - -export function SaveProfile(arg1, arg2) { - return window['go']['main']['App']['SaveProfile'](arg1, arg2); -} - -export function SyncDesktop(arg1, arg2) { - return window['go']['main']['App']['SyncDesktop'](arg1, arg2); -} diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts deleted file mode 100644 index f35e9df..0000000 --- a/frontend/wailsjs/go/models.ts +++ /dev/null @@ -1,83 +0,0 @@ -export namespace main { - - export class fileInfo { - name: string; - description: string; - path: string; - destination: string; - iconDestination: string; - iconIndex: number; - extension: string; - isFolder: boolean; - iconId: string; - iconName: string; - - static createFrom(source: any = {}) { - return new fileInfo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.description = source["description"]; - this.path = source["path"]; - this.destination = source["destination"]; - this.iconDestination = source["iconDestination"]; - this.iconIndex = source["iconIndex"]; - this.extension = source["extension"]; - this.isFolder = source["isFolder"]; - this.iconId = source["iconId"]; - this.iconName = source["iconName"]; - } - } - export class profile { - name: string; - id: string; - value: fileInfo[]; - - static createFrom(source: any = {}) { - return new profile(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.id = source["id"]; - this.value = this.convertValues(source["value"], fileInfo); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class profileInfo { - value: any; - label: string; - - static createFrom(source: any = {}) { - return new profileInfo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.value = source["value"]; - this.label = source["label"]; - } - } - -} - diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json deleted file mode 100644 index 1e7c8a5..0000000 --- a/frontend/wailsjs/runtime/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@wailsapp/runtime", - "version": "2.0.0", - "description": "Wails Javascript runtime library", - "main": "runtime.js", - "types": "runtime.d.ts", - "scripts": { - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wailsapp/wails.git" - }, - "keywords": [ - "Wails", - "Javascript", - "Go" - ], - "author": "Lea Anthony ", - "license": "MIT", - "bugs": { - "url": "https://github.com/wailsapp/wails/issues" - }, - "homepage": "https://github.com/wailsapp/wails#readme" -} diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts deleted file mode 100644 index a3723f9..0000000 --- a/frontend/wailsjs/runtime/runtime.d.ts +++ /dev/null @@ -1,235 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export interface Position { - x: number; - y: number; -} - -export interface Size { - w: number; - h: number; -} - -export interface Screen { - isCurrent: boolean; - isPrimary: boolean; - width : number - height : number -} - -// Environment information such as platform, buildtype, ... -export interface EnvironmentInfo { - buildType: string; - platform: string; - arch: string; -} - -// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) -// emits the given event. Optional data may be passed with the event. -// This will trigger any event listeners. -export function EventsEmit(eventName: string, ...data: any): void; - -// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. -export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) -// sets up a listener for the given event name, but will only trigger a given number times. -export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; - -// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) -// sets up a listener for the given event name, but will only trigger once. -export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) -// unregisters the listener for the given event name. -export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; - -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all listeners. -export function EventsOffAll(): void; - -// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) -// logs the given message as a raw message -export function LogPrint(message: string): void; - -// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) -// logs the given message at the `trace` log level. -export function LogTrace(message: string): void; - -// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) -// logs the given message at the `debug` log level. -export function LogDebug(message: string): void; - -// [LogError](https://wails.io/docs/reference/runtime/log#logerror) -// logs the given message at the `error` log level. -export function LogError(message: string): void; - -// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) -// logs the given message at the `fatal` log level. -// The application will quit after calling this method. -export function LogFatal(message: string): void; - -// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) -// logs the given message at the `info` log level. -export function LogInfo(message: string): void; - -// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) -// logs the given message at the `warning` log level. -export function LogWarning(message: string): void; - -// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) -// Forces a reload by the main application as well as connected browsers. -export function WindowReload(): void; - -// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) -// Reloads the application frontend. -export function WindowReloadApp(): void; - -// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) -// Sets the window AlwaysOnTop or not on top. -export function WindowSetAlwaysOnTop(b: boolean): void; - -// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) -// *Windows only* -// Sets window theme to system default (dark/light). -export function WindowSetSystemDefaultTheme(): void; - -// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) -// *Windows only* -// Sets window to light theme. -export function WindowSetLightTheme(): void; - -// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) -// *Windows only* -// Sets window to dark theme. -export function WindowSetDarkTheme(): void; - -// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) -// Centers the window on the monitor the window is currently on. -export function WindowCenter(): void; - -// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) -// Sets the text in the window title bar. -export function WindowSetTitle(title: string): void; - -// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) -// Makes the window full screen. -export function WindowFullscreen(): void; - -// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) -// Restores the previous window dimensions and position prior to full screen. -export function WindowUnfullscreen(): void; - -// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) -// Returns the state of the window, i.e. whether the window is in full screen mode or not. -export function WindowIsFullscreen(): Promise; - -// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) -// Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): Promise; - -// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) -// Gets the width and height of the window. -export function WindowGetSize(): Promise; - -// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) -// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMaxSize(width: number, height: number): void; - -// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) -// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMinSize(width: number, height: number): void; - -// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) -// Sets the window position relative to the monitor the window is currently on. -export function WindowSetPosition(x: number, y: number): void; - -// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) -// Gets the window position relative to the monitor the window is currently on. -export function WindowGetPosition(): Promise; - -// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) -// Hides the window. -export function WindowHide(): void; - -// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) -// Shows the window, if it is currently hidden. -export function WindowShow(): void; - -// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) -// Maximises the window to fill the screen. -export function WindowMaximise(): void; - -// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) -// Toggles between Maximised and UnMaximised. -export function WindowToggleMaximise(): void; - -// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) -// Restores the window to the dimensions and position prior to maximising. -export function WindowUnmaximise(): void; - -// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) -// Returns the state of the window, i.e. whether the window is maximised or not. -export function WindowIsMaximised(): Promise; - -// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) -// Minimises the window. -export function WindowMinimise(): void; - -// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) -// Restores the window to the dimensions and position prior to minimising. -export function WindowUnminimise(): void; - -// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) -// Returns the state of the window, i.e. whether the window is minimised or not. -export function WindowIsMinimised(): Promise; - -// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) -// Returns the state of the window, i.e. whether the window is normal or not. -export function WindowIsNormal(): Promise; - -// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) -// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. -export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; - -// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) -// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. -export function ScreenGetAll(): Promise; - -// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) -// Opens the given URL in the system browser. -export function BrowserOpenURL(url: string): void; - -// [Environment](https://wails.io/docs/reference/runtime/intro#environment) -// Returns information about the environment -export function Environment(): Promise; - -// [Quit](https://wails.io/docs/reference/runtime/intro#quit) -// Quits the application. -export function Quit(): void; - -// [Hide](https://wails.io/docs/reference/runtime/intro#hide) -// Hides the application. -export function Hide(): void; - -// [Show](https://wails.io/docs/reference/runtime/intro#show) -// Shows the application. -export function Show(): void; - -// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) -// Returns the current text stored on clipboard -export function ClipboardGetText(): Promise; - -// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) -// Sets a text on the clipboard -export function ClipboardSetText(text: string): Promise; diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js deleted file mode 100644 index bd4f371..0000000 --- a/frontend/wailsjs/runtime/runtime.js +++ /dev/null @@ -1,202 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export function LogPrint(message) { - window.runtime.LogPrint(message); -} - -export function LogTrace(message) { - window.runtime.LogTrace(message); -} - -export function LogDebug(message) { - window.runtime.LogDebug(message); -} - -export function LogInfo(message) { - window.runtime.LogInfo(message); -} - -export function LogWarning(message) { - window.runtime.LogWarning(message); -} - -export function LogError(message) { - window.runtime.LogError(message); -} - -export function LogFatal(message) { - window.runtime.LogFatal(message); -} - -export function EventsOnMultiple(eventName, callback, maxCallbacks) { - return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); -} - -export function EventsOn(eventName, callback) { - return EventsOnMultiple(eventName, callback, -1); -} - -export function EventsOff(eventName, ...additionalEventNames) { - return window.runtime.EventsOff(eventName, ...additionalEventNames); -} - -export function EventsOnce(eventName, callback) { - return EventsOnMultiple(eventName, callback, 1); -} - -export function EventsEmit(eventName) { - let args = [eventName].slice.call(arguments); - return window.runtime.EventsEmit.apply(null, args); -} - -export function WindowReload() { - window.runtime.WindowReload(); -} - -export function WindowReloadApp() { - window.runtime.WindowReloadApp(); -} - -export function WindowSetAlwaysOnTop(b) { - window.runtime.WindowSetAlwaysOnTop(b); -} - -export function WindowSetSystemDefaultTheme() { - window.runtime.WindowSetSystemDefaultTheme(); -} - -export function WindowSetLightTheme() { - window.runtime.WindowSetLightTheme(); -} - -export function WindowSetDarkTheme() { - window.runtime.WindowSetDarkTheme(); -} - -export function WindowCenter() { - window.runtime.WindowCenter(); -} - -export function WindowSetTitle(title) { - window.runtime.WindowSetTitle(title); -} - -export function WindowFullscreen() { - window.runtime.WindowFullscreen(); -} - -export function WindowUnfullscreen() { - window.runtime.WindowUnfullscreen(); -} - -export function WindowIsFullscreen() { - return window.runtime.WindowIsFullscreen(); -} - -export function WindowGetSize() { - return window.runtime.WindowGetSize(); -} - -export function WindowSetSize(width, height) { - window.runtime.WindowSetSize(width, height); -} - -export function WindowSetMaxSize(width, height) { - window.runtime.WindowSetMaxSize(width, height); -} - -export function WindowSetMinSize(width, height) { - window.runtime.WindowSetMinSize(width, height); -} - -export function WindowSetPosition(x, y) { - window.runtime.WindowSetPosition(x, y); -} - -export function WindowGetPosition() { - return window.runtime.WindowGetPosition(); -} - -export function WindowHide() { - window.runtime.WindowHide(); -} - -export function WindowShow() { - window.runtime.WindowShow(); -} - -export function WindowMaximise() { - window.runtime.WindowMaximise(); -} - -export function WindowToggleMaximise() { - window.runtime.WindowToggleMaximise(); -} - -export function WindowUnmaximise() { - window.runtime.WindowUnmaximise(); -} - -export function WindowIsMaximised() { - return window.runtime.WindowIsMaximised(); -} - -export function WindowMinimise() { - window.runtime.WindowMinimise(); -} - -export function WindowUnminimise() { - window.runtime.WindowUnminimise(); -} - -export function WindowSetBackgroundColour(R, G, B, A) { - window.runtime.WindowSetBackgroundColour(R, G, B, A); -} - -export function ScreenGetAll() { - return window.runtime.ScreenGetAll(); -} - -export function WindowIsMinimised() { - return window.runtime.WindowIsMinimised(); -} - -export function WindowIsNormal() { - return window.runtime.WindowIsNormal(); -} - -export function BrowserOpenURL(url) { - window.runtime.BrowserOpenURL(url); -} - -export function Environment() { - return window.runtime.Environment(); -} - -export function Quit() { - window.runtime.Quit(); -} - -export function Hide() { - window.runtime.Hide(); -} - -export function Show() { - window.runtime.Show(); -} - -export function ClipboardGetText() { - return window.runtime.ClipboardGetText(); -} - -export function ClipboardSetText(text) { - return window.runtime.ClipboardSetText(text); -} \ No newline at end of file diff --git a/go.mod b/go.mod index c3d66ba..e04c332 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.22.0 require ( github.com/google/uuid v1.6.0 github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff - github.com/wailsapp/wails/v2 v2.8.1 + github.com/wailsapp/wails/v2 v2.9.1 ) require ( @@ -34,11 +34,11 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect github.com/wailsapp/go-webview2 v1.0.10 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect ) // replace github.com/wailsapp/wails/v2 v2.8.0 => C:\Users\bedoy\go\pkg\mod diff --git a/go.sum b/go.sum index eb97fbc..4db4d07 100644 --- a/go.sum +++ b/go.sum @@ -68,15 +68,15 @@ github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhy github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v2 v2.8.1 h1:KAudNjlFaiXnDfFEfSNoLoibJ1ovoutSrJ8poerTPW0= -github.com/wailsapp/wails/v2 v2.8.1/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +github.com/wailsapp/wails/v2 v2.9.1 h1:irsXnoQrCpeKzKTYZ2SUVlRRyeMR6I0vCO9Q1cvlEdc= +github.com/wailsapp/wails/v2 v2.9.1/go.mod h1:7maJV2h+Egl11Ak8QZN/jlGLj2wg05bsQS+ywJPT0gI= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -87,12 +87,12 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 663810ff8df1783ca04ba899375a8de6119c653e Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 23 Jun 2024 20:37:54 +0300 Subject: [PATCH 004/221] reset to fresh project --- .gitignore | 9 +- app.go | 579 +-- frontend/index.html | 3 +- frontend/package-lock.json | 3535 +++++++++++++++++ frontend/package.json | 55 +- frontend/package.json.md5 | 1 - frontend/public/setlnkdesc.vbs | 17 - frontend/public/setlnkicon.vbs | 18 - frontend/src/App.tsx | 24 +- frontend/src/assets/folder-search.png | Bin 386 -> 0 bytes frontend/src/assets/folder-search.svg | 7 - frontend/src/assets/unknown.png | Bin 6090 -> 0 bytes frontend/src/components/CreateProfileForm.tsx | 76 - frontend/src/components/FileContainer.tsx | 90 - frontend/src/components/FileGrid.tsx | 56 - frontend/src/components/TopBar.tsx | 91 - frontend/src/components/mode-toggle.tsx | 37 - frontend/src/components/ui/accordion.tsx | 56 - frontend/src/components/ui/combobox.tsx | 86 - frontend/src/components/ui/command.tsx | 153 - frontend/src/components/ui/dialog.tsx | 120 - frontend/src/components/ui/dropdown-menu.tsx | 198 - frontend/src/components/ui/form.tsx | 176 - frontend/src/components/ui/input.tsx | 25 - frontend/src/components/ui/label.tsx | 24 - frontend/src/components/ui/popover.tsx | 29 - frontend/src/contexts/profile-provider.tsx | 48 - frontend/src/contexts/theme-provider.tsx | 73 - frontend/src/index.css | 46 +- frontend/src/structs.tsx | 23 - frontend/tsconfig.json | 5 +- frontend/vite.config.ts | 1 - frontend/yarn.lock | 2835 ------------- go.mod | 11 +- go.sum | 11 +- main.go | 10 +- 36 files changed, 3618 insertions(+), 4910 deletions(-) create mode 100644 frontend/package-lock.json delete mode 100644 frontend/package.json.md5 delete mode 100644 frontend/public/setlnkdesc.vbs delete mode 100644 frontend/public/setlnkicon.vbs delete mode 100644 frontend/src/assets/folder-search.png delete mode 100644 frontend/src/assets/folder-search.svg delete mode 100644 frontend/src/assets/unknown.png delete mode 100644 frontend/src/components/CreateProfileForm.tsx delete mode 100644 frontend/src/components/FileContainer.tsx delete mode 100644 frontend/src/components/FileGrid.tsx delete mode 100644 frontend/src/components/TopBar.tsx delete mode 100644 frontend/src/components/mode-toggle.tsx delete mode 100644 frontend/src/components/ui/accordion.tsx delete mode 100644 frontend/src/components/ui/combobox.tsx delete mode 100644 frontend/src/components/ui/command.tsx delete mode 100644 frontend/src/components/ui/dialog.tsx delete mode 100644 frontend/src/components/ui/dropdown-menu.tsx delete mode 100644 frontend/src/components/ui/form.tsx delete mode 100644 frontend/src/components/ui/input.tsx delete mode 100644 frontend/src/components/ui/label.tsx delete mode 100644 frontend/src/components/ui/popover.tsx delete mode 100644 frontend/src/contexts/profile-provider.tsx delete mode 100644 frontend/src/contexts/theme-provider.tsx delete mode 100644 frontend/src/structs.tsx delete mode 100644 frontend/yarn.lock diff --git a/.gitignore b/.gitignore index fc8e9b5..8ca564a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ # Logs logs *.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* # Editor directories and files .vscode/* @@ -13,12 +18,14 @@ logs *.sln *.sw? -# wails +# Wails *.exe~ *.exe *.syso +*.local node_modules /build/bin /frontend/dist +/frontend/dist-ssr /frontend/wailsjs /frontend/package.json.md5 diff --git a/app.go b/app.go index 1a7e025..224be71 100644 --- a/app.go +++ b/app.go @@ -2,31 +2,9 @@ package main import ( "context" - _ "embed" - "encoding/base64" - "encoding/json" "fmt" - "io" - "io/fs" - "net/http" - "os" - "os/exec" - "os/user" - "path" - "path/filepath" - "strings" - - "github.com/google/uuid" - lnk "github.com/parsiya/golnk" - runtime "github.com/wailsapp/wails/v2/pkg/runtime" ) -//go:embed frontend/public/setlnkicon.vbs -var setlnkicon string - -//go:embed frontend/public/setlnkdesc.vbs -var setlnkdesc string - // App struct type App struct { ctx context.Context @@ -60,558 +38,7 @@ func (a *App) shutdown(ctx context.Context) { // Perform your teardown here } -type fileInfo struct { - Name string `json:"name"` - Description string `json:"description"` - Path string `json:"path"` - Destination string `json:"destination"` - IconDestination string `json:"iconDestination"` - IconIndex int `json:"iconIndex"` - Extension string `json:"extension"` - IsFolder bool `json:"isFolder"` - IconId string `json:"iconId"` - IconName string `json:"iconName"` -} - -type profileInfo struct { - Value any `json:"value"` - Label string `json:"label"` -} - -type profile struct { - Name string `json:"name"` - Id string `json:"id"` - Value []fileInfo `json:"value"` -} - -// Allowed file types -var allowedTypes = []string{".lnk"} - -func CheckErr(err error, msg string, fatal bool) { - if err != nil { - fmt.Printf("%s - %v\n", msg, err) - if fatal { - panic(msg) - } - } -} - -// Check if an item is in a slice -func contains(slice []string, item string) bool { - for _, value := range slice { - if value == item { - return true - } - } - return false -} - -// Copy copies the contents of the file at srcpath to a regular file -// at dstpath. If the file named by dstpath already exists, it is -// truncated. The function does not copy the file mode, file -// permission bits, or file attributes. -// Source: https://stackoverflow.com/a/74107689 -func Copy(srcpath, dstpath string) (err error) { - r, err := os.Open(srcpath) - if err != nil { - return err - } - defer r.Close() // ignore error: file was opened read-only. - - w, err := os.Create(dstpath) - if err != nil { - return err - } - - defer func() { - // Report the error, if any, from Close, but do so - // only if there isn't already an outgoing error. - if c := w.Close(); err == nil { - err = c - } - }() - - _, err = io.Copy(w, r) - return err -} - -// Returns the save directory -func getSaveDir() string { - userConfigDir, err := os.UserConfigDir() - - CheckErr(err, "Failed to get user config dir", true) - - return filepath.Join(userConfigDir, "desktop-manager") -} - -// Returns the profile directory -func getProfileDir() string { - return filepath.Join(getSaveDir(), "profiles") -} - -// Returns the icon directory -func getIconDir(profileName string) string { - return filepath.Join(getSaveDir(), "icon", profileName) -} - -// Returns the base64 icon directory -func getBase64Dir(profileName string) string { - return filepath.Join(getIconDir(profileName), "base64") -} - -// Returns the script directory -func getScriptDir() string { - return filepath.Join(getSaveDir(), "scripts") -} - -// Returns the desktop paths -func getDesktopPaths() []string { - userDir, err := user.Current() - CheckErr(err, "Failed to get user dir", true) - - homedir := userDir.HomeDir - desktop := filepath.Join(homedir, "Desktop") - - public := "C:\\Users\\Public\\Desktop" - - return []string{desktop, public} -} - -// GetFileInfo retrieves information about a file. -// -// Parameters: -// - path: the path of the directory containing the file. -// - file: the file to retrieve information about. -// -// Returns: -// - fileInfo: the information about the file. -// - error: an error if the file type is not allowed or if there was a failure reading the file. -func GetFileInfo(path string, file *fs.DirEntry) (fileInfo, error) { - fileName := (*file).Name() - extension := filepath.Ext(fileName) - noExtFileName := strings.TrimSuffix(fileName, extension) - - if !contains(allowedTypes, extension) { - return fileInfo{}, fmt.Errorf("file type not allowed") - } else if extension == ".lnk" { - filePath := filepath.Join(path, fileName) - - lnkInfo := fileInfo{ - Name: noExtFileName, - Description: "", - Path: filePath, - Destination: "", - IconDestination: "", - IconIndex: 0, - Extension: extension, - IsFolder: false, - IconId: "", - IconName: "", - } - - f, err := lnk.File(filePath) - CheckErr(err, "Failed to read lnk file", false) - - if f.StringData.NameString != "" { - lnkInfo.Description = f.StringData.NameString - } - - if f.LinkInfo.LocalBasePath != "" { - lnkInfo.Destination = f.LinkInfo.LocalBasePath - } - if f.LinkInfo.LocalBasePathUnicode != "" { - lnkInfo.Destination = f.LinkInfo.LocalBasePathUnicode - } - - if f.StringData.IconLocation != "" { - lnkInfo.IconDestination = f.StringData.IconLocation - } - - if f.Header.IconIndex != 0 { - lnkInfo.IconIndex = int(f.Header.IconIndex) - } - - return lnkInfo, nil - } else { - return fileInfo{}, nil - } -} - -// GetFileInfoSlice retrieves file information for the provided paths. -// -// paths: a slice of strings representing the directories to read files from. -// []fileInfo: a slice of fileInfo structs containing information about the files. -func GetFileInfoSlice(paths []string) []fileInfo { - icons := []fileInfo{} - - for _, path := range paths { - currentFiles, pathError := os.ReadDir(path) - CheckErr(pathError, "Failed to read directory", false) - - for _, file := range currentFiles { - fileInfo, err := GetFileInfo(path, &file) - CheckErr(err, "Failed to get file info", false) - - if fileInfo.Path != "" && contains(allowedTypes, fileInfo.Extension) { - icons = append(icons, fileInfo) - } - } - } - - return icons -} - -func (a *App) AddProfile(name string) { - // Create profile - profile := profile{ - Name: name, - Id: uuid.New().String(), - Value: []fileInfo{}, - } - - // Convert to JSON - profileJSON, err := json.Marshal(profile) - CheckErr(err, "Failed to marshal profile", false) - - // Get save directory - profileDir := getProfileDir() - - // Create folder - err = os.MkdirAll(profileDir, os.ModePerm) - CheckErr(err, "Failed to create profile folder", false) - - // Write json - err = os.WriteFile(filepath.Join(profileDir, name), profileJSON, 0644) - CheckErr(err, "Failed to write profile file", false) -} - -func (a *App) RemoveProfile(profileName string) { - profileDir := getProfileDir() - err := os.Remove(filepath.Join(profileDir, profileName)) - CheckErr(err, "Failed to remove profile file", false) -} - -func CopyIcons(profile *profile) { - for i, fileInfo := range (*profile).Value { - if filepath.Ext(fileInfo.IconDestination) == ".ico" { - // Generate UUID and save path - uuid := uuid.New().String() - savePath := filepath.Join(getIconDir((*profile).Name), uuid+".ico") - - // Create dir - err := os.MkdirAll(filepath.Dir(savePath), os.ModePerm) - CheckErr(err, "Failed to create icon folder", false) - - // Copy icon - err = Copy(fileInfo.IconDestination, savePath) - CheckErr(err, "Failed to copy icon", false) - - // Generate base64 version - GenerateBase64Icon(&(*profile).Name, &fileInfo.IconDestination, &uuid) - - // Icon name - fileInfo.IconName = uuid - - (*profile).Value[i] = fileInfo - } - } -} - -func GenerateBase64Icon(profileName *string, filePath *string, fileName *string) { - // Create folder - err := os.MkdirAll(getBase64Dir(*profileName), os.ModePerm) - CheckErr(err, "Failed to create base64 image folder", false) - - destination := filepath.Join(getBase64Dir(*profileName), *fileName) - - // Read the entire file into a byte slice - bytes, err := os.ReadFile(*filePath) - if err != nil { - fmt.Println(err) - } - - var base64Encoding string - - // Determine the content type of the image file - mimeType := http.DetectContentType(bytes) - - // Prepend the appropriate URI scheme header depending - // on the MIME type - switch mimeType { - case "image/x-icon": - base64Encoding += "data:image/x-icon;base64," - destination += ".ico" - default: - fmt.Println("Unknown MIME type: ", mimeType) - } - - // Append the base64 encoded output - base64Encoding += base64.StdEncoding.EncodeToString(bytes) - - // Write the full base64 representation of the image to file - err = os.WriteFile(destination, []byte(base64Encoding), 0644) - CheckErr(err, "Failed to write icon file", false) -} - -func (a *App) SyncDesktop(profileName string, includeIcons bool) profile { - profile := a.GetProfile(profileName) - profile.Value = GetFileInfoSlice(getDesktopPaths()) - if includeIcons { - CopyIcons(&profile) - } - - profileJSON, err := json.Marshal(profile) - CheckErr(err, "Failed to marshal profile", false) - profileJSONStr := string(profileJSON) - - a.SaveProfile(profile.Name, profileJSONStr) - - return profile -} - -func (a *App) GetFileInfo(profileName string) fileInfo { - defaultDirectory := getDesktopPaths()[0] - - println("Default directory: ", defaultDirectory) - - result, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ - DefaultDirectory: defaultDirectory, - Title: "Select Icon", - Filters: []runtime.FileFilter{ - { - DisplayName: "Shortcuts (*.lnk)", - Pattern: "*.lnk", - }, - }, - }) - CheckErr(err, "Failed to open file dialog", false) - - println("Selected file: ", result) - - if len(result) == 0 { - return fileInfo{} - } else { - var file fs.DirEntry - - files, err := os.ReadDir(filepath.Dir(result)) - CheckErr(err, "Failed to read directory", false) - for _, f := range files { - if f.Name() == filepath.Base(result) { - file = f - break - } - } - - fileInfo, err := GetFileInfo(filepath.Dir(result), &file) - CheckErr(err, "Failed to get file info", false) - - return fileInfo - } -} - -func (a *App) GetDesktopIcons() []fileInfo { - return GetFileInfoSlice(getDesktopPaths()) -} - -func (a *App) GetProfiles() []profileInfo { - saveDir := getProfileDir() - - profiles, profilesError := os.ReadDir(saveDir) - - if profilesError != nil { - println("No profile found in ", saveDir) - } - - profileInfos := []profileInfo{} - - for _, profile := range profiles { - profileInfos = append(profileInfos, profileInfo{ - Value: profile.Name(), - Label: profile.Name(), - }) - } - - return profileInfos -} - -func (a *App) GetProfile(profileName string) profile { - saveDir := getProfileDir() - - profileValue, err := os.ReadFile(filepath.Join(saveDir, profileName)) - if err != nil { - fmt.Println(err) - } - - var profileInfoSlice profile - jsonErr := json.Unmarshal(profileValue, &profileInfoSlice) - if jsonErr != nil { - fmt.Println(jsonErr) - } - - valueBytes, marshalErr := json.Marshal(profileInfoSlice.Value) - if marshalErr != nil { - fmt.Println(marshalErr) - } - - var fileInfoSlice []fileInfo - jsonErr = json.Unmarshal(valueBytes, &fileInfoSlice) - if jsonErr != nil { - fmt.Println(jsonErr) - } - - profile := profile{ - Name: profileName, - Value: fileInfoSlice, - } - - return profile -} - -func (a *App) SaveProfile(profileName string, profile string) { - err := os.WriteFile(filepath.Join(getProfileDir(), profileName), []byte(profile), 0644) - CheckErr(err, "Failed to write profile file", false) -} - -func (a *App) GetIcon(profileName string, iconName string) string { - saveDir := filepath.Join(getBase64Dir(profileName), iconName+".ico") - - bytes, err := os.ReadFile(saveDir) - CheckErr(err, "Failed to read icon file", false) - - return string(bytes) -} - -func (a *App) SaveIcon(profileName string, fileInfo fileInfo) string { - var defaultDirectory string - - // check if path exists - if _, err := os.Stat(fileInfo.IconDestination); err == nil { - defaultDirectory = filepath.Dir(fileInfo.IconDestination) - } else if _, err := os.Stat(fileInfo.Destination); err == nil { - defaultDirectory = filepath.Dir(fileInfo.Destination) - } else { - defaultDirectory = getDesktopPaths()[0] - } - - println("Default directory: ", defaultDirectory) - - result, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ - DefaultDirectory: defaultDirectory, - Title: "Select Icon", - Filters: []runtime.FileFilter{ - { - DisplayName: "*.ico", - Pattern: "*.ico", - }, - }, - }) - - if err != nil { - fmt.Println(err) - } - - println("Selected file: ", result) - - if len(result) == 0 { - return "" - } else { - if fileInfo.IconName != "" { - // Delete existing icon - err := os.Remove(filepath.Join(getIconDir(profileName), fileInfo.IconName+".ico")) - if err != nil { - fmt.Println(err) - } - - // Delete base64 version - err = os.Remove(filepath.Join(getBase64Dir(profileName), fileInfo.IconName+".ico")) - if err != nil { - fmt.Println(err) - } - } - - uuid := uuid.New().String() - savePath := filepath.Join(getIconDir(profileName), uuid+".ico") - - // Create dir - noFolderErr := os.MkdirAll(filepath.Dir(savePath), os.ModePerm) - if noFolderErr != nil { - fmt.Println(noFolderErr) - } - - // Copy icon - err := Copy(result, savePath) - if err != nil { - fmt.Println(err) - } - - // Generate base64 version - GenerateBase64Icon(&profileName, &result, &uuid) - - return uuid - } -} - -func MatchMissingFile(fileInfo *fileInfo) { - files, err := os.ReadDir(filepath.Dir(fileInfo.Path)) - CheckErr(err, "Failed to read directory", false) - - for _, f := range files { - currentFileInfo, err := GetFileInfo(filepath.Dir((*fileInfo).Path), &f) - CheckErr(err, "Failed to get file info", false) - // Match by destination - if currentFileInfo.Destination == (*fileInfo).Destination { - fmt.Println("Found matching file: ", currentFileInfo) - // Rename currentFile to fileInfo.name - fmt.Println("Attempting to rename: ", currentFileInfo.Path, " to ", filepath.Join(filepath.Dir(currentFileInfo.Path), (*fileInfo).Name)+(*fileInfo).Extension) - err := os.Rename(currentFileInfo.Path, filepath.Join(filepath.Dir(currentFileInfo.Path), (*fileInfo).Name)+(*fileInfo).Extension) - CheckErr(err, "Failed to rename file", false) - if err == nil { - fmt.Println("Successfully renamed: ", currentFileInfo.Path, " to ", filepath.Join(filepath.Dir(currentFileInfo.Path), (*fileInfo).Name)+(*fileInfo).Extension) - } - } - } -} - -func SetIcon(profileName *string, fileInfo *fileInfo) { - // Check if fileInfo.Path exists - if _, err := os.Stat(filepath.Join(filepath.Dir((*fileInfo).Path), (*fileInfo).Name) + (*fileInfo).Extension); err != nil { - fmt.Println("Failed to find file: ", (*fileInfo).Path) - fmt.Println("Attempting to match missing file: ", (*fileInfo)) - MatchMissingFile(fileInfo) - } - - // Check if type is allowed - if contains(allowedTypes, (*fileInfo).Extension) && (*fileInfo).IconName != "" { - err := os.WriteFile(path.Join(getScriptDir(), "setlnkicon.vbs"), []byte(setlnkicon), 0644) - CheckErr(err, "Failed to write setlnkicon.vbs", false) - - cmd := exec.Command("cscript.exe", getScriptDir()+"\\setlnkicon.vbs", filepath.Dir((*fileInfo).Path), filepath.Base((*fileInfo).Path), getIconDir(*profileName)+"\\"+(*fileInfo).IconName+".ico", "0") - - _, err = cmd.Output() - CheckErr(err, "Failed to execute command", false) - } -} - -func SetDesc(fileInfo *fileInfo) { - // Check if type is allowed - if contains(allowedTypes, (*fileInfo).Extension) && (*fileInfo).Description != "" { - err := os.WriteFile(path.Join(getScriptDir(), "setlnkdesc.vbs"), []byte(setlnkdesc), 0644) - CheckErr(err, "Failed to write setlnkdesc.vbs", false) - - cmd := exec.Command("cscript.exe", getScriptDir()+"\\setlnkdesc.vbs", filepath.Dir((*fileInfo).Path), filepath.Base((*fileInfo).Path), (*fileInfo).Description) - - _, err = cmd.Output() - CheckErr(err, "Failed to execute command", false) - } -} - -func (a *App) RunProfile(profileName string, fileInfos []fileInfo) { - // Create scripts directory - err := os.MkdirAll(getScriptDir(), os.ModePerm) - CheckErr(err, "Failed to create script folder", false) - - for _, fileInfo := range fileInfos { - SetIcon(&profileName, &fileInfo) - SetDesc(&fileInfo) - } +// Greet returns a greeting for the given name +func (a *App) Greet(name string) string { + return fmt.Sprintf("Hello %s, It's show time!", name) } diff --git a/frontend/index.html b/frontend/index.html index 38fe32c..9f65e7c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,8 +2,9 @@ + - Desktop Manager + Vite + React + TS + Tailwind + shadcn/ui
diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..2204888 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3535 @@ +{ + "name": "desktop-manager", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "help", + "version": "0.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.37", + "@types/react-dom": "^18.0.11", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", + "@vitejs/plugin-react": "^4.0.0", + "autoprefixer": "^10.4.12", + "eslint": "^8.38.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.3.4", + "postcss": "^8.4.17", + "tailwindcss": "^3.1.8", + "typescript": "^5.0.2", + "vite": "^4.3.9" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", + "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", + "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", + "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", + "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.14.tgz", + "integrity": "sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.6.tgz", + "integrity": "sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.0.tgz", + "integrity": "sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.60.0", + "@typescript-eslint/type-utils": "5.60.0", + "@typescript-eslint/utils": "5.60.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.0.tgz", + "integrity": "sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.60.0", + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/typescript-estree": "5.60.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz", + "integrity": "sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/visitor-keys": "5.60.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.0.tgz", + "integrity": "sha512-X7NsRQddORMYRFH7FWo6sA9Y/zbJ8s1x1RIAtnlj6YprbToTiQnM6vxcMu7iYhdunmoC0rUWlca13D5DVHkK2g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.60.0", + "@typescript-eslint/utils": "5.60.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.0.tgz", + "integrity": "sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz", + "integrity": "sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/visitor-keys": "5.60.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.0.tgz", + "integrity": "sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.0", + "@typescript-eslint/types": "5.60.0", + "@typescript-eslint/typescript-estree": "5.60.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.0.tgz", + "integrity": "sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.60.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.1.tgz", + "integrity": "sha512-g25lL98essfeSj43HJ0o4DMp0325XK0ITkxpgChzJU/CyemgyChtlxfnRbjfwxDGCTRxTiXtQAsdebQXKMRSOA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.22.5", + "@babel/plugin-transform-react-jsx-self": "^7.22.5", + "@babel/plugin-transform-react-jsx-source": "^7.22.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0" + } + }, + "node_modules/acorn": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001507", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001507.tgz", + "integrity": "sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.440", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.440.tgz", + "integrity": "sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.3.5.tgz", + "integrity": "sha512-61qNIsc7fo9Pp/mju0J83kzvLm0Bsayu7OQSLEoJxLDCBjIIyb87bkzufoOvdDxLkSlMfkF7UxomC4+eztUBSA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jiti": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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 + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.2.tgz", + "integrity": "sha512-VLnkxZMDr3jpxgtmS8pQZ0UvhslmF4ADq/9w4erkctbgjCqLW9oa89fJuXEs4ZmgyoF7Dm8rMDKSS5b5u2hHUg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", + "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "dev": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json index 6ddcb42..e2b4244 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "frontend", + "name": "desktop-manager", "private": true, "version": "0.0.0", "type": "module", @@ -10,39 +10,30 @@ "preview": "vite preview" }, "dependencies": { - "@hookform/resolvers": "^3.3.4", - "@radix-ui/react-accordion": "^1.1.2", - "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-popover": "^1.0.7", - "@radix-ui/react-slot": "^1.0.2", - "class-variance-authority": "^0.6.0", - "clsx": "^1.2.1", - "cmdk": "^0.2.1", - "lucide-react": "^0.252.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-hook-form": "^7.51.0", - "tailwind-merge": "^1.13.2", - "tailwindcss-animate": "^1.0.6", - "zod": "^3.22.4" + "@radix-ui/react-slot": "^1.1.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.396.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.3.0", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@types/react": "^18.0.37", - "@types/react-dom": "^18.0.11", - "@typescript-eslint/eslint-plugin": "^5.59.0", - "@typescript-eslint/parser": "^5.59.0", - "@vitejs/plugin-react": "^4.0.0", - "autoprefixer": "^10.4.12", - "eslint": "^8.38.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.3.4", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.13.1", + "@typescript-eslint/parser": "^7.13.1", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "eslint": "^9.5.0", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.7", + "postcss": "^8.4.38", + "url": "^0.11.3", "path": "^0.12.7", - "postcss": "^8.4.17", - "tailwindcss": "^3.1.8", - "typescript": "^5.0.2", - "url": "^0.11.1", - "vite": "^4.3.9" + "tailwindcss": "^3.4.4", + "typescript": "^5.5.2", + "vite": "^5.3.1" } } diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 deleted file mode 100644 index 50d2b91..0000000 --- a/frontend/package.json.md5 +++ /dev/null @@ -1 +0,0 @@ -7be0872a8fdf54877448dff68fb922a2 \ No newline at end of file diff --git a/frontend/public/setlnkdesc.vbs b/frontend/public/setlnkdesc.vbs deleted file mode 100644 index ae861c6..0000000 --- a/frontend/public/setlnkdesc.vbs +++ /dev/null @@ -1,17 +0,0 @@ -Dim Arg, shortcutPath, shortcutName, iconPath -Set Arg = WScript.Arguments - -shortcutPath = Arg(0) -shortcutName = Arg(1) -shortcutDesc = Arg(2) - -Set objShell = CreateObject("Shell.Application") -Set objFolder = objShell.NameSpace(shortcutPath) - -Set objFolderItem = objFolder.ParseName(shortcutName) -Set objShortcut = objFolderItem.GetLink - -objShortcut.Description = shortcutDesc -objShortcut.Save - -set Arg = Nothing \ No newline at end of file diff --git a/frontend/public/setlnkicon.vbs b/frontend/public/setlnkicon.vbs deleted file mode 100644 index ae84591..0000000 --- a/frontend/public/setlnkicon.vbs +++ /dev/null @@ -1,18 +0,0 @@ -Dim Arg, shortcutPath, shortcutName, iconPath -Set Arg = WScript.Arguments - -shortcutPath = Arg(0) -shortcutName = Arg(1) -iconPath = Arg(2) -iconIndex = Arg(3) - -Set objShell = CreateObject("Shell.Application") -Set objFolder = objShell.NameSpace(shortcutPath) - -Set objFolderItem = objFolder.ParseName(shortcutName) -Set objShortcut = objFolderItem.GetLink - -objShortcut.SetIconLocation iconPath, iconIndex -objShortcut.Save - -set Arg = Nothing \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1c4280d..4844a39 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,17 +1,17 @@ -import { ThemeProvider } from "./contexts/theme-provider"; -import { ProfileProvider } from "./contexts/profile-provider"; -import TopBar from "./components/TopBar"; -import { FileGrid } from "./components/FileGrid"; +import { Button } from "@/components/ui/button" +import React from "react" function App() { + const [count, setCount] = React.useState(0) + return ( - - - - - - - ); +
+
+

Vite + React + TS + Tailwind + shadcn/ui

+ +
+
+ ) } -export default App; +export default App diff --git a/frontend/src/assets/folder-search.png b/frontend/src/assets/folder-search.png deleted file mode 100644 index 66524d998b105af01b62703d4da30824a34ddd0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386 zcmV-|0e$|7P)Px$JV``BR7gwhma$I5Fcd{kMwo%IqAM#BTZb-{I+cYP`V(vjP&Yn+75WFQ)S>DG zVnnR0?F`H?Lay{ISten(w1UdQu zIzA*7FaZYkzMmT83ed+j1r8lJ74UBEw}Cek+X7wzMX~wk099T4K)d|ZR}np-Dn-}8 z5ike(VFa#!G- - - - - \ No newline at end of file diff --git a/frontend/src/assets/unknown.png b/frontend/src/assets/unknown.png deleted file mode 100644 index a88693912669b8e4cd1b57c81dfd291827167b4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6090 zcmeHL^;;BexSnO#r8_00yIDjeR~kua=@3C!=>}l|Y5nMsazT)iR#0+DLAnGa1?i7a zYUwjx=llofI@kH(oS$aqdgh&H-uu3vdgJtU)k%pMi2wi~)znZi000Ph3jqk=;KkCb z^y%LxZv%BDplX;YcTeO|hj5yq(VlE5Wr%yx3DM8YMt?4!s4z?XkshNS$!{Y5V#UQ=|Tu+J3gnPub zZZps!kgsaFecKTqNoH=rpR@+QbvwOidU(@%k>?${aa1zBu)n~&oB-2ctAlaXrPq5< zu|Y-wX&4uU9iAkb;NIKfRDcwXK;lvIAfd4yhmU0lK{NFK&HvM|V2GTSqvOL3amOa8n<0&dO!EV7;`oyU=i5K(TrWF*% zzqAS=Mh66?<%1XAp+YNl6#*KhE|Kw}(4^gh87mm!t%zf@V^V^qf;u9O>dr3Kgr1ch z-{0d2h+aKws}5Y2OavJ|Wwb8l^1aPyD$@l0;fWfhq0yBxTNa6iQ1_EX`~xnze$7X+ zEtI}`SNqnd)z8wukK{dmnMJKk8jh^FAyX#k<5uPs+c=wF`s$EH)KLbUFf^t7Oph=| z&+`kAw`^5^Ym{PMa+1bd6QaaG%c8q-=B#<7k!zsg6cm`swaW6vnZ05~inOQDg10O9!dj4)f`g5K*rnMNRLK2Ow&^)` zB($dMZqoK;)$rAek#>U@l1gF%Nizo`C$s^p?`&8^;@qu*zrM2K36XBu5<7PSmCc+T zmt!6Dbk29@yKYnfJzi6)a@lPSRX6;H(?zMN`4Vhugvu~yAQ}lze-?aAt{L~W`j!}X zX-374@$dHmKeBZtN4)QTEnQQEdSF%$n`FoiRq3D%t4JC4BDTh&R2@TQf^G_%2WYrh zE@xrVN&Y--xil*>MB0#0wgH)cB9|-Y5Mt;*Q+wN;6T*lU)9DJaGBMoVYNo=XN`t!6 zdc|U{V1YLicISC5HLV|Mm*u|CmKdt^y@^2K>n1*ADV*R}SArMb95B zS?xvOh}FB*eE-?12yU-vYny3$BM-FkCH`^jlU*&F>AUCZq8Q*bbS>bO8=EiZkyCq| zIdAk3So56{~~XhCMtCIrIKOzzU5Pe#?kBjA0NjeGvi-gQY~ zl__|N-97l!04xAb(aHSLe@(=& zqsLF_Sv7LcaH?YBct~rvd#3b<_2xWv;o4mCh{@Cfs+)TGCM3WR(cgQ4+BEbAIYzHN za~2hvHGE`o?HBxVOTYX)zcl}fuA#Z{Qvv&A@WEV=*HQ*mRkUr&RulXhL736wQ0wr8 z_6=#1B+zW6XyR_s_JqX#6S1o2%PllVN&=_f_E9%P^v(A>9~KK$6YMKa=XRHF;o_8B z@tfUhzVaW%YuB9>cH9|RZO8>ifQH;zjOmli4c!MM_mp*RKJX-nm;0CmC2H=I$=puB zZ=jXAxovLF$MIg&Y423>yI6hb3Q@wAx_LI-w(ROKIOF;LMXSHyfmIj#o=~q=y;I`F z-dwaVwBM1Kg_rP2KAy!bs`Ej6h?}$K`(ddvP2o^jjRN#})VvSmdhQ9^qj?KcgxC2; z&kNb(^_!yJlf6sHQfo(C$*^_z<@u39sj z&eFVNmreXSOfs`d*Qltl*GRRT@tZ#V^_Dzm8F#HJM={#wAHL22os69S*=kHcEzZ8w z?YTy1>;47)kW1E45xaba1h~`yDJstJ>qRC1Ugqot));=5zXt)xB1TO%&4iL1NXsh9 ztuxBprOu)y(tfNvu8Ufj7&1;Y1M!C1w+xQMa?9QxX&DAh{!|A95&CW0qTg*oR8V*bmENcSjvW zv?Z&>3+e0na%ssYdlKf3vV!810s4{i%Vm=#8?4MLxcu%OZ* zCV9`$WYi$*4@Wb-vLxX)%hD~vW-~zT}+W=x;8SZ8bZZsC?X~MbB@e z@^%Nj@m)#$k+og6(;;J?ZToNUSJ)Y-h6xdU!~P0lM6+i8>k$o1kyVWT6Fa)NA ziEs1VN5VMNUcv_cS71PQol3)~CnZ}ZQ_>E#-bs0bXw#1-FZzV#I!uU)hr1SsueLr* z)y8df?d`0a^SVdZ7cXwgMjyjfY&3WcFK&-|J}j)1Jrym)=`0t)65msoDdX+v+oHiB zBO&R`-N^WtuAiG4M-Wp{R6teod!)M-N!6aZsCEP!FdC(aMryK`falqEEiw}X3Q`SiMBJMEH zj2VBBA=CVsbdO!;I|xZIu~g1q_eaGoXC(ojGV#1p3R`si&kUA@!(!FWlrpvJ0!5+x z_j&CG0Zd|X>QHK)V2u)WAt;)8;}LnupdCt!U28n56$kaG_86=d(9}$Hf_@mdF)xi- z8$t|B6Q|usZH3Fd(UsDX%t}ER^tM5PJG#7vhPnJ7I;jgFFK4iSe1BWlhVdq6Tgj)L z9MaCkZPUayzEhH?+&A4)S@n>Uo|K6?QQDKfwMWFPaoptd;xI7Wmro4Th?sUHru+{R zb|>iH+U&d=w~D4u?kC52OOqenvc_xgNX?~>Abs3Mk*uUBS1k$OXCOI$rG?cbh(b10 zIEbi7KqAiyvwj%7JvTj>ee65iJmeXwq*c~tk&k;O(?32Poln(^pr};#s z$!bR4jB6VNK>`Vp3fe~D!v=CEt+T5<*Ol3UQIDBO*|dL9zMlG?*K8(z)B7H3mo2cnKzJ>Q&cR8^XTfj#fOVD+_Z94c{t3t7Z_{+CQ zj>7jZV!TY3(KL$nxp3PI-YF?|{Wh&Sob`SJ$BZ~11LO|B-9lXYNytl#@0_(Bv_?m| z@SLgbY$W|u14qtDU{q4FlT)$B!_SGriyZ~C}%G zIU_hp2v0BCLS0Ri551Iw(q{keVAb{&Hu~Ppd9X_NWKMhTT;7(-geFB9ZiABn`dmL2 zZcFjBsF-HlWb?B{_l(2XKx28!J8^l*dcEygjrvb!a->*}c>+zX2Eo&6imdr+8&!`$ zEso=Q0#A}wBSz$AUxmjDdS4PhN9R)scRW5zvk^L{cG{QS`qClt)3XC? zAtMm4UUt47IGj7~8HG$0ZAIJ~FWF}8t8CLwy>gLf#F`@FFDZ9N*8QhsUQl4$=xj^z z5EA{9q;wzo&uBren3F}h#Tt`bkJ#?vnY%;NV{>OyoXu^ObFyu8(C~w)R~*4A7VKdh zm>X`a$a{7IjDnbOs`diPJSQlU7YSvMtI%M1v%7c6i*%3LmZBpvEzKVyRS-&AP|{?? z(%~g^{@jhaOeV5oxb)n}Dd1kIsNQ(8Z8*zUN-m*SMN@`7rl8pgrEG0h&f+VYHi5XaEuFFt(i?*l)gLMsO-9Mtg@C$=$DX44H6^_T_g<26G38C4F(d6-y)Xmt@zMH3#d3F>sPHdv2EL zl>v%E2@*liL$CNU|h;5HnS@fc>dpngr;G!(uI0_6z3WLM(4Ev z^x}_E^@z5T?}W-YbiTZ1ld;_40i~1kf-raL?%8D@rOiY-;77*6ll0C!MduVBbnXAw z^{x(Puxh`bccpDza#`U<;qrHVO6oNg-)#}U3~+C=-(gxPH~&3nJx^EcZrgO43iSq8 zwheg(scG{=5K|W?ykAp)N3Z;@5z46nsQj{F?Vm343m$7RI|=`D!cp?f%yGbSZ%_)7X1w)XXGr4tw9JN<_j%SN+ctp)XHR#g7xc3tOq zw|H!a75)puPMAg?hbzA(NAy2kyij}+yC+%%`MmT_c-Uh&9)EkK=Q%6PXA=$_ajvr@ zFP4lgD@0gW649H1=S4Ln0n)#o0bLJTB^<^9fqHkF>`#q4VsBC4kdM@jE+kuXKWbbqMphEoZ$0@o7vU)P z5f3CwpypYm&XJS5RNj7cwXo${>7|6Bb`Fv8o z)Gq&)=t=bZ3IR#LR2K2F#TigW)BWZK!DR#(k+gRaMFwCl2`A$uA!Q@^Hbol=*H^tN zf*3tQC<{cvDX2EZI>9Q4v9uEZ>sar9VTpD_QZq5ulgFMAV_jI~7OlN`uRxvjKVy3! zsm^6*8}}tPm)Q0=2__}N2*L2=Y40-X#|*H5WzVDKfp#Jc1yV9or1hN2BNs&IH(Xqv zszwuRs#8TiX<|LW1oV}u`$z-(H>P!(CV3!VK|L_LQ%rC|azMDHwhVG$%>Vzf|BIO2 akS9s9S#)Ue{{au@0Zmn1l`19Mi2neKVYMp& diff --git a/frontend/src/components/CreateProfileForm.tsx b/frontend/src/components/CreateProfileForm.tsx deleted file mode 100644 index bf6862c..0000000 --- a/frontend/src/components/CreateProfileForm.tsx +++ /dev/null @@ -1,76 +0,0 @@ -"use client" - -import { AddProfile } from "wailsjs/go/main/App"; - -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" -import { z } from "zod" - -import { Button } from "@/components/ui/button" -import { - Form, - FormControl, - /* FormDescription, */ - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" - -const AddProfileF = (name: string) => { - AddProfile(name).then(() => { - console.log("Profile created: " + name); - // select profile - }); -} - -const formSchema = z.object({ - profileName: z.string() - .min(1, { message: "Profile name is required" }) - .max(50, { message: "Profile name can not exceed 50 characters" }) - // Check for windows file name compatibility - .regex(/^(?!\.)/, { message: "Profile name can not start with a period" }) - .regex(/^(?!.*\.$)/, { message: "Profile name can not end with a period" }) - .regex(/^[^<>:"/\\|?*]*$/, { message: "Profile name can not contain any of the following characters: < > : \" / \\ | ? *" }) - .regex(/^(?!con$)(?!prn$)(?!aux$)(?!nul$)(?!com[0-9]$)(?!lpt[0-9]$)(?!com[¹²³])(?!lpt[¹²³])/i, { message: "Profile name can not be any of the following: con, prn, aux, nul, com[0-9], lpt[0-9], com⁽¹⁻³⁾, lpt⁽¹⁻³⁾" }) - , -}) - -export function CreateProfileForm() { - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - profileName: "", - } - }); - - function onSubmit(data: z.infer) { - AddProfileF(data.profileName); - } - - return ( -
- - ( - - Profile name - - - - {/* - This is the name of your profile - */} - - - )} - /> - - - - ) - -} \ No newline at end of file diff --git a/frontend/src/components/FileContainer.tsx b/frontend/src/components/FileContainer.tsx deleted file mode 100644 index 0098258..0000000 --- a/frontend/src/components/FileContainer.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useEffect, useRef, useState } from "react" -import { GetIcon, SaveIcon, SaveProfile } from "wailsjs/go/main/App" -import { Input } from "@/components/ui/input" -import { Pencil, Settings2, XOctagon } from "lucide-react" -import { Button } from "./ui/button" -import { useProfile } from "@/contexts/profile-provider" - -import unknown from "../assets/folder-search.svg" -import { fileInfo } from "@/structs" - - -interface FileContainerProps { - fileInfo: fileInfo - index: number -} - -export const FileContainer = (props: FileContainerProps) => { - const { profile, setProfile } = useProfile(); - const [editing, setEditing] = useState(false); - - const iconRef = useRef(null); - - useEffect(() => { - GetIconF(); - }, []) - - useEffect(() => { - if (props.fileInfo.iconName !== "") { - GetIconF(); - } else { - iconRef.current!.src = unknown - } - }, [props.fileInfo.iconName]) - - useEffect(() => { - if (editing) { - SaveProfile(profile.name, JSON.stringify(profile)).then(() => setEditing(false)) - } - }, [profile]) - - function GetIconF() { - if (props.fileInfo.iconName !== "") { - GetIcon(profile.name, props.fileInfo.iconName).then((res) => { - if (res) iconRef.current!.src = res - }); - } - } - - function DeleteRow() { - setEditing(true) - setProfile({ ...profile, value: profile.value.filter((_, i) => i !== props.index) }) - } - - return ( -
-
- - { - setEditing(true) - setProfile({ ...profile, value: profile.value.map((f, i) => i === props.index ? { ...f, name: e.target.value } : f) }) - }} - /> - { - setEditing(true); - setProfile({ ...profile, value: profile.value.map((f, i) => i === props.index ? { ...f, description: e.target.value } : f) }) - }} /> -
-
- - -
-
- ) -} \ No newline at end of file diff --git a/frontend/src/components/FileGrid.tsx b/frontend/src/components/FileGrid.tsx deleted file mode 100644 index 2aaa9d5..0000000 --- a/frontend/src/components/FileGrid.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useEffect, useState } from "react"; -import { useProfile } from "@/contexts/profile-provider"; -import { SaveProfile, GetFileInfo } from "wailsjs/go/main/App"; -import { FileContainer } from "./FileContainer" - -import { fileInfo } from "@/structs" -import { Button } from "./ui/button"; -import { PlusSquare } from "lucide-react"; - -export const FileGrid = () => { - const { profile, setProfile } = useProfile(); - const [fileInfos, setFileInfos] = useState(); - - /* const GetDesktopIconsF = () => { - GetDesktopIcons().then((res) => { - console.log(res); - setFileInfos(res); - }) - } - - useEffect(() => { - GetDesktopIconsF(); - }, []) */ - - useEffect(() => { - setFileInfos(profile?.value); - }, [profile]) - - const AddRow = () => { - GetFileInfo(profile.name).then((fileInfo) => { - if (fileInfo.extension !== ".lnk") return - setProfile({ ...profile, value: [...profile.value, fileInfo] }) - SaveProfile(profile.name, JSON.stringify({ ...profile, value: [...profile.value, fileInfo] })) - }) - } - - - return ( -
- {fileInfos?.map((fileInfo, i) => ( - - ))} -
- {profile.name && ( - - )} -
- -
- ) -} \ No newline at end of file diff --git a/frontend/src/components/TopBar.tsx b/frontend/src/components/TopBar.tsx deleted file mode 100644 index a3341bc..0000000 --- a/frontend/src/components/TopBar.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useEffect, useState } from "react"; -import { useProfile } from "@/contexts/profile-provider"; -import { GetProfiles, GetProfile, SyncDesktop, RemoveProfile, RunProfile } from "wailsjs/go/main/App"; -import { Button } from "./ui/button"; -import { Combobox } from "./ui/combobox" -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" -import { RefreshCw, Play, Plus, Pause } from 'lucide-react'; -import { ModeToggle } from "./mode-toggle" -import { CreateProfileForm } from "./CreateProfileForm" - -import { /* profile as profStruct, */ profileInfo, fileInfo } from "@/structs"; -import React from "react"; - -const TopBar = () => { - const { profile, setProfile } = useProfile(); - const [running, setRunning] = useState(false); - - const [profiles, setProfiles] = useState([]); - - useEffect(() => { - GetProfilesF(); - }, []) - - const GetProfilesF = () => { - GetProfiles().then((res) => setProfiles(res)); - } - - const onProfileChange = (profileName: string) => { - GetProfile(profileName).then((res) => { - setProfile(res); - }); - } - - return ( -
-
- onProfileChange(value)} - onExpand={() => GetProfilesF()} - onElementContextMenu={(value) => console.log("Context: " + value)} - /> - - - - - - - - -
-
- {profile.name && ( - - - - - - )} - - -
-
- ) -} - -export default TopBar; \ No newline at end of file diff --git a/frontend/src/components/mode-toggle.tsx b/frontend/src/components/mode-toggle.tsx deleted file mode 100644 index 992546f..0000000 --- a/frontend/src/components/mode-toggle.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Moon, Sun } from "lucide-react" - -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { useTheme } from "@/contexts/theme-provider" - -export function ModeToggle() { - const { setTheme } = useTheme() - - return ( - - - - - - setTheme("light")}> - Light - - setTheme("dark")}> - Dark - - setTheme("system")}> - System - - - - ) -} diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx deleted file mode 100644 index 5ee3e0b..0000000 --- a/frontend/src/components/ui/accordion.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from "react" -import * as AccordionPrimitive from "@radix-ui/react-accordion" -import { ChevronDown } from "lucide-react" - -import { cn } from "@/lib/utils" - -const Accordion = AccordionPrimitive.Root - -const AccordionItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AccordionItem.displayName = "AccordionItem" - -const AccordionTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - svg]:rotate-180", - className - )} - {...props} - > - {children} - - - -)) -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName - -const AccordionContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - -
{children}
-
-)) - -AccordionContent.displayName = AccordionPrimitive.Content.displayName - -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/frontend/src/components/ui/combobox.tsx b/frontend/src/components/ui/combobox.tsx deleted file mode 100644 index 1285f9b..0000000 --- a/frontend/src/components/ui/combobox.tsx +++ /dev/null @@ -1,86 +0,0 @@ -"use client" - -import * as React from "react" -import { Check, ChevronsUpDown } from "lucide-react" - -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from "@/components/ui/command" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" - -interface ComboboxProps { - elements: { value: any; label: string }[]; - placeholder?: string; - noElementsText?: string; - initialValue?: any; - searchBar?: boolean; - onChange: (value: any) => void; - onExpand?: () => void; - onElementContextMenu?: (value: any) => void; -} - -export function Combobox(props: ComboboxProps) { - const [open, setOpen] = React.useState(false) - const [value, setValue] = React.useState(props.initialValue ? props.initialValue : "") - - return ( - - - - - - - {(props.searchBar === true || props.searchBar === undefined) && ( - - )} - {props.noElementsText ? props.noElementsText : "No element found."} - - {props.elements.map((element) => ( - { - setValue(currentValue === value ? "" : currentValue) - props.onChange(currentValue === value ? "" : currentValue) - setOpen(false) - }} - onContextMenu={(value) => { - props.onElementContextMenu?.(value.currentTarget.getAttribute("data-value")) - }} - > - - {element.label} - - ))} - - - - - ) -} diff --git a/frontend/src/components/ui/command.tsx b/frontend/src/components/ui/command.tsx deleted file mode 100644 index d623ee0..0000000 --- a/frontend/src/components/ui/command.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import * as React from "react" -import { type DialogProps } from "@radix-ui/react-dialog" -import { Command as CommandPrimitive } from "cmdk" -import { Search } from "lucide-react" - -import { cn } from "@/lib/utils" -import { Dialog, DialogContent } from "@/components/ui/dialog" - -const Command = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -Command.displayName = CommandPrimitive.displayName - -interface CommandDialogProps extends DialogProps {} - -const CommandDialog = ({ children, ...props }: CommandDialogProps) => { - return ( - - - - {children} - - - - ) -} - -const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- - -
-)) - -CommandInput.displayName = CommandPrimitive.Input.displayName - -const CommandList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandList.displayName = CommandPrimitive.List.displayName - -const CommandEmpty = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->((props, ref) => ( - -)) - -CommandEmpty.displayName = CommandPrimitive.Empty.displayName - -const CommandGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandGroup.displayName = CommandPrimitive.Group.displayName - -const CommandSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -CommandSeparator.displayName = CommandPrimitive.Separator.displayName - -const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandItem.displayName = CommandPrimitive.Item.displayName - -const CommandShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ) -} -CommandShortcut.displayName = "CommandShortcut" - -export { - Command, - CommandDialog, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandShortcut, - CommandSeparator, -} diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx deleted file mode 100644 index c23630e..0000000 --- a/frontend/src/components/ui/dialog.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import * as React from "react" -import * as DialogPrimitive from "@radix-ui/react-dialog" -import { X } from "lucide-react" - -import { cn } from "@/lib/utils" - -const Dialog = DialogPrimitive.Root - -const DialogTrigger = DialogPrimitive.Trigger - -const DialogPortal = DialogPrimitive.Portal - -const DialogClose = DialogPrimitive.Close - -const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName - -const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - -)) -DialogContent.displayName = DialogPrimitive.Content.displayName - -const DialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -DialogHeader.displayName = "DialogHeader" - -const DialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -DialogFooter.displayName = "DialogFooter" - -const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogTitle.displayName = DialogPrimitive.Title.displayName - -const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogDescription.displayName = DialogPrimitive.Description.displayName - -export { - Dialog, - DialogPortal, - DialogOverlay, - DialogClose, - DialogTrigger, - DialogContent, - DialogHeader, - DialogFooter, - DialogTitle, - DialogDescription, -} diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx deleted file mode 100644 index 769ff7a..0000000 --- a/frontend/src/components/ui/dropdown-menu.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import * as React from "react" -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { Check, ChevronRight, Circle } from "lucide-react" - -import { cn } from "@/lib/utils" - -const DropdownMenu = DropdownMenuPrimitive.Root - -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger - -const DropdownMenuGroup = DropdownMenuPrimitive.Group - -const DropdownMenuPortal = DropdownMenuPrimitive.Portal - -const DropdownMenuSub = DropdownMenuPrimitive.Sub - -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup - -const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, children, ...props }, ref) => ( - - {children} - - -)) -DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName - -const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName - -const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)) -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName - -const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, ...props }, ref) => ( - -)) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName - -const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - -)) -DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName - -const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - - - - {children} - -)) -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName - -const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, ...props }, ref) => ( - -)) -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName - -const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName - -const DropdownMenuShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ) -} -DropdownMenuShortcut.displayName = "DropdownMenuShortcut" - -export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup, -} diff --git a/frontend/src/components/ui/form.tsx b/frontend/src/components/ui/form.tsx deleted file mode 100644 index 4603f8b..0000000 --- a/frontend/src/components/ui/form.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { Slot } from "@radix-ui/react-slot" -import { - Controller, - ControllerProps, - FieldPath, - FieldValues, - FormProvider, - useFormContext, -} from "react-hook-form" - -import { cn } from "@/lib/utils" -import { Label } from "@/components/ui/label" - -const Form = FormProvider - -type FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath -> = { - name: TName -} - -const FormFieldContext = React.createContext( - {} as FormFieldContextValue -) - -const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath ->({ - ...props -}: ControllerProps) => { - return ( - - - - ) -} - -const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext) - const itemContext = React.useContext(FormItemContext) - const { getFieldState, formState } = useFormContext() - - const fieldState = getFieldState(fieldContext.name, formState) - - if (!fieldContext) { - throw new Error("useFormField should be used within ") - } - - const { id } = itemContext - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - } -} - -type FormItemContextValue = { - id: string -} - -const FormItemContext = React.createContext( - {} as FormItemContextValue -) - -const FormItem = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => { - const id = React.useId() - - return ( - -
- - ) -}) -FormItem.displayName = "FormItem" - -const FormLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => { - const { error, formItemId } = useFormField() - - return ( -
{ - SetEnableLogging(!enableLogging).then(() => { - setEnableLogging(!enableLogging); - }); + SetConfigField("enableLogging", String(!enableLogging)).then( + () => { + setEnableLogging(!enableLogging); + } + ); }} /> @@ -113,7 +101,7 @@ export function AdvancedSettings() { value="trace" aria-label="Enable trace logging" onClick={() => { - SetEnableTrace(!enableTrace).then(() => { + SetConfigField("enableTrace", String(!enableTrace)).then(() => { setEnableTrace(!enableTrace); }); }} @@ -125,7 +113,7 @@ export function AdvancedSettings() { value="debug" aria-label="Enable debug logging" onClick={() => { - SetEnableDebug(!enableDebug).then(() => { + SetConfigField("enableDebug", String(!enableDebug)).then(() => { setEnableDebug(!enableDebug); }); }} @@ -137,7 +125,7 @@ export function AdvancedSettings() { value="info" aria-label="Enable info logging" onClick={() => { - SetEnableInfo(!enableInfo).then(() => { + SetConfigField("enableInfo", String(!enableInfo)).then(() => { setEnableInfo(!enableInfo); }); }} @@ -149,7 +137,7 @@ export function AdvancedSettings() { value="warn" aria-label="Enable warn logging" onClick={() => { - SetEnableWarn(!enableWarn).then(() => { + SetConfigField("enableWarn", String(!enableWarn)).then(() => { setEnableWarn(!enableWarn); }); }} @@ -161,7 +149,7 @@ export function AdvancedSettings() { value="error" aria-label="Enable error logging" onClick={() => { - SetEnableError(!enableError).then(() => { + SetConfigField("enableError", String(!enableError)).then(() => { setEnableError(!enableError); }); }} @@ -173,7 +161,7 @@ export function AdvancedSettings() { value="fatal" aria-label="Enable fatal logging" onClick={() => { - SetEnableFatal(!enableFatal).then(() => { + SetConfigField("enableFatal", String(!enableFatal)).then(() => { setEnableFatal(!enableFatal); }); }} diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index 3bdca59..b4e25b7 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -1,7 +1,4 @@ -import { - GetUseSystemTitleBar, - SetUseSystemTitleBar, -} from "wailsjs/go/main/App"; +import { GetConfigField, SetConfigField } from "wailsjs/go/main/App"; import { Moon, Sun, Monitor } from "lucide-react"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { @@ -23,8 +20,8 @@ export function AppSettings() { const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); useEffect(() => { - GetUseSystemTitleBar().then((value) => { - setUseSystemTitleBar(value); + GetConfigField("useSystemTitleBar").then((value) => { + setUseSystemTitleBar(value === "true"); }); }, []); @@ -80,7 +77,7 @@ export function AppSettings() { { - SetUseSystemTitleBar(value).then(() => { + SetConfigField("useSystemTitleBar", String(value)).then(() => { setUseSystemTitleBar(value); }); }} diff --git a/frontend/src/components/Settings/GeneralSettings.tsx b/frontend/src/components/Settings/GeneralSettings.tsx index 32b61a1..e669586 100644 --- a/frontend/src/components/Settings/GeneralSettings.tsx +++ b/frontend/src/components/Settings/GeneralSettings.tsx @@ -1,4 +1,4 @@ -import { GetLanguage, SetLanguage } from "wailsjs/go/main/App"; +import { SetConfigField, GetConfigField } from "wailsjs/go/main/App"; import { SettingsGroup, SettingsItem, @@ -19,16 +19,20 @@ export function GeneralSettings() { }; const [language, setLanguage] = useState("en"); + const [langIsSet, setLangIsSet] = useState(false); useEffect(() => { - GetLanguage().then((value) => { + GetConfigField("language").then((value) => { setLanguage(value); + setLangIsSet(true); }); }, []); useEffect(() => { - SetLanguage(language); - changeLanguage(language); + if (langIsSet) { + SetConfigField("language", language); + changeLanguage(language); + } }, [language]); return ( diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 68bf928..d316f82 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -1,4 +1,4 @@ -import { Minimize, Maximize, Quit, GetUseSystemTitleBar } from "wailsjs/go/main/App"; +import { Minimize, Maximize, Quit, GetConfigField } from "wailsjs/go/main/App"; import { Minus, Copy, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import icon from "../assets/appicon.png"; @@ -8,8 +8,8 @@ export default function TitleBar() { const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); useEffect(() => { - GetUseSystemTitleBar().then((value) => { - setUseSystemTitleBar(value) + GetConfigField("useSystemTitleBar").then((value) => { + setUseSystemTitleBar(value === "true"); }) }), [] diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 2a89358..7bdcee6 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -1,14 +1,14 @@ -import { GetLanguage } from "wailsjs/go/main/App"; +import { GetConfigField } from "wailsjs/go/main/App"; import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import HttpApi from "i18next-http-backend"; -import locales from '@/locales.json'; +import locales from "@/locales.json"; const initializeI18n = async () => { - const language = (await GetLanguage()) || "en"; + const language = (await GetConfigField("language")) || "en"; - const supportedLngs = locales.locales.map(language => language.code); + const supportedLngs = locales.locales.map((language) => language.code); i18n .use(HttpApi) @@ -30,4 +30,4 @@ const initializeI18n = async () => { return i18n; }; -export default initializeI18n; \ No newline at end of file +export default initializeI18n; From 6a90a2f7af29f385fd90b0aa111e30aed53a5b1e Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 05:42:42 +0300 Subject: [PATCH 031/221] remove set-get theme functions --- config.go | 20 ------- frontend/src/contexts/theme-provider.tsx | 68 ++++++++++++------------ 2 files changed, 34 insertions(+), 54 deletions(-) diff --git a/config.go b/config.go index d908bfe..b4f8edb 100644 --- a/config.go +++ b/config.go @@ -193,26 +193,6 @@ func (app *App) SetConfigField(field string, value string) { } } -func (app *App) GetTheme() string { - if config.Theme == nil { - return "undefined" - } - return *config.Theme -} - -func (app *App) SetTheme(theme string) { - runtime.LogDebug(app.ctx, fmt.Sprintf("Setting theme to %s", theme)) - - config.Theme = &theme - err := SetConfig(config) - - if err != nil { - runtime.LogError(app.ctx, err.Error()) - } - - runtime.LogInfo(app.ctx, fmt.Sprintf("Setted theme to %s", theme)) -} - // Set default config to configPath func SetDefaultConfig() error { config = GetDefaultConfig() diff --git a/frontend/src/contexts/theme-provider.tsx b/frontend/src/contexts/theme-provider.tsx index 0812b6c..9f20368 100644 --- a/frontend/src/contexts/theme-provider.tsx +++ b/frontend/src/contexts/theme-provider.tsx @@ -1,89 +1,89 @@ -import { GetTheme, Log, SetTheme } from "wailsjs/go/main/App" -import { createContext, useContext, useEffect, useState } from "react" +import { GetConfigField, Log, SetConfigField } from "wailsjs/go/main/App"; +import { createContext, useContext, useEffect, useState } from "react"; -type Theme = "dark" | "light" | "system" +type Theme = "dark" | "light" | "system"; type ThemeProviderProps = { - children: React.ReactNode - defaultTheme?: Theme -} + children: React.ReactNode; + defaultTheme?: Theme; +}; type ThemeProviderState = { - theme: Theme - setTheme: (theme: Theme) => void -} + theme: Theme; + setTheme: (theme: Theme) => void; +}; const initialState: ThemeProviderState = { theme: "system", setTheme: () => null, -} +}; -const ThemeProviderContext = createContext(initialState) +const ThemeProviderContext = createContext(initialState); export function ThemeProvider({ children, defaultTheme = "system", ...props }: ThemeProviderProps) { - const [theme, setThemeState] = useState(defaultTheme) + const [theme, setThemeState] = useState(defaultTheme); useEffect(() => { const fetchTheme = async () => { try { - const storedTheme = await GetTheme() + const storedTheme = await GetConfigField("theme"); if (storedTheme) { - setThemeState(storedTheme as Theme) + setThemeState(storedTheme as Theme); } } catch (error) { - Log("Failed to fetch theme", 4) + Log("Failed to fetch theme", 4); } - } + }; - fetchTheme() - }, []) + fetchTheme(); + }, []); useEffect(() => { - const root = window.document.documentElement + const root = window.document.documentElement; - root.classList.remove("light", "dark") + root.classList.remove("light", "dark"); if (theme === "system") { const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") .matches ? "dark" - : "light" + : "light"; - root.classList.add(systemTheme) - return + root.classList.add(systemTheme); + return; } - root.classList.add(theme) - }, [theme]) + root.classList.add(theme); + }, [theme]); const value = { theme, setTheme: async (theme: Theme) => { try { - await SetTheme(theme) - setThemeState(theme) + await SetConfigField("theme", theme); + setThemeState(theme); } catch (error) { - Log("Failed to set theme", 4) + Log("Failed to set theme", 4); } }, - } + }; return ( {children} - ) + ); } export const useTheme = () => { - const context = useContext(ThemeProviderContext) + const context = useContext(ThemeProviderContext); if (context === undefined) - throw new Error("useTheme must be used within a ThemeProvider") + throw new Error("useTheme must be used within a ThemeProvider"); - return context -} + return context; +}; From 404cb2647063065a2f2159d93341644608117c76 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 05:48:24 +0300 Subject: [PATCH 032/221] move theme setting to general --- .../src/components/Settings/AppSettings.tsx | 38 ------------------ .../components/Settings/GeneralSettings.tsx | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index b4e25b7..5c605c7 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -1,6 +1,4 @@ import { GetConfigField, SetConfigField } from "wailsjs/go/main/App"; -import { Moon, Sun, Monitor } from "lucide-react"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { SettingsGroup, SettingsItem, @@ -9,14 +7,12 @@ import { SettingLabel, } from "../ui/settings-group"; import { Switch } from "@/components/ui/switch"; -import { useTheme } from "@/contexts/theme-provider"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; export function AppSettings() { const { t } = useTranslation(); - const { theme, setTheme } = useTheme(); const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); useEffect(() => { @@ -27,40 +23,6 @@ export function AppSettings() { return ( - -
- {t("settings.application.theme.label")} - - {t("settings.application.theme.description")} - -
- - - setTheme("system")} - > - - - setTheme("light")} - > - - - setTheme("dark")} - > - - - - -
-
diff --git a/frontend/src/components/Settings/GeneralSettings.tsx b/frontend/src/components/Settings/GeneralSettings.tsx index e669586..860af8a 100644 --- a/frontend/src/components/Settings/GeneralSettings.tsx +++ b/frontend/src/components/Settings/GeneralSettings.tsx @@ -10,6 +10,9 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Combobox } from "../ui/combobox"; import locales from "@/locales.json"; +import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; +import { Monitor, Moon, Sun } from "lucide-react"; +import { useTheme } from "@/contexts/theme-provider"; export function GeneralSettings() { const { t, i18n } = useTranslation(); @@ -35,6 +38,8 @@ export function GeneralSettings() { } }, [language]); + const { theme, setTheme } = useTheme(); + return ( @@ -61,6 +66,40 @@ export function GeneralSettings() { /> + + +
+ {t("settings.application.theme.label")} + + {t("settings.application.theme.description")} + +
+ + + setTheme("system")} + > + + + setTheme("light")} + > + + + setTheme("dark")} + > + + + + +
); } From e93c6496d2635ef53136c729a8d5babc144f487a Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 15:46:18 +0300 Subject: [PATCH 033/221] generalize get-set config field functions --- config.go | 131 ++++++++++-------- .../components/Settings/AdvancedSettings.tsx | 28 ++-- .../src/components/Settings/AppSettings.tsx | 4 +- .../components/Settings/GeneralSettings.tsx | 4 +- frontend/src/components/TitleBar.tsx | 4 +- frontend/src/contexts/theme-provider.tsx | 4 +- frontend/src/i18n.ts | 2 +- 7 files changed, 97 insertions(+), 80 deletions(-) diff --git a/config.go b/config.go index b4f8edb..e874a37 100644 --- a/config.go +++ b/config.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "reflect" "strconv" "github.com/wailsapp/wails/v2/pkg/runtime" @@ -121,76 +122,90 @@ func merge_defaults() { } } -func (app *App) GetConfigField(field string) string { - runtime.LogDebug(app.ctx, fmt.Sprintf("Getting config field %s", field)) - - switch field { - case "theme": - return *config.Theme - case "useSystemTitleBar": - return fmt.Sprintf("%t", *config.UseSystemTitleBar) - case "enableLogging": - return fmt.Sprintf("%t", *config.EnableLogging) - case "enableTrace": - return fmt.Sprintf("%t", *config.EnableTrace) - case "enableDebug": - return fmt.Sprintf("%t", *config.EnableDebug) - case "enableInfo": - return fmt.Sprintf("%t", *config.EnableInfo) - case "enableWarn": - return fmt.Sprintf("%t", *config.EnableWarn) - case "enableError": - return fmt.Sprintf("%t", *config.EnableError) - case "enableFatal": - return fmt.Sprintf("%t", *config.EnableFatal) - case "language": - return *config.Language - default: - runtime.LogWarning(app.ctx, fmt.Sprintf("Unknown config field %s", field)) +func (app *App) GetConfigField(fieldName string) string { + runtime.LogDebug(app.ctx, fmt.Sprintf("Attempting to get config field %s", fieldName)) + + // Get the reflection Type and Value of the Config struct + v := reflect.ValueOf(&config).Elem() + t := v.Type() + + // Find the field by name + _, found := t.FieldByName(fieldName) + if !found { + runtime.LogWarning(app.ctx, fmt.Sprintf("Unknown config field: %s", fieldName)) return "undefined" } + + // Get the field value + fieldValue := v.FieldByName(fieldName) + + // Check if the field is a pointer + if fieldValue.Kind() == reflect.Ptr { + if fieldValue.IsNil() { + runtime.LogWarning(app.ctx, fmt.Sprintf("Config field %s is nil", fieldName)) + return "undefined" + } + // Dereference the pointer + fieldValue = fieldValue.Elem() + } + + runtime.LogDebug(app.ctx, fmt.Sprintf("Config field %s has value: %v", fieldName, fieldValue.Interface())) + return fmt.Sprintf("%v", fieldValue.Interface()) } -func (app *App) SetConfigField(field string, value string) { - runtime.LogDebug(app.ctx, fmt.Sprintf("Setting config field %s to %s", field, value)) +func (app *App) SetConfigField(fieldName string, value string) { + runtime.LogDebug(app.ctx, fmt.Sprintf("Attempting to set config field %s to %s", fieldName, value)) - valueBool, err := strconv.ParseBool(value) + v := reflect.ValueOf(&config).Elem() + t := v.Type() - if err != nil { - runtime.LogDebug(app.ctx, fmt.Sprintf("Failed to parse %s to bool", value)) + _, found := t.FieldByName(fieldName) + if !found { + runtime.LogWarning(app.ctx, fmt.Sprintf("Unknown config field: %s", fieldName)) + return } - unknown_field := false - - switch field { - case "theme": - config.Theme = &value - case "useSystemTitleBar": - config.UseSystemTitleBar = &valueBool - case "enableLogging": - config.EnableLogging = &valueBool - case "enableTrace": - config.EnableTrace = &valueBool - case "enableDebug": - config.EnableDebug = &valueBool - case "enableInfo": - config.EnableInfo = &valueBool - case "enableWarn": - config.EnableWarn = &valueBool - case "enableError": - config.EnableError = &valueBool - case "enableFatal": - config.EnableFatal = &valueBool - case "language": - config.Language = &value + fieldValue := v.FieldByName(fieldName) + + if fieldValue.Kind() == reflect.Ptr { + if fieldValue.IsNil() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + } + fieldValue = fieldValue.Elem() + } + + switch fieldValue.Kind() { + case reflect.String: + fieldValue.SetString(value) + case reflect.Bool: + boolVal, err := strconv.ParseBool(value) + if err != nil { + runtime.LogError(app.ctx, fmt.Sprintf("Invalid value for boolean field %s: %s", fieldName, value)) + return + } + fieldValue.SetBool(boolVal) + case reflect.Int: + intVal, err := strconv.Atoi(value) + if err != nil { + runtime.LogError(app.ctx, fmt.Sprintf("Invalid value for integer field %s: %s", fieldName, value)) + return + } + fieldValue.SetInt(int64(intVal)) default: - unknown_field = true - runtime.LogWarning(app.ctx, fmt.Sprintf("Unknown config field %s", field)) + runtime.LogError(app.ctx, fmt.Sprintf("Unsupported field type for field %s", fieldName)) + return } - if !unknown_field { - SetConfig(config) + runtime.LogDebug(app.ctx, fmt.Sprintf("Config field %s set to %v", fieldName, fieldValue.Interface())) + + runtime.LogDebug(app.ctx, "Attempting to write to config file") + err := SetConfig(config) + + if err != nil { + runtime.LogError(app.ctx, fmt.Sprintf("Failed to write to config file: %v", err)) } + + runtime.LogDebug(app.ctx, "Config file written") } // Set default config to configPath diff --git a/frontend/src/components/Settings/AdvancedSettings.tsx b/frontend/src/components/Settings/AdvancedSettings.tsx index 44053a3..1f60185 100644 --- a/frontend/src/components/Settings/AdvancedSettings.tsx +++ b/frontend/src/components/Settings/AdvancedSettings.tsx @@ -23,31 +23,31 @@ export function AdvancedSettings() { const [enableFatal, setEnableFatal] = useState(false); useEffect(() => { - GetConfigField("enableLogging").then((value) => { + GetConfigField("EnableLogging").then((value) => { setEnableLogging(value === "true"); }); - GetConfigField("enableTrace").then((value) => { + GetConfigField("EnableTrace").then((value) => { setEnableTrace(value === "true"); }); - GetConfigField("enableDebug").then((value) => { + GetConfigField("EnableDebug").then((value) => { setEnableDebug(value === "true"); }); - GetConfigField("enableInfo").then((value) => { + GetConfigField("EnableInfo").then((value) => { setEnableInfo(value === "true"); }); - GetConfigField("enableWarn").then((value) => { + GetConfigField("EnableWarn").then((value) => { setEnableWarn(value === "true"); }); - GetConfigField("enableError").then((value) => { + GetConfigField("EnableError").then((value) => { setEnableError(value === "true"); }); - GetConfigField("enableFatal").then((value) => { + GetConfigField("EnableFatal").then((value) => { setEnableFatal(value === "true"); }); }, []); @@ -68,7 +68,7 @@ export function AdvancedSettings() { { - SetConfigField("enableLogging", String(!enableLogging)).then( + SetConfigField("EnableLogging", String(!enableLogging)).then( () => { setEnableLogging(!enableLogging); } @@ -101,7 +101,7 @@ export function AdvancedSettings() { value="trace" aria-label="Enable trace logging" onClick={() => { - SetConfigField("enableTrace", String(!enableTrace)).then(() => { + SetConfigField("EnableTrace", String(!enableTrace)).then(() => { setEnableTrace(!enableTrace); }); }} @@ -113,7 +113,7 @@ export function AdvancedSettings() { value="debug" aria-label="Enable debug logging" onClick={() => { - SetConfigField("enableDebug", String(!enableDebug)).then(() => { + SetConfigField("EnableDebug", String(!enableDebug)).then(() => { setEnableDebug(!enableDebug); }); }} @@ -125,7 +125,7 @@ export function AdvancedSettings() { value="info" aria-label="Enable info logging" onClick={() => { - SetConfigField("enableInfo", String(!enableInfo)).then(() => { + SetConfigField("EnableInfo", String(!enableInfo)).then(() => { setEnableInfo(!enableInfo); }); }} @@ -137,7 +137,7 @@ export function AdvancedSettings() { value="warn" aria-label="Enable warn logging" onClick={() => { - SetConfigField("enableWarn", String(!enableWarn)).then(() => { + SetConfigField("EnableWarn", String(!enableWarn)).then(() => { setEnableWarn(!enableWarn); }); }} @@ -149,7 +149,7 @@ export function AdvancedSettings() { value="error" aria-label="Enable error logging" onClick={() => { - SetConfigField("enableError", String(!enableError)).then(() => { + SetConfigField("EnableError", String(!enableError)).then(() => { setEnableError(!enableError); }); }} @@ -161,7 +161,7 @@ export function AdvancedSettings() { value="fatal" aria-label="Enable fatal logging" onClick={() => { - SetConfigField("enableFatal", String(!enableFatal)).then(() => { + SetConfigField("EnableFatal", String(!enableFatal)).then(() => { setEnableFatal(!enableFatal); }); }} diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index 5c605c7..6b27b3e 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -16,7 +16,7 @@ export function AppSettings() { const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); useEffect(() => { - GetConfigField("useSystemTitleBar").then((value) => { + GetConfigField("UseSystemTitleBar").then((value) => { setUseSystemTitleBar(value === "true"); }); }, []); @@ -39,7 +39,7 @@ export function AppSettings() { { - SetConfigField("useSystemTitleBar", String(value)).then(() => { + SetConfigField("UseSystemTitleBar", String(value)).then(() => { setUseSystemTitleBar(value); }); }} diff --git a/frontend/src/components/Settings/GeneralSettings.tsx b/frontend/src/components/Settings/GeneralSettings.tsx index 860af8a..66e09f3 100644 --- a/frontend/src/components/Settings/GeneralSettings.tsx +++ b/frontend/src/components/Settings/GeneralSettings.tsx @@ -25,7 +25,7 @@ export function GeneralSettings() { const [langIsSet, setLangIsSet] = useState(false); useEffect(() => { - GetConfigField("language").then((value) => { + GetConfigField("Language").then((value) => { setLanguage(value); setLangIsSet(true); }); @@ -33,7 +33,7 @@ export function GeneralSettings() { useEffect(() => { if (langIsSet) { - SetConfigField("language", language); + SetConfigField("Language", language); changeLanguage(language); } }, [language]); diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index d316f82..5213e3a 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -8,9 +8,11 @@ export default function TitleBar() { const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); useEffect(() => { - GetConfigField("useSystemTitleBar").then((value) => { + GetConfigField("UseSystemTitleBar").then((value) => { setUseSystemTitleBar(value === "true"); }) + + GetConfigField("deneme") }), [] return ( diff --git a/frontend/src/contexts/theme-provider.tsx b/frontend/src/contexts/theme-provider.tsx index 9f20368..e3c0b51 100644 --- a/frontend/src/contexts/theme-provider.tsx +++ b/frontend/src/contexts/theme-provider.tsx @@ -30,7 +30,7 @@ export function ThemeProvider({ useEffect(() => { const fetchTheme = async () => { try { - const storedTheme = await GetConfigField("theme"); + const storedTheme = await GetConfigField("Theme"); if (storedTheme) { setThemeState(storedTheme as Theme); } @@ -64,7 +64,7 @@ export function ThemeProvider({ theme, setTheme: async (theme: Theme) => { try { - await SetConfigField("theme", theme); + await SetConfigField("Theme", theme); setThemeState(theme); } catch (error) { Log("Failed to set theme", 4); diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 7bdcee6..83e6b31 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -6,7 +6,7 @@ import HttpApi from "i18next-http-backend"; import locales from "@/locales.json"; const initializeI18n = async () => { - const language = (await GetConfigField("language")) || "en"; + const language = (await GetConfigField("Language")) || "en"; const supportedLngs = locales.locales.map((language) => language.code); From fc3d80cec46b5ac0ca3676de2692428b1e71c53d Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 15:54:50 +0300 Subject: [PATCH 034/221] generalize merge defaults config function --- config.go | 57 +++++++++++++++---------------------------------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/config.go b/config.go index e874a37..34b18cc 100644 --- a/config.go +++ b/config.go @@ -53,7 +53,7 @@ func GetDefaultConfig() Config { var config Config = GetDefaultConfig() func config_init() error { - err := CreateConfig() + err := CreateConfigIfNotExist() if err != nil { return errors.New("failed to create config file") } @@ -72,49 +72,22 @@ func merge_defaults() { fmt.Println("Merging default config") - merged := false + v := reflect.ValueOf(&config).Elem() + t := v.Type() - if config.Theme == nil { - config.Theme = defaultConfig.Theme - merged = true - } - if config.UseSystemTitleBar == nil { - config.UseSystemTitleBar = defaultConfig.UseSystemTitleBar - merged = true - } + merged := false - if config.EnableLogging == nil { - config.EnableLogging = defaultConfig.EnableLogging - merged = true - } - if config.EnableTrace == nil { - config.EnableTrace = defaultConfig.EnableTrace - merged = true - } - if config.EnableDebug == nil { - config.EnableDebug = defaultConfig.EnableDebug - merged = true - } - if config.EnableInfo == nil { - config.EnableInfo = defaultConfig.EnableInfo - merged = true - } - if config.EnableWarn == nil { - config.EnableWarn = defaultConfig.EnableWarn - merged = true - } - if config.EnableError == nil { - config.EnableError = defaultConfig.EnableError - merged = true - } - if config.EnableFatal == nil { - config.EnableFatal = defaultConfig.EnableFatal - merged = true - } + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + fieldName := field.Name + fieldValue := v.FieldByName(fieldName) - if config.Language == nil { - config.Language = defaultConfig.Language - merged = true + if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { + // If config's field is nil, set it to the default value's field + defaultValue := reflect.ValueOf(&defaultConfig).Elem().FieldByName(fieldName) + fieldValue.Set(defaultValue) + merged = true + } } if merged { @@ -226,7 +199,7 @@ func SetConfig(newConfig Config) error { } // Creates a default config at configPath if none exists -func CreateConfig() error { +func CreateConfigIfNotExist() error { configPath = get_config_path() if _, err := os.Stat(configPath); os.IsNotExist(err) { From 4be00921355a2aa510c37f1de424d0c7daf4fca0 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 16:01:57 +0300 Subject: [PATCH 035/221] fix tab selection when maximizing --- frontend/src/components/ui/tabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx index 989c7ec..090d6d6 100644 --- a/frontend/src/components/ui/tabs.tsx +++ b/frontend/src/components/ui/tabs.tsx @@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef< Date: Tue, 25 Jun 2024 16:30:18 +0300 Subject: [PATCH 036/221] save window state --- app.go | 16 ++++++++++++++++ config.go | 5 ++++- frontend/src/components/TitleBar.tsx | 2 -- logger.go | 14 +++++++------- main.go | 15 ++++++++++++++- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app.go b/app.go index fa31ffd..95cc0e1 100644 --- a/app.go +++ b/app.go @@ -41,6 +41,22 @@ func (a App) domReady(ctx context.Context) { // either by clicking the window close button or calling runtime.Quit. // Returning true will cause the application to continue, false will continue shutdown as normal. func (a *App) beforeClose(ctx context.Context) (prevent bool) { + if runtime.WindowIsFullscreen(a.ctx) { + runtime.LogInfo(a.ctx, "Setting window state to fullscreen") + var windowState = 3 + config.WindowStartState = &windowState + } else if runtime.WindowIsMaximised(a.ctx) { + var windowState = 2 + config.WindowStartState = &windowState + runtime.LogInfo(a.ctx, "Setting window state to maximized") + } else { + var windowState = 0 + config.WindowStartState = &windowState + runtime.LogInfo(a.ctx, "Setting window state to normal") + } + + WriteConfig() + return false } diff --git a/config.go b/config.go index 34b18cc..04567b1 100644 --- a/config.go +++ b/config.go @@ -22,6 +22,7 @@ type Config struct { EnableError *bool `json:"enableError"` // true, false EnableFatal *bool `json:"enableFatal"` // true, false Language *string `json:"language"` // en, tr + WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen } func GetDefaultConfig() Config { @@ -35,6 +36,7 @@ func GetDefaultConfig() Config { defaultEnableError := true defaultEnableFatal := true defaultLanguage := "en" + defaultWindowStartState := 0 return Config{ Theme: &defaultTheme, @@ -47,6 +49,7 @@ func GetDefaultConfig() Config { EnableError: &defaultEnableError, EnableFatal: &defaultEnableFatal, Language: &defaultLanguage, + WindowStartState: &defaultWindowStartState, } } @@ -172,7 +175,7 @@ func (app *App) SetConfigField(fieldName string, value string) { runtime.LogDebug(app.ctx, fmt.Sprintf("Config field %s set to %v", fieldName, fieldValue.Interface())) runtime.LogDebug(app.ctx, "Attempting to write to config file") - err := SetConfig(config) + err := WriteConfig() if err != nil { runtime.LogError(app.ctx, fmt.Sprintf("Failed to write to config file: %v", err)) diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 5213e3a..1dfea99 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -11,8 +11,6 @@ export default function TitleBar() { GetConfigField("UseSystemTitleBar").then((value) => { setUseSystemTitleBar(value === "true"); }) - - GetConfigField("deneme") }), [] return ( diff --git a/logger.go b/logger.go index bc5c5f4..85081b5 100644 --- a/logger.go +++ b/logger.go @@ -27,7 +27,7 @@ func NewLogger(filename string) Logger { } } -// Print works like Sprintf. +// Print func (l *FileLogger) Print(message string) { f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { @@ -44,7 +44,7 @@ func (l *FileLogger) Println(message string) { l.Print(message + "\n") } -// Trace level logging. Works like Sprintf. +// Trace level logging. func (l *FileLogger) Trace(message string) { if *config.EnableTrace { println("TRACE | " + message) @@ -52,7 +52,7 @@ func (l *FileLogger) Trace(message string) { } } -// Debug level logging. Works like Sprintf. +// Debug level logging. func (l *FileLogger) Debug(message string) { if *config.EnableDebug { println("DEBUG | " + message) @@ -60,7 +60,7 @@ func (l *FileLogger) Debug(message string) { } } -// Info level logging. Works like Sprintf. +// Info level logging. func (l *FileLogger) Info(message string) { if *config.EnableInfo { println("INFO | " + message) @@ -68,7 +68,7 @@ func (l *FileLogger) Info(message string) { } } -// Warning level logging. Works like Sprintf. +// Warning level logging. func (l *FileLogger) Warning(message string) { if *config.EnableWarn { println("WARN | " + message) @@ -76,7 +76,7 @@ func (l *FileLogger) Warning(message string) { } } -// Error level logging. Works like Sprintf. +// Error level logging. func (l *FileLogger) Error(message string) { if *config.EnableError { println("ERROR | " + message) @@ -84,7 +84,7 @@ func (l *FileLogger) Error(message string) { } } -// Fatal level logging. Works like Sprintf. +// Fatal level logging. func (l *FileLogger) Fatal(message string) { if *config.EnableFatal { println("FATAL | " + message) diff --git a/main.go b/main.go index 390bb50..fdc0ff6 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,19 @@ func main() { fileLogger = NewLogger(logFile) } + // Window State + windowsStateInt := *config.WindowStartState + windowState := options.Normal + + switch windowsStateInt { + case 1: + windowState = options.Minimised + case 2: + windowState = options.Maximised + case 3: + windowState = options.Fullscreen + } + // Create application with options err = wails.Run(&options.App{ Title: "Desktop Manager", @@ -62,7 +75,7 @@ func main() { OnDomReady: app.domReady, OnBeforeClose: app.beforeClose, OnShutdown: app.shutdown, - WindowStartState: options.Normal, + WindowStartState: windowState, Bind: []interface{}{ app, }, From c9d3a18dc376da7714ec04db2692a143fa305f04 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 16:37:15 +0300 Subject: [PATCH 037/221] only save config before close --- app.go | 10 +++++++++- config.go | 35 +---------------------------------- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/app.go b/app.go index 95cc0e1..6b17220 100644 --- a/app.go +++ b/app.go @@ -55,7 +55,15 @@ func (a *App) beforeClose(ctx context.Context) (prevent bool) { runtime.LogInfo(a.ctx, "Setting window state to normal") } - WriteConfig() + runtime.LogInfo(a.ctx, "Saving config") + err := WriteConfig() + + if err != nil { + runtime.LogError(a.ctx, err.Error()) + return false + } + + runtime.LogInfo(a.ctx, "Saving config complete") return false } diff --git a/config.go b/config.go index 04567b1..4af7c79 100644 --- a/config.go +++ b/config.go @@ -78,8 +78,6 @@ func merge_defaults() { v := reflect.ValueOf(&config).Elem() t := v.Type() - merged := false - for i := 0; i < t.NumField(); i++ { field := t.Field(i) fieldName := field.Name @@ -89,13 +87,8 @@ func merge_defaults() { // If config's field is nil, set it to the default value's field defaultValue := reflect.ValueOf(&defaultConfig).Elem().FieldByName(fieldName) fieldValue.Set(defaultValue) - merged = true } } - - if merged { - SetConfig(config) - } } func (app *App) GetConfigField(fieldName string) string { @@ -173,32 +166,6 @@ func (app *App) SetConfigField(fieldName string, value string) { } runtime.LogDebug(app.ctx, fmt.Sprintf("Config field %s set to %v", fieldName, fieldValue.Interface())) - - runtime.LogDebug(app.ctx, "Attempting to write to config file") - err := WriteConfig() - - if err != nil { - runtime.LogError(app.ctx, fmt.Sprintf("Failed to write to config file: %v", err)) - } - - runtime.LogDebug(app.ctx, "Config file written") -} - -// Set default config to configPath -func SetDefaultConfig() error { - config = GetDefaultConfig() - return WriteConfig() -} - -// GetConfig returns the current config -func GetConfig() Config { - return config -} - -// SetConfig sets the current config -func SetConfig(newConfig Config) error { - config = newConfig - return WriteConfig() } // Creates a default config at configPath if none exists @@ -206,7 +173,7 @@ func CreateConfigIfNotExist() error { configPath = get_config_path() if _, err := os.Stat(configPath); os.IsNotExist(err) { - return SetDefaultConfig() + config = GetDefaultConfig() } return nil } From de3ab03dfbbca2c7c2b1124d463e85dccb6156f0 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 16:44:21 +0300 Subject: [PATCH 038/221] fix custom titlebar --- frontend/src/App.tsx | 2 +- frontend/src/components/TitleBar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 85d2836..a9cf56a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,7 +3,7 @@ import ModeToggle from "@/components/ModeToggle"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import TitleBar from "./components/TitleBar"; import Settings from "./components/Settings"; -import { useTranslation } from 'react-i18next'; +import { useTranslation } from "react-i18next"; function App() { const { t } = useTranslation(); diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 1dfea99..6000ddd 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -11,7 +11,7 @@ export default function TitleBar() { GetConfigField("UseSystemTitleBar").then((value) => { setUseSystemTitleBar(value === "true"); }) - }), [] + }, []) return ( !useSystemTitleBar && ( From 77d814604130c27fb01c3793ee981b6b9bf0cc47 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 16:48:46 +0300 Subject: [PATCH 039/221] remove unnecessary config --- app.go | 6 +----- main.go | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app.go b/app.go index 6b17220..65fb5dd 100644 --- a/app.go +++ b/app.go @@ -41,11 +41,7 @@ func (a App) domReady(ctx context.Context) { // either by clicking the window close button or calling runtime.Quit. // Returning true will cause the application to continue, false will continue shutdown as normal. func (a *App) beforeClose(ctx context.Context) (prevent bool) { - if runtime.WindowIsFullscreen(a.ctx) { - runtime.LogInfo(a.ctx, "Setting window state to fullscreen") - var windowState = 3 - config.WindowStartState = &windowState - } else if runtime.WindowIsMaximised(a.ctx) { + if runtime.WindowIsMaximised(a.ctx) { var windowState = 2 config.WindowStartState = &windowState runtime.LogInfo(a.ctx, "Setting window state to maximized") diff --git a/main.go b/main.go index fdc0ff6..287e922 100644 --- a/main.go +++ b/main.go @@ -59,7 +59,6 @@ func main() { MinWidth: 1024, MinHeight: 768, DisableResize: false, - Fullscreen: false, Frameless: !*config.UseSystemTitleBar, StartHidden: false, HideWindowOnClose: false, From cc6ce4f5817f3be2a8514ebbd1393d3a6d60618a Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 17:00:07 +0300 Subject: [PATCH 040/221] add window scale to config --- config.go | 3 +++ main.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config.go b/config.go index 4af7c79..12bc6f5 100644 --- a/config.go +++ b/config.go @@ -23,6 +23,7 @@ type Config struct { EnableFatal *bool `json:"enableFatal"` // true, false Language *string `json:"language"` // en, tr WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen + WindowScale *int `json:"windowScale"` // % } func GetDefaultConfig() Config { @@ -37,6 +38,7 @@ func GetDefaultConfig() Config { defaultEnableFatal := true defaultLanguage := "en" defaultWindowStartState := 0 + defaultWindowScale := 100 return Config{ Theme: &defaultTheme, @@ -50,6 +52,7 @@ func GetDefaultConfig() Config { EnableFatal: &defaultEnableFatal, Language: &defaultLanguage, WindowStartState: &defaultWindowStartState, + WindowScale: &defaultWindowScale, } } diff --git a/main.go b/main.go index 287e922..1bb02cc 100644 --- a/main.go +++ b/main.go @@ -85,7 +85,7 @@ func main() { DisableWindowIcon: false, // DisableFramelessWindowDecorations: false, WebviewUserDataPath: path.Join(os.Getenv("APPDATA"), "desktop-manager"), - ZoomFactor: 1.0, + ZoomFactor: float64(*config.WindowScale) / 100, }, }) From 8ff1a26643339f2bb9717545f2fa05bc20e25be0 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 18:09:33 +0300 Subject: [PATCH 041/221] add scale, improve settings --- frontend/package.json | 1 + frontend/public/locales/en.json | 5 ++ frontend/public/locales/tr.json | 5 ++ .../components/Settings/AdvancedSettings.tsx | 69 +++++++++++-------- .../src/components/Settings/AppSettings.tsx | 55 +++++++++++++-- .../components/Settings/GeneralSettings.tsx | 15 +++- frontend/src/components/ui/settings-group.tsx | 31 ++++++++- frontend/src/components/ui/skeleton.tsx | 15 ++++ frontend/src/components/ui/slider.tsx | 26 +++++++ frontend/yarn.lock | 22 ++++++ 10 files changed, 204 insertions(+), 40 deletions(-) create mode 100644 frontend/src/components/ui/skeleton.tsx create mode 100644 frontend/src/components/ui/slider.tsx diff --git a/frontend/package.json b/frontend/package.json index 30c4bef..f48314d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-slider": "^1.2.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", diff --git a/frontend/public/locales/en.json b/frontend/public/locales/en.json index 2b3c926..0245041 100644 --- a/frontend/public/locales/en.json +++ b/frontend/public/locales/en.json @@ -23,6 +23,11 @@ "application": { "label": "Application", + "window_scale": { + "label": "Scale", + "description": "Select the window scale." + }, + "theme": { "label": "Theme", "description": "Select a color scheme for the interface." diff --git a/frontend/public/locales/tr.json b/frontend/public/locales/tr.json index 3f0610b..68244c0 100644 --- a/frontend/public/locales/tr.json +++ b/frontend/public/locales/tr.json @@ -23,6 +23,11 @@ "application": { "label": "Uygulama", + "window_scale": { + "label": "Yakınlaştırma", + "description": "Pencere ölçeğini seçin." + }, + "theme": { "label": "Tema", "description": "Arayüz için bir tema seçin." diff --git a/frontend/src/components/Settings/AdvancedSettings.tsx b/frontend/src/components/Settings/AdvancedSettings.tsx index 1f60185..d54b9a8 100644 --- a/frontend/src/components/Settings/AdvancedSettings.tsx +++ b/frontend/src/components/Settings/AdvancedSettings.tsx @@ -1,4 +1,4 @@ -import { GetConfigField, SetConfigField } from "wailsjs/go/main/App"; +import { GetConfigField, Log, SetConfigField } from "wailsjs/go/main/App"; import { SettingsGroup, SettingsItem, @@ -14,6 +14,8 @@ import { useTranslation } from "react-i18next"; export function AdvancedSettings() { const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(true); + const [enableLogging, setEnableLogging] = useState(false); const [enableTrace, setEnableTrace] = useState(false); const [enableDebug, setEnableDebug] = useState(false); @@ -23,38 +25,45 @@ export function AdvancedSettings() { const [enableFatal, setEnableFatal] = useState(false); useEffect(() => { - GetConfigField("EnableLogging").then((value) => { - setEnableLogging(value === "true"); - }); - - GetConfigField("EnableTrace").then((value) => { - setEnableTrace(value === "true"); - }); - - GetConfigField("EnableDebug").then((value) => { - setEnableDebug(value === "true"); - }); - - GetConfigField("EnableInfo").then((value) => { - setEnableInfo(value === "true"); - }); - - GetConfigField("EnableWarn").then((value) => { - setEnableWarn(value === "true"); - }); - - GetConfigField("EnableError").then((value) => { - setEnableError(value === "true"); - }); - - GetConfigField("EnableFatal").then((value) => { - setEnableFatal(value === "true"); - }); + Promise.all([ + GetConfigField("EnableLogging"), + GetConfigField("EnableTrace"), + GetConfigField("EnableDebug"), + GetConfigField("EnableInfo"), + GetConfigField("EnableWarn"), + GetConfigField("EnableError"), + GetConfigField("EnableFatal"), + ]) + .then( + ([ + enableLogging, + enableTrace, + enableDebug, + enableInfo, + enableWarn, + enableError, + enableFatal, + ]) => { + setEnableLogging(enableLogging === "true"); + setEnableTrace(enableTrace === "true"); + setEnableDebug(enableDebug === "true"); + setEnableInfo(enableInfo === "true"); + setEnableWarn(enableWarn === "true"); + setEnableError(enableError === "true"); + setEnableFatal(enableFatal === "true"); + + setIsLoading(false); + } + ) + .catch((error) => { + Log("Error while loading advanced settings: " + error, 4); + setIsLoading(false); + }); }, []); return ( - +
{t("settings.advanced.logging.label")} @@ -78,7 +87,7 @@ export function AdvancedSettings() { - +
{t("settings.advanced.log_levels.label")} diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index 6b27b3e..8991edb 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -9,21 +9,68 @@ import { import { Switch } from "@/components/ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { Slider } from "../ui/slider"; export function AppSettings() { const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(true); + const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); + const [useScale, setUseScale] = useState(-1); useEffect(() => { - GetConfigField("UseSystemTitleBar").then((value) => { - setUseSystemTitleBar(value === "true"); - }); + Promise.all([ + GetConfigField("UseSystemTitleBar"), + GetConfigField("WindowScale"), + ]) + .then(([useSystemTitleBarValue, windowScaleValue]) => { + setUseSystemTitleBar(useSystemTitleBarValue === "true"); + setUseScale(parseInt(windowScaleValue)); + setIsLoading(false); // Mark loading as complete + }) + .catch((error) => { + console.error("Error fetching configuration:", error); + setIsLoading(false); // Handle loading error + }); }, []); return ( - + +
+ + {t("settings.application.window_scale.label")} + + + {t("settings.application.window_scale.description") + + " (" + + t("settings.restart_the_app_for_changes_to_take_effect") + + ")"} + +
+ +
+ 50% + { + SetConfigField("WindowScale", String(value)).then(() => { + console.log(value[0]); + setUseScale(value[0]); + }); + }} + defaultValue={[useScale]} + min={50} + max={250} + step={10} + className={"w-64 cursor-pointer"} + /> + 250%
({useScale}%)
+
+
+
+ +
{t("settings.application.use_system_title_bar.label")} diff --git a/frontend/src/components/Settings/GeneralSettings.tsx b/frontend/src/components/Settings/GeneralSettings.tsx index 66e09f3..e5decd6 100644 --- a/frontend/src/components/Settings/GeneralSettings.tsx +++ b/frontend/src/components/Settings/GeneralSettings.tsx @@ -21,13 +21,22 @@ export function GeneralSettings() { i18n.changeLanguage(lng); }; + const [isLoading, setIsLoading] = useState(true); + const [language, setLanguage] = useState("en"); const [langIsSet, setLangIsSet] = useState(false); useEffect(() => { - GetConfigField("Language").then((value) => { + Promise.all([ + GetConfigField("Language") + ]).then(([value]) => { setLanguage(value); setLangIsSet(true); + + setIsLoading(false); // Mark loading as complete + }).catch((error) => { + console.error("Error fetching configuration:", error); + setIsLoading(false); // Handle loading error }); }, []); @@ -42,7 +51,7 @@ export function GeneralSettings() { return ( - +
{t("settings.general.language.label")} @@ -67,7 +76,7 @@ export function GeneralSettings() { - +
{t("settings.application.theme.label")} diff --git a/frontend/src/components/ui/settings-group.tsx b/frontend/src/components/ui/settings-group.tsx index 1d1d7ab..78daaac 100644 --- a/frontend/src/components/ui/settings-group.tsx +++ b/frontend/src/components/ui/settings-group.tsx @@ -1,4 +1,5 @@ import React, { ReactNode } from "react"; +import { Skeleton } from "@/components/ui/skeleton" interface SettingsComponentProps extends React.HTMLAttributes { children: ReactNode; @@ -8,9 +9,20 @@ export const SettingsGroup: React.FC = ({ children, clas
{children}
); -export const SettingsItem: React.FC = ({ children, className, ...rest }) => ( -
{children}
-); +interface SettingsItemProps extends React.HTMLAttributes { + loading?: boolean; + vertical?: boolean; + children: ReactNode; +} + +export const SettingsItem: React.FC = ({ children, className, loading, vertical, ...rest }) => { + if (loading) { + return + } else { + return
{children}
+ } +} export const SettingLabel: React.FC = ({ children, className, ...rest }) => (
{children}
@@ -23,3 +35,16 @@ export const SettingDescription: React.FC = ({ children, export const SettingContent: React.FC = ({ children, className, ...rest }) => (
{children}
); + +interface SettingsGroupSkeletonProps extends React.HTMLAttributes { +} + +const SettingsItemSkeleton: React.FC = ({ className, ...rest }) => ( +
+
+ + +
+ +
+); \ No newline at end of file diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..01b8b6d --- /dev/null +++ b/frontend/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/frontend/src/components/ui/slider.tsx b/frontend/src/components/ui/slider.tsx new file mode 100644 index 0000000..e161dae --- /dev/null +++ b/frontend/src/components/ui/slider.tsx @@ -0,0 +1,26 @@ +import * as React from "react" +import * as SliderPrimitive from "@radix-ui/react-slider" + +import { cn } from "@/lib/utils" + +const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = SliderPrimitive.Root.displayName + +export { Slider } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e4f6f79..44a93b2 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -492,6 +492,11 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@radix-ui/number@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46" + integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ== + "@radix-ui/primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" @@ -760,6 +765,23 @@ "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0" +"@radix-ui/react-slider@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-1.2.0.tgz#7a4c817d24386b420631a3fdc75563706d743472" + integrity sha512-dAHCDA4/ySXROEPaRtaMV5WHL8+JB/DbtyTbJjYkY0RXmKMO2Ln8DFZhywG5/mVQ4WqHDBc8smc14yPXPqZHYA== + dependencies: + "@radix-ui/number" "1.1.0" + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-use-previous" "1.1.0" + "@radix-ui/react-use-size" "1.1.0" + "@radix-ui/react-slot@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" From 38744af36fe8512744a76153d3f245c1013c4a80 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 18:13:48 +0300 Subject: [PATCH 042/221] update combobox --- .../components/Settings/GeneralSettings.tsx | 34 ++++++++----------- frontend/src/components/ui/combobox.tsx | 17 ++++++---- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/Settings/GeneralSettings.tsx b/frontend/src/components/Settings/GeneralSettings.tsx index e5decd6..75033c1 100644 --- a/frontend/src/components/Settings/GeneralSettings.tsx +++ b/frontend/src/components/Settings/GeneralSettings.tsx @@ -24,29 +24,20 @@ export function GeneralSettings() { const [isLoading, setIsLoading] = useState(true); const [language, setLanguage] = useState("en"); - const [langIsSet, setLangIsSet] = useState(false); useEffect(() => { - Promise.all([ - GetConfigField("Language") - ]).then(([value]) => { - setLanguage(value); - setLangIsSet(true); + Promise.all([GetConfigField("Language")]) + .then(([value]) => { + setLanguage(value); - setIsLoading(false); // Mark loading as complete - }).catch((error) => { - console.error("Error fetching configuration:", error); - setIsLoading(false); // Handle loading error - }); + setIsLoading(false); // Mark loading as complete + }) + .catch((error) => { + console.error("Error fetching configuration:", error); + setIsLoading(false); // Handle loading error + }); }, []); - useEffect(() => { - if (langIsSet) { - SetConfigField("Language", language); - changeLanguage(language); - } - }, [language]); - const { theme, setTheme } = useTheme(); return ( @@ -60,8 +51,7 @@ export function GeneralSettings() {
({ value: language.code, @@ -72,6 +62,10 @@ export function GeneralSettings() { nothingFoundMessage={t( "settings.general.language.no_languages_found" )} + onChange={(value) => { + SetConfigField("Language", value); + changeLanguage(value); + }} /> diff --git a/frontend/src/components/ui/combobox.tsx b/frontend/src/components/ui/combobox.tsx index 3ecc80c..bb65f20 100644 --- a/frontend/src/components/ui/combobox.tsx +++ b/frontend/src/components/ui/combobox.tsx @@ -23,14 +23,19 @@ interface ComboboxProps { searchPlaceholder?: string; nothingFoundMessage?: string; mandatory?: boolean; - value: any; - setValue: (value: any) => void; + initialValue?: any; + onChange: (value: any) => void; } export function Combobox(props: ComboboxProps) { + const [value, setValue] = React.useState(props.initialValue) const [open, setOpen] = React.useState(false) + React.useEffect(() => { + props.onChange(value) + }, [value]) + return ( @@ -40,8 +45,8 @@ export function Combobox(props: ComboboxProps) { aria-expanded={open} className="justify-between w-[200px]" > - {props.value - ? props.elements.find((element) => element.value === props.value)?.label + {value + ? props.elements.find((element) => element.value === value)?.label : (props.placeholder ? props.placeholder : "Select...")} @@ -57,7 +62,7 @@ export function Combobox(props: ComboboxProps) { key={element.value} value={element.value} onSelect={(currentValue) => { - props.setValue(!props.mandatory && currentValue === props.value ? "" : currentValue) + setValue(!props.mandatory && currentValue === value ? "" : currentValue) setOpen(false) }} > @@ -65,7 +70,7 @@ export function Combobox(props: ComboboxProps) { From dac3e6ac067debdc341c355284081efdf7c6b642 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 18:18:09 +0300 Subject: [PATCH 043/221] update theme locale location --- frontend/public/locales/en.json | 9 ++++----- frontend/public/locales/tr.json | 9 ++++----- frontend/src/components/Settings/GeneralSettings.tsx | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/frontend/public/locales/en.json b/frontend/public/locales/en.json index 0245041..f018623 100644 --- a/frontend/public/locales/en.json +++ b/frontend/public/locales/en.json @@ -17,6 +17,10 @@ "select_language": "Select language...", "search_language": "Search language...", "no_languages_found": "No languages found." + }, + "theme": { + "label": "Theme", + "description": "Select a color scheme for the interface." } }, @@ -27,11 +31,6 @@ "label": "Scale", "description": "Select the window scale." }, - - "theme": { - "label": "Theme", - "description": "Select a color scheme for the interface." - }, "use_system_title_bar": { "label": "Use System Title Bar", "description": "Switch to the default system title bar instead of the custom one." diff --git a/frontend/public/locales/tr.json b/frontend/public/locales/tr.json index 68244c0..b060b58 100644 --- a/frontend/public/locales/tr.json +++ b/frontend/public/locales/tr.json @@ -17,6 +17,10 @@ "select_language": "Dil seçin...", "search_language": "Dil ara...", "no_languages_found": "Dil bulunamadı." + }, + "theme": { + "label": "Tema", + "description": "Arayüz için bir tema seçin." } }, @@ -27,11 +31,6 @@ "label": "Yakınlaştırma", "description": "Pencere ölçeğini seçin." }, - - "theme": { - "label": "Tema", - "description": "Arayüz için bir tema seçin." - }, "use_system_title_bar": { "label": "Sistem Başlık Çubuğunu Kullan", "description": "Özel başlık çubuğu yerine varsayılan sistem başlık çubuğuna geçin." diff --git a/frontend/src/components/Settings/GeneralSettings.tsx b/frontend/src/components/Settings/GeneralSettings.tsx index 75033c1..e0618a9 100644 --- a/frontend/src/components/Settings/GeneralSettings.tsx +++ b/frontend/src/components/Settings/GeneralSettings.tsx @@ -72,9 +72,9 @@ export function GeneralSettings() {
- {t("settings.application.theme.label")} + {t("settings.general.theme.label")} - {t("settings.application.theme.description")} + {t("settings.general.theme.description")}
From fe20f89049b5b418f5571848e530f57b9694426f Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 18:44:34 +0300 Subject: [PATCH 044/221] update scale setting --- frontend/src/App.tsx | 13 +++++++ frontend/src/components/ModeToggle.tsx | 4 +- .../src/components/Settings/AppSettings.tsx | 16 ++++---- frontend/src/components/ui/my-slider.tsx | 38 +++++++++++++++++++ main.go | 2 +- 5 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/ui/my-slider.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a9cf56a..0ec808a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,26 @@ +import { GetConfigField } from "wailsjs/go/main/App"; import { ThemeProvider } from "./contexts/theme-provider"; import ModeToggle from "@/components/ModeToggle"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import TitleBar from "./components/TitleBar"; import Settings from "./components/Settings"; import { useTranslation } from "react-i18next"; +import { useEffect } from "react"; function App() { const { t } = useTranslation(); + useEffect(() => { + Promise.all([GetConfigField("WindowScale")]) + .then(([windowScaleValue]) => { + document.documentElement.style.fontSize = + Number(windowScaleValue) * (16 / 100) + "px"; + }) + .catch((error) => { + console.error("Error fetching configuration:", error); + }); + }, []); + return (
diff --git a/frontend/src/components/ModeToggle.tsx b/frontend/src/components/ModeToggle.tsx index b15a78d..d452345 100644 --- a/frontend/src/components/ModeToggle.tsx +++ b/frontend/src/components/ModeToggle.tsx @@ -17,8 +17,8 @@ export default function ModeToggle() { }} className="bg-transparent hover:bg-transparent duration-0" > - - + + Toggle theme ); diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index 8991edb..8606488 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -9,7 +9,7 @@ import { import { Switch } from "@/components/ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Slider } from "../ui/slider"; +import { Slider } from "../ui/my-slider"; export function AppSettings() { const { t } = useTranslation(); @@ -43,10 +43,7 @@ export function AppSettings() { {t("settings.application.window_scale.label")} - {t("settings.application.window_scale.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} + {t("settings.application.window_scale.description")}
@@ -55,17 +52,20 @@ export function AppSettings() { { SetConfigField("WindowScale", String(value)).then(() => { - console.log(value[0]); setUseScale(value[0]); }); }} + onRelease={() => { + document.documentElement.style.fontSize = + useScale * (16 / 100) + "px"; + }} defaultValue={[useScale]} min={50} - max={250} + max={150} step={10} className={"w-64 cursor-pointer"} /> - 250%
({useScale}%)
+ 150%
({useScale}%)
diff --git a/frontend/src/components/ui/my-slider.tsx b/frontend/src/components/ui/my-slider.tsx new file mode 100644 index 0000000..d5367f2 --- /dev/null +++ b/frontend/src/components/ui/my-slider.tsx @@ -0,0 +1,38 @@ +import * as React from "react"; +import * as SliderPrimitive from "@radix-ui/react-slider"; +import { cn } from "@/lib/utils"; + +interface SliderProps extends React.ComponentProps { + onRelease?: (value: number) => void; +} + +export const Slider: React.FC = ({ + className, + onRelease, + ...props +}) => { + const handleRelease = (event: React.PointerEvent) => { + const sliderValue = parseFloat(event.currentTarget.getAttribute("aria-valuenow") || "0"); + if (onRelease) { + onRelease(sliderValue); + } + }; + + return ( + + + + + + + ); +}; \ No newline at end of file diff --git a/main.go b/main.go index 1bb02cc..287e922 100644 --- a/main.go +++ b/main.go @@ -85,7 +85,7 @@ func main() { DisableWindowIcon: false, // DisableFramelessWindowDecorations: false, WebviewUserDataPath: path.Join(os.Getenv("APPDATA"), "desktop-manager"), - ZoomFactor: float64(*config.WindowScale) / 100, + ZoomFactor: 1.0, }, }) From 8251e1c3956487cdfb33cb6e3a9bb87d3aa3b946 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 21:20:13 +0300 Subject: [PATCH 045/221] add acrylic effect --- frontend/src/components/TitleBar.tsx | 60 ++++++++------ frontend/src/components/ui/my-slider.tsx | 2 +- frontend/src/components/ui/settings-group.tsx | 2 +- frontend/src/index.css | 78 ++++++++++--------- frontend/tailwind.config.js | 38 ++++----- main.go | 5 +- 6 files changed, 102 insertions(+), 83 deletions(-) diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 6000ddd..96adae7 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -10,30 +10,46 @@ export default function TitleBar() { useEffect(() => { GetConfigField("UseSystemTitleBar").then((value) => { setUseSystemTitleBar(value === "true"); - }) - }, []) - + }); + }, []); + return ( !useSystemTitleBar && ( -
Maximize()}> -

- - {document.title} -

-
- - - -
-
+
Maximize()} + > +

+ + {document.title} +

+
+ + + +
+
) ); } - - diff --git a/frontend/src/components/ui/my-slider.tsx b/frontend/src/components/ui/my-slider.tsx index d5367f2..135b7df 100644 --- a/frontend/src/components/ui/my-slider.tsx +++ b/frontend/src/components/ui/my-slider.tsx @@ -31,7 +31,7 @@ export const Slider: React.FC = ({ ); diff --git a/frontend/src/components/ui/settings-group.tsx b/frontend/src/components/ui/settings-group.tsx index 78daaac..2381d37 100644 --- a/frontend/src/components/ui/settings-group.tsx +++ b/frontend/src/components/ui/settings-group.tsx @@ -25,7 +25,7 @@ export const SettingsItem: React.FC = ({ children, className, } export const SettingLabel: React.FC = ({ children, className, ...rest }) => ( -
{children}
+
{children}
); export const SettingDescription: React.FC = ({ children, className, ...rest }) => ( diff --git a/frontend/src/index.css b/frontend/src/index.css index 3c80d15..b1607a9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -4,67 +4,69 @@ @layer base { :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; + --background: hsla(0, 0%, 100%, var(--opacity)); + --foreground: hsl(222.2, 84%, 4.9%); - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + --muted: hsla(210, 40%, 96.1%, var(--opacity)); + --muted-foreground: hsl(215.4, 16.3%, 46.9%); - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; + --popover: hsla(0, 0%, 100%, var(--opacity)); + --popover-foreground: hsl(222.2, 84%, 4.9%); - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --card: hsla(0, 0%, 100%, var(--opacity)); + --card-foreground: hsl(222.2, 84%, 4.9%); - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; + --border: hsla(214.3, 31.8%, 91.4%, var(--opacity)); + --input: hsla(214.3, 31.8%, 91.4%, var(--opacity)); - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; + --primary: hsla(222.2, 47.4%, 11.2%, var(--opacity)); + --primary-foreground: hsl(210, 40%, 98%); - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; + --secondary: hsla(210, 40%, 96.1%, var(--opacity)); + --secondary-foreground: hsl(222.2, 47.4%, 11.2%); - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --accent: hsla(210, 40%, 96.1%, var(--opacity)); + --accent-foreground: hsl(222.2, 47.4%, 11.2%); - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; + --destructive: hsla(0, 84.2%, 60.2%, var(--opacity)); + --destructive-foreground: hsl(210, 40%, 98%); - --ring: 215 20.2% 65.1%; + --ring: hsla(215, 20.2%, 65.1%, var(--opacity)); --radius: 0.5rem; + + --opacity: 0.85; } .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; + --background: hsla(222.2, 84%, 4.9%, var(--opacity)); + --foreground: hsl(210, 40%, 98%); - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; + --muted: hsla(217.2, 32.6%, 17.5%, var(--opacity)); + --muted-foreground: hsl(215, 20.2%, 65.1%); - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; + --popover: hsla(222.2, 84%, 4.9%, var(--opacity)); + --popover-foreground: hsl(210, 40%, 98%); - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; + --card: hsla(222.2, 84%, 4.9%, var(--opacity)); + --card-foreground: hsl(210, 40%, 98%); - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; + --border: hsla(217.2, 32.6%, 17.5%, var(--opacity)); + --input: hsla(217.2, 32.6%, 17.5%, var(--opacity)); - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; + --primary: hsla(210, 40%, 98%, var(--opacity)); + --primary-foreground: hsl(222.2, 47.4%, 11.2%); - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; + --secondary: hsla(217.2, 32.6%, 17.5%, var(--opacity)); + --secondary-foreground: hsl(210, 40%, 98%); - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; + --accent: hsla(217.2, 32.6%, 17.5%, var(--opacity)); + --accent-foreground: hsl(210, 40%, 98%); - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 85.7% 97.3%; + --destructive: hsla(0, 62.8%, 30.6%, var(--opacity)); + --destructive-foreground: hsl(0, 85.7%, 97.3%); - --ring: 217.2 32.6% 17.5%; + --ring: hsla(217.2, 32.6%, 17.5%, var(--opacity)); } } diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index cc7fbde..2492f5e 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -17,38 +17,38 @@ module.exports = { }, extend: { colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", + border: "var(--border)", + input: "var(--input)", + ring: "var(--ring)", + background: "var(--background)", + foreground: "var(--foreground)", primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", + DEFAULT: "var(--primary)", + foreground: "var(--primary-foreground)", }, secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", + DEFAULT: "var(--secondary)", + foreground: "var(--secondary-foreground)", }, destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", + DEFAULT: "var(--destructive)", + foreground: "var(--destructive-foreground)", }, muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", + DEFAULT: "var(--muted)", + foreground: "var(--muted-foreground)", }, accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", + DEFAULT: "var(--accent)", + foreground: "var(--accent-foreground)", }, popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", + DEFAULT: "var(--popover)", + foreground: "var(--popover-foreground)", }, card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", + DEFAULT: "var(--card)", + foreground: "var(--card-foreground)", }, }, borderRadius: { diff --git a/main.go b/main.go index 287e922..67a757f 100644 --- a/main.go +++ b/main.go @@ -80,9 +80,10 @@ func main() { }, // Windows platform specific options Windows: &windows.Options{ - WebviewIsTransparent: false, - WindowIsTranslucent: false, + WebviewIsTransparent: true, + WindowIsTranslucent: true, DisableWindowIcon: false, + BackdropType: windows.Acrylic, // DisableFramelessWindowDecorations: false, WebviewUserDataPath: path.Join(os.Getenv("APPDATA"), "desktop-manager"), ZoomFactor: 1.0, From 4804a28477e7c3f9702eff86a7c38c5afbb5019a Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 22:05:18 +0300 Subject: [PATCH 046/221] add opacity setting --- config.go | 3 + frontend/public/locales/en.json | 9 +++ frontend/public/locales/tr.json | 9 +++ .../src/components/Settings/AppSettings.tsx | 63 +++++++++++++++---- frontend/src/components/ui/my-slider.tsx | 14 +---- 5 files changed, 75 insertions(+), 23 deletions(-) diff --git a/config.go b/config.go index 12bc6f5..1e61b8d 100644 --- a/config.go +++ b/config.go @@ -24,6 +24,7 @@ type Config struct { Language *string `json:"language"` // en, tr WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen WindowScale *int `json:"windowScale"` // % + Opacity *int `json:"opacity"` // % } func GetDefaultConfig() Config { @@ -39,6 +40,7 @@ func GetDefaultConfig() Config { defaultLanguage := "en" defaultWindowStartState := 0 defaultWindowScale := 100 + opacity := 90 return Config{ Theme: &defaultTheme, @@ -53,6 +55,7 @@ func GetDefaultConfig() Config { Language: &defaultLanguage, WindowStartState: &defaultWindowStartState, WindowScale: &defaultWindowScale, + Opacity: &opacity, } } diff --git a/frontend/public/locales/en.json b/frontend/public/locales/en.json index f018623..74ba0ac 100644 --- a/frontend/public/locales/en.json +++ b/frontend/public/locales/en.json @@ -27,6 +27,15 @@ "application": { "label": "Application", + "window_effect": { + "label": "Window Effect", + "description": "Choose the effect for the window." + }, + "window_opacity": { + "label": "Window Opacity", + "description": "Adjust the window opacity. (This setting will be ignored if the window effect is set to 'None'.)" + }, + "window_scale": { "label": "Scale", "description": "Select the window scale." diff --git a/frontend/public/locales/tr.json b/frontend/public/locales/tr.json index b060b58..6540939 100644 --- a/frontend/public/locales/tr.json +++ b/frontend/public/locales/tr.json @@ -27,6 +27,15 @@ "application": { "label": "Uygulama", + "window_effect": { + "label": "Pencere Efekti", + "description": "Pencere efektini seçin." + }, + "window_opacity": { + "label": "Pencere Opaklığı", + "description": "Pencere opaklığını ayarlayın. (Efekt 'Yok' olarak ayarlandığında yok sayılacaktır.)" + }, + "window_scale": { "label": "Yakınlaştırma", "description": "Pencere ölçeğini seçin." diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index 8606488..71b81d4 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -18,17 +18,22 @@ export function AppSettings() { const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); const [useScale, setUseScale] = useState(-1); + const [useOpacity, setUseOpacity] = useState(-1); useEffect(() => { Promise.all([ GetConfigField("UseSystemTitleBar"), GetConfigField("WindowScale"), + GetConfigField("Opacity"), ]) - .then(([useSystemTitleBarValue, windowScaleValue]) => { - setUseSystemTitleBar(useSystemTitleBarValue === "true"); - setUseScale(parseInt(windowScaleValue)); - setIsLoading(false); // Mark loading as complete - }) + .then( + ([useSystemTitleBarValue, windowScaleValue, windowOpacityValue]) => { + setUseSystemTitleBar(useSystemTitleBarValue === "true"); + setUseScale(parseInt(windowScaleValue)); + setUseOpacity(parseInt(windowOpacityValue)); + setIsLoading(false); // Mark loading as complete + } + ) .catch((error) => { console.error("Error fetching configuration:", error); setIsLoading(false); // Handle loading error @@ -37,6 +42,41 @@ export function AppSettings() { return ( + +
+ + {t("settings.application.window_opacity.label")} + + + {t("settings.application.window_opacity.description")} + +
+ +
+
50%
+ { + setUseOpacity(value[0]); + document.documentElement.style.setProperty( + "--opacity", + String(useOpacity / 100) + ); + }} + onPointerUp={() => { + SetConfigField("Opacity", String(useOpacity)); + }} + defaultValue={[useOpacity]} + min={50} + max={100} + step={1} + className={"w-64 cursor-pointer"} + /> +
100%
+
({useOpacity}%)
+
+
+
+
@@ -48,16 +88,16 @@ export function AppSettings() {
- 50% +
50%
{ - SetConfigField("WindowScale", String(value)).then(() => { - setUseScale(value[0]); - }); + setUseScale(value[0]); }} - onRelease={() => { + onPointerUp={() => { document.documentElement.style.fontSize = useScale * (16 / 100) + "px"; + + SetConfigField("WindowScale", String(useScale)); }} defaultValue={[useScale]} min={50} @@ -65,7 +105,8 @@ export function AppSettings() { step={10} className={"w-64 cursor-pointer"} /> - 150%
({useScale}%)
+
150%
+
({useScale}%)
diff --git a/frontend/src/components/ui/my-slider.tsx b/frontend/src/components/ui/my-slider.tsx index 135b7df..59f6b03 100644 --- a/frontend/src/components/ui/my-slider.tsx +++ b/frontend/src/components/ui/my-slider.tsx @@ -2,22 +2,13 @@ import * as React from "react"; import * as SliderPrimitive from "@radix-ui/react-slider"; import { cn } from "@/lib/utils"; -interface SliderProps extends React.ComponentProps { - onRelease?: (value: number) => void; -} +interface SliderProps extends React.ComponentProps {} + export const Slider: React.FC = ({ className, - onRelease, ...props }) => { - const handleRelease = (event: React.PointerEvent) => { - const sliderValue = parseFloat(event.currentTarget.getAttribute("aria-valuenow") || "0"); - if (onRelease) { - onRelease(sliderValue); - } - }; - return ( = ({ className )} {...props} - onPointerUp={handleRelease} > From 988e0251ff23f608433fde073abc4959909d8220 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 23:29:43 +0300 Subject: [PATCH 047/221] add window effect settings --- config.go | 7 +- frontend/public/locales/en.json | 12 ++- frontend/public/locales/tr.json | 8 +- frontend/src/App.tsx | 12 ++- .../src/components/Settings/AppSettings.tsx | 101 +++++++++++++++++- frontend/src/components/ui/my-slider.tsx | 2 +- frontend/src/components/ui/settings-group.tsx | 6 +- main.go | 23 +++- 8 files changed, 155 insertions(+), 16 deletions(-) diff --git a/config.go b/config.go index 1e61b8d..5a4b2cc 100644 --- a/config.go +++ b/config.go @@ -25,6 +25,7 @@ type Config struct { WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen WindowScale *int `json:"windowScale"` // % Opacity *int `json:"opacity"` // % + WindowEffect *int `json:"windowEffect"` // 0 = Auto, 1 = None, 2 = Mica, 3 = Acrylic, 4 = Tabbed } func GetDefaultConfig() Config { @@ -40,7 +41,8 @@ func GetDefaultConfig() Config { defaultLanguage := "en" defaultWindowStartState := 0 defaultWindowScale := 100 - opacity := 90 + defaultOpacity := 90 + defaultWindowEffect := 0 return Config{ Theme: &defaultTheme, @@ -55,7 +57,8 @@ func GetDefaultConfig() Config { Language: &defaultLanguage, WindowStartState: &defaultWindowStartState, WindowScale: &defaultWindowScale, - Opacity: &opacity, + Opacity: &defaultOpacity, + WindowEffect: &defaultWindowEffect, } } diff --git a/frontend/public/locales/en.json b/frontend/public/locales/en.json index 74ba0ac..00ae317 100644 --- a/frontend/public/locales/en.json +++ b/frontend/public/locales/en.json @@ -29,11 +29,17 @@ "window_effect": { "label": "Window Effect", - "description": "Choose the effect for the window." + "description": "Choose the effect for the window.", + + "auto": "Auto", + "none": "None", + "acrylic": "Acrylic", + "mica": "Mica", + "tabbed": "Tabbed" }, "window_opacity": { - "label": "Window Opacity", - "description": "Adjust the window opacity. (This setting will be ignored if the window effect is set to 'None'.)" + "label": "Background Opacity", + "description": "Sets the opacity of the window. (This setting will be ignored if the window effect is set to 'None'.)" }, "window_scale": { diff --git a/frontend/public/locales/tr.json b/frontend/public/locales/tr.json index 6540939..ede16d1 100644 --- a/frontend/public/locales/tr.json +++ b/frontend/public/locales/tr.json @@ -29,7 +29,13 @@ "window_effect": { "label": "Pencere Efekti", - "description": "Pencere efektini seçin." + "description": "Pencere efektini seçin.", + + "auto": "Otomatik", + "none": "Yok", + "acrylic": "Akrilik", + "mica": "Mika", + "tabbed": "Sekmeli" }, "window_opacity": { "label": "Pencere Opaklığı", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0ec808a..0bdcc7c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,10 +11,18 @@ function App() { const { t } = useTranslation(); useEffect(() => { - Promise.all([GetConfigField("WindowScale")]) - .then(([windowScaleValue]) => { + Promise.all([ + GetConfigField("WindowScale"), + GetConfigField("Opacity"), + GetConfigField("WindowEffect"), + ]) + .then(([windowScaleValue, opacityValue, windowEffectValue]) => { document.documentElement.style.fontSize = Number(windowScaleValue) * (16 / 100) + "px"; + document.documentElement.style.setProperty( + "--opacity", + String(Number(windowEffectValue === "1" ? "100" : opacityValue) / 100) + ); }) .catch((error) => { console.error("Error fetching configuration:", error); diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index 71b81d4..51032e0 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -10,6 +10,7 @@ import { Switch } from "@/components/ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Slider } from "../ui/my-slider"; +import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; export function AppSettings() { const { t } = useTranslation(); @@ -19,18 +20,26 @@ export function AppSettings() { const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); const [useScale, setUseScale] = useState(-1); const [useOpacity, setUseOpacity] = useState(-1); + const [useWindowEffect, setUseWindowEffect] = useState(""); useEffect(() => { Promise.all([ GetConfigField("UseSystemTitleBar"), GetConfigField("WindowScale"), GetConfigField("Opacity"), + GetConfigField("WindowEffect"), ]) .then( - ([useSystemTitleBarValue, windowScaleValue, windowOpacityValue]) => { + ([ + useSystemTitleBarValue, + windowScaleValue, + windowOpacityValue, + windowEffectValue, + ]) => { setUseSystemTitleBar(useSystemTitleBarValue === "true"); setUseScale(parseInt(windowScaleValue)); setUseOpacity(parseInt(windowOpacityValue)); + setUseWindowEffect(windowEffectValue); setIsLoading(false); // Mark loading as complete } ) @@ -40,9 +49,97 @@ export function AppSettings() { }); }, []); + useEffect(() => { + document.documentElement.style.setProperty( + "--opacity", + String(Number(useWindowEffect === "1" ? "100" : useOpacity) / 100) + ); + }, [useWindowEffect]); + return ( - + +
+ + {t("settings.application.window_effect.label")} + + + {t("settings.application.window_effect.description") + + " (" + + t("settings.restart_the_app_for_changes_to_take_effect") + + ")"} + +
+ + + { + SetConfigField("WindowEffect", "1").then(() => { + setUseWindowEffect("1"); + }); + }} + > + {t("settings.application.window_effect.none")} + + + { + SetConfigField("WindowEffect", "0").then(() => { + setUseWindowEffect("0"); + }); + }} + > + {t("settings.application.window_effect.auto")} + + + { + SetConfigField("WindowEffect", "2").then(() => { + setUseWindowEffect("2"); + }); + }} + > + {t("settings.application.window_effect.mica")} + + + { + SetConfigField("WindowEffect", "3").then(() => { + setUseWindowEffect("3"); + }); + }} + > + {t("settings.application.window_effect.acrylic")} + + + { + SetConfigField("WindowEffect", "4").then(() => { + setUseWindowEffect("4"); + }); + }} + > + {t("settings.application.window_effect.tabbed")} + + + +
+ +
{t("settings.application.window_opacity.label")} diff --git a/frontend/src/components/ui/my-slider.tsx b/frontend/src/components/ui/my-slider.tsx index 59f6b03..12e2e21 100644 --- a/frontend/src/components/ui/my-slider.tsx +++ b/frontend/src/components/ui/my-slider.tsx @@ -17,7 +17,7 @@ export const Slider: React.FC = ({ )} {...props} > - + = ({ children, clas interface SettingsItemProps extends React.HTMLAttributes { loading?: boolean; vertical?: boolean; + disabled?: boolean; children: ReactNode; } -export const SettingsItem: React.FC = ({ children, className, loading, vertical, ...rest }) => { +export const SettingsItem: React.FC = ({ children, className, disabled, loading, vertical, ...rest }) => { if (loading) { return } else { - return
{children}
} } diff --git a/main.go b/main.go index 67a757f..9aa7d5f 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,23 @@ func main() { windowState = options.Fullscreen } + // Window Effect + windowEffectInt := *config.WindowEffect + windowEffect := windows.Auto + windowTransparent := true + + switch windowEffectInt { + case 1: + windowEffect = windows.None + windowTransparent = false + case 2: + windowEffect = windows.Mica + case 3: + windowEffect = windows.Acrylic + case 4: + windowEffect = windows.Tabbed + } + // Create application with options err = wails.Run(&options.App{ Title: "Desktop Manager", @@ -80,10 +97,10 @@ func main() { }, // Windows platform specific options Windows: &windows.Options{ - WebviewIsTransparent: true, - WindowIsTranslucent: true, + WebviewIsTransparent: windowTransparent, + WindowIsTranslucent: windowTransparent, DisableWindowIcon: false, - BackdropType: windows.Acrylic, + BackdropType: windowEffect, // DisableFramelessWindowDecorations: false, WebviewUserDataPath: path.Join(os.Getenv("APPDATA"), "desktop-manager"), ZoomFactor: 1.0, From ab9167ea9927cc1fcfc80cc021278802ddfd3e15 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 25 Jun 2024 23:39:47 +0300 Subject: [PATCH 048/221] set system title bar theme --- frontend/src/contexts/theme-provider.tsx | 3 ++- main.go | 1 + theme.go | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 theme.go diff --git a/frontend/src/contexts/theme-provider.tsx b/frontend/src/contexts/theme-provider.tsx index e3c0b51..39fca34 100644 --- a/frontend/src/contexts/theme-provider.tsx +++ b/frontend/src/contexts/theme-provider.tsx @@ -1,4 +1,4 @@ -import { GetConfigField, Log, SetConfigField } from "wailsjs/go/main/App"; +import { GetConfigField, Log, SetConfigField, SetTheme } from "wailsjs/go/main/App"; import { createContext, useContext, useEffect, useState } from "react"; type Theme = "dark" | "light" | "system"; @@ -66,6 +66,7 @@ export function ThemeProvider({ try { await SetConfigField("Theme", theme); setThemeState(theme); + SetTheme(theme); } catch (error) { Log("Failed to set theme", 4); } diff --git a/main.go b/main.go index 9aa7d5f..41a00ce 100644 --- a/main.go +++ b/main.go @@ -104,6 +104,7 @@ func main() { // DisableFramelessWindowDecorations: false, WebviewUserDataPath: path.Join(os.Getenv("APPDATA"), "desktop-manager"), ZoomFactor: 1.0, + DisablePinchZoom: true, }, }) diff --git a/theme.go b/theme.go new file mode 100644 index 0000000..d5422e7 --- /dev/null +++ b/theme.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + + "github.com/wailsapp/wails/v2/pkg/runtime" +) + +func (app *App) SetTheme(theme string) { + runtime.LogDebug(app.ctx, fmt.Sprintf("Setting windows theme to %s", theme)) + switch theme { + case "light": + runtime.WindowSetLightTheme(app.ctx) + case "dark": + runtime.WindowSetDarkTheme(app.ctx) + case "system": + runtime.WindowSetSystemDefaultTheme(app.ctx) + } +} From 6f161e2ba4c0c190e3ec8b146038ee314caee059 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 00:18:49 +0300 Subject: [PATCH 049/221] disable overscroll --- frontend/src/index.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/index.css b/frontend/src/index.css index b1607a9..4715861 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -71,6 +71,10 @@ } @layer base { + html { + height: 100%; + overflow: hidden; + } * { @apply border-border; } From bd4dae13140ebe6a86572ee494d586a3cbdd940e Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 00:37:19 +0300 Subject: [PATCH 050/221] add single instance lock --- app.go | 14 ++++++++++++++ frontend/src/components/Settings/AppSettings.tsx | 10 ++++++---- main.go | 4 ++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 65fb5dd..dd78adc 100644 --- a/app.go +++ b/app.go @@ -2,7 +2,9 @@ package main import ( "context" + "strings" + "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/runtime" ) @@ -69,6 +71,18 @@ func (a *App) shutdown(ctx context.Context) { // Perform your teardown here } +// onSecondInstanceLaunch is called when the application is launched from a second instance +func (a *App) onSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) { + secondInstanceArgs := secondInstanceData.Args + + runtime.LogDebug(a.ctx, "User opened a second instance "+strings.Join(secondInstanceArgs, ",")) + runtime.LogDebug(a.ctx, "User opened a second instance from "+secondInstanceData.WorkingDirectory) + + runtime.WindowUnminimise(a.ctx) + runtime.Show(a.ctx) + go runtime.EventsEmit(a.ctx, "launchArgs", secondInstanceArgs) +} + // Log logs a message func (a *App) Log(msg string, level int) { switch level { diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index 51032e0..f98e6e6 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -50,10 +50,12 @@ export function AppSettings() { }, []); useEffect(() => { - document.documentElement.style.setProperty( - "--opacity", - String(Number(useWindowEffect === "1" ? "100" : useOpacity) / 100) - ); + if (!isLoading) { + document.documentElement.style.setProperty( + "--opacity", + String(Number(useWindowEffect === "1" ? "100" : useOpacity) / 100) + ); + } }, [useWindowEffect]); return ( diff --git a/main.go b/main.go index 41a00ce..52194b2 100644 --- a/main.go +++ b/main.go @@ -92,6 +92,10 @@ func main() { OnBeforeClose: app.beforeClose, OnShutdown: app.shutdown, WindowStartState: windowState, + SingleInstanceLock: &options.SingleInstanceLock{ + UniqueId: "4d1e7b7b-42a3-4357-92f3-425b1087b545", + OnSecondInstanceLaunch: app.onSecondInstanceLaunch, + }, Bind: []interface{}{ app, }, From 931154d2eb1425270ec57095920c753f3d64056e Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 16:35:04 +0300 Subject: [PATCH 051/221] Workflow (#4) * add code signing and draft release to build workflow --- .github/workflows/build.yml | 108 +++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3cf888..c05a1bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,34 +1,102 @@ -name: Build - +name: "Build" on: + workflow_dispatch: push: branches: - - main - development + - main pull_request: branches: - - main - development + - main + +permissions: + contents: write jobs: - build: + package: + name: Package strategy: - fail-fast: false matrix: - build: - [ - { - name: desktop-manager, - platform: windows/amd64, - os: windows-latest, - }, - ] - runs-on: ${{ matrix.build.os }} + platform: [windows-latest] + build-name: ["desktop-manager"] + go-version: [1.22] + node-version: [20] + runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 with: - submodules: recursive - - uses: dAppServer/wails-build-action@v2.2 + go-version: ${{ matrix.go-version }} + + - name: Setup Node + uses: actions/setup-node@v4 with: - build-name: ${{ matrix.build.name }} - build-platform: ${{ matrix.build.platform }} + node-version: ${{ matrix.node-version }} + + - name: Setup Wails + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Build Wails app + run: wails build + + - name: Build NSIS installer + run: wails build -nsis + + - name: Sign Windows binaries + shell: powershell + run: | + echo "Creating certificate file" + New-Item -ItemType directory -Path certificate + Set-Content -Path certificate\certificate.txt -Value '${{ secrets.WIN_SIGNING_CERT }}' + certutil -decode certificate\certificate.txt certificate\certificate.pfx + echo "Signing Binary" + & 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe' sign /fd sha256 /tr http://ts.ssl.com /f certificate\certificate.pfx /p '${{ secrets.WIN_SIGNING_CERT_PASSWORD }}' .\build\bin\${{matrix.build-name}}.exe + echo "Signing Installer" + & 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe' sign /fd sha256 /tr http://ts.ssl.com /f certificate\certificate.pfx /p '${{ secrets.WIN_SIGNING_CERT_PASSWORD }}' .\build\bin\${{matrix.build-name}}-amd64-installer.exe + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: binaries + path: build/bin/* + + extract-version: + if: github.ref == 'refs/heads/main' + name: Extract version + runs-on: ubuntu-latest + + outputs: + version: ${{ steps.extract_version.outputs.version }} + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Extract version + id: extract_version + run: | + version=$(jq -r '.info.productVersion' wails.json) + echo "version=$version" >> $GITHUB_OUTPUT + + create-release: + if: github.ref == 'refs/heads/main' + name: Create release + needs: [extract-version, package] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: binaries + path: ./artifacts + + - name: Create Draft Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + gh release create v${{ needs.extract-version.outputs.version }} ./artifacts/* --title "Release v${{ needs.extract-version.outputs.version }}" --draft --generate-notes \ No newline at end of file From 0a0b7f956faf33ca61ecd101675682ac93a62a34 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 16:49:56 +0300 Subject: [PATCH 052/221] add certificate --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8ca564a..d934974 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ node_modules /frontend/dist-ssr /frontend/wailsjs /frontend/package.json.md5 + +# Certificate files +certificate.* From fcf28b18b4c63efc48a3471e75e82905fbeec39f Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 17:26:20 +0300 Subject: [PATCH 053/221] fix window effect setting bug --- frontend/src/App.tsx | 47 ++++++++++--------- .../src/components/Settings/AppSettings.tsx | 22 +++++++-- frontend/src/contexts/storage-provider.tsx | 46 ++++++++++++++++++ 3 files changed, 90 insertions(+), 25 deletions(-) create mode 100644 frontend/src/contexts/storage-provider.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0bdcc7c..a1cb576 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,6 +6,7 @@ import TitleBar from "./components/TitleBar"; import Settings from "./components/Settings"; import { useTranslation } from "react-i18next"; import { useEffect } from "react"; +import { StorageProvider } from "./contexts/storage-provider"; function App() { const { t } = useTranslation(); @@ -31,29 +32,31 @@ function App() { return ( -
- - - -
- {t("nav.my_packs")} - {t("nav.edit")} - {t("nav.settings")} -
- -
+ +
+ + + +
+ {t("nav.my_packs")} + {t("nav.edit")} + {t("nav.settings")} +
+ +
- - View your packs here. - - - Edit your packs here. - - - - -
-
+ + View your packs here. + + + Edit your packs here. + + + + +
+
+
); } diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/Settings/AppSettings.tsx index f98e6e6..2cde84d 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/Settings/AppSettings.tsx @@ -11,9 +11,11 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Slider } from "../ui/my-slider"; import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; +import { useStorage } from "@/contexts/storage-provider"; export function AppSettings() { const { t } = useTranslation(); + const { getValue, setValue } = useStorage(); const [isLoading, setIsLoading] = useState(true); @@ -40,6 +42,9 @@ export function AppSettings() { setUseScale(parseInt(windowScaleValue)); setUseOpacity(parseInt(windowOpacityValue)); setUseWindowEffect(windowEffectValue); + if (getValue("initialWindowEffect") === undefined) { + setValue("initialWindowEffect", windowEffectValue); + } setIsLoading(false); // Mark loading as complete } ) @@ -53,7 +58,13 @@ export function AppSettings() { if (!isLoading) { document.documentElement.style.setProperty( "--opacity", - String(Number(useWindowEffect === "1" ? "100" : useOpacity) / 100) + String( + Number( + useWindowEffect === "1" || getValue("initialWindowEffect") === "1" + ? "100" + : useOpacity + ) / 100 + ) ); } }, [useWindowEffect]); @@ -140,7 +151,7 @@ export function AppSettings() {
@@ -158,7 +169,12 @@ export function AppSettings() { setUseOpacity(value[0]); document.documentElement.style.setProperty( "--opacity", - String(useOpacity / 100) + String( + (useWindowEffect === "1" || + getValue("initialWindowEffect") === "1" + ? 100 + : useOpacity) / 100 + ) ); }} onPointerUp={() => { diff --git a/frontend/src/contexts/storage-provider.tsx b/frontend/src/contexts/storage-provider.tsx new file mode 100644 index 0000000..6bae6f6 --- /dev/null +++ b/frontend/src/contexts/storage-provider.tsx @@ -0,0 +1,46 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; + +interface StorageContextType { + setValue: (key: string, value: any) => void; + getValue: (key: string) => any; +} + +interface StorageProviderProps { + children: ReactNode; +} + +const StorageContext = createContext(undefined); + +export const StorageProvider: React.FC = ({ children }) => { + const [storage, setStorage] = useState<{ [key: string]: any }>({}); + + const setValue = (key: string, value: any) => { + setStorage((prevStorage) => ({ + ...prevStorage, + [key]: value, + })); + }; + + const getValue = (key: string) => { + return storage[key]; + }; + + const contextValue: StorageContextType = { + setValue, + getValue, + }; + + return ( + + {children} + + ); +}; + +export const useStorage = () => { + const context = useContext(StorageContext); + if (!context) { + throw new Error('useStorage must be used within a StorageProvider'); + } + return context; +}; From 170168ff4132e3e751ccc883a8b59291010377fc Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 18:28:47 +0300 Subject: [PATCH 054/221] add max log files setting --- app.go | 6 +++ config.go | 3 ++ frontend/public/locales/en.json | 4 ++ frontend/public/locales/tr.json | 6 ++- .../components/Settings/AdvancedSettings.tsx | 38 ++++++++++++++++++ frontend/src/components/ui/input.tsx | 25 ++++++++++++ logger.go | 40 +++++++++++++++++++ main.go | 2 + 8 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/ui/input.tsx diff --git a/app.go b/app.go index dd78adc..8e33cd8 100644 --- a/app.go +++ b/app.go @@ -25,6 +25,8 @@ func (a *App) startup(ctx context.Context) { a.ctx = ctx appContext = ctx + runtime.LogInfo(appContext, "Starting application") + // Initiate paths runtime.LogInfo(appContext, "Initiating paths") err := path_init() @@ -32,6 +34,10 @@ func (a *App) startup(ctx context.Context) { if err != nil { runtime.LogError(appContext, err.Error()) } + + // Delete old log files + runtime.LogInfo(appContext, "Deleting old log files") + delete_old_logs() } // domReady is called after front-end resources have been loaded diff --git a/config.go b/config.go index 5a4b2cc..9da2274 100644 --- a/config.go +++ b/config.go @@ -21,6 +21,7 @@ type Config struct { EnableWarn *bool `json:"enableWarn"` // true, false EnableError *bool `json:"enableError"` // true, false EnableFatal *bool `json:"enableFatal"` // true, false + MaxLogFiles *int `json:"maxLogFiles"` // int Language *string `json:"language"` // en, tr WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen WindowScale *int `json:"windowScale"` // % @@ -38,6 +39,7 @@ func GetDefaultConfig() Config { defaultEnableWarn := true defaultEnableError := true defaultEnableFatal := true + defaultMaxLogFiles := 20 defaultLanguage := "en" defaultWindowStartState := 0 defaultWindowScale := 100 @@ -54,6 +56,7 @@ func GetDefaultConfig() Config { EnableWarn: &defaultEnableWarn, EnableError: &defaultEnableError, EnableFatal: &defaultEnableFatal, + MaxLogFiles: &defaultMaxLogFiles, Language: &defaultLanguage, WindowStartState: &defaultWindowStartState, WindowScale: &defaultWindowScale, diff --git a/frontend/public/locales/en.json b/frontend/public/locales/en.json index 00ae317..b9e4281 100644 --- a/frontend/public/locales/en.json +++ b/frontend/public/locales/en.json @@ -66,6 +66,10 @@ "log_levels": { "label": "Selected Log Levels", "description": "Choose the log levels to be recorded." + }, + "max_log_files":{ + "label": "Max Log Files", + "description": "Set the maximum number of log files to keep." } } } diff --git a/frontend/public/locales/tr.json b/frontend/public/locales/tr.json index ede16d1..800a05e 100644 --- a/frontend/public/locales/tr.json +++ b/frontend/public/locales/tr.json @@ -30,7 +30,7 @@ "window_effect": { "label": "Pencere Efekti", "description": "Pencere efektini seçin.", - + "auto": "Otomatik", "none": "Yok", "acrylic": "Akrilik", @@ -66,6 +66,10 @@ "log_levels": { "label": "Seçilen Günlük Seviyeleri", "description": "Kaydedilecek günlük seviyelerini seçin." + }, + "max_log_files": { + "label": "Maksimum Günlük Dosyası Sayısı", + "description": "Saklanacak maksimum günlük dosyası sayısını ayarlayın." } } } diff --git a/frontend/src/components/Settings/AdvancedSettings.tsx b/frontend/src/components/Settings/AdvancedSettings.tsx index d54b9a8..40ba361 100644 --- a/frontend/src/components/Settings/AdvancedSettings.tsx +++ b/frontend/src/components/Settings/AdvancedSettings.tsx @@ -10,6 +10,7 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { Switch } from "../ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { Input } from "../ui/input"; export function AdvancedSettings() { const { t } = useTranslation(); @@ -23,6 +24,7 @@ export function AdvancedSettings() { const [enableWarn, setEnableWarn] = useState(false); const [enableError, setEnableError] = useState(false); const [enableFatal, setEnableFatal] = useState(false); + const [maxLogFiles, setMaxLogFiles] = useState(-1); useEffect(() => { Promise.all([ @@ -33,6 +35,7 @@ export function AdvancedSettings() { GetConfigField("EnableWarn"), GetConfigField("EnableError"), GetConfigField("EnableFatal"), + GetConfigField("MaxLogFiles"), ]) .then( ([ @@ -43,6 +46,7 @@ export function AdvancedSettings() { enableWarn, enableError, enableFatal, + maxLogFiles, ]) => { setEnableLogging(enableLogging === "true"); setEnableTrace(enableTrace === "true"); @@ -51,6 +55,7 @@ export function AdvancedSettings() { setEnableWarn(enableWarn === "true"); setEnableError(enableError === "true"); setEnableFatal(enableFatal === "true"); + setMaxLogFiles(parseInt(maxLogFiles)); setIsLoading(false); } @@ -180,6 +185,39 @@ export function AdvancedSettings() { + + +
+ + {t("settings.advanced.max_log_files.label")} + + + {t("settings.advanced.max_log_files.description") + + " (" + + t("settings.restart_the_app_for_changes_to_take_effect") + + ")"} + +
+ + { + const value = Math.max( + 1, + Math.min(10000, parseInt(e.target.value)) + ); + const targetValue = isNaN(parseInt(e.target.value)) ? 20 : value; + SetConfigField("MaxLogFiles", String(targetValue)).then(() => { + setMaxLogFiles(value); + }); + }} + min={1} + max={10000} + /> + +
); } diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx new file mode 100644 index 0000000..677d05f --- /dev/null +++ b/frontend/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/logger.go b/logger.go index 85081b5..58a8de7 100644 --- a/logger.go +++ b/logger.go @@ -1,8 +1,13 @@ package main import ( + "fmt" "log" "os" + "path" + "sort" + + "github.com/wailsapp/wails/v2/pkg/runtime" ) type Logger interface { @@ -92,3 +97,38 @@ func (l *FileLogger) Fatal(message string) { } os.Exit(1) } + +func delete_old_logs() { + maxLogFiles := *config.MaxLogFiles + + files, err := os.ReadDir(logsFolder) + if err != nil { + runtime.LogWarning(appContext, "Failed to read log files in logs folder: "+err.Error()) + } + + runtime.LogTrace(appContext, "Attempting to delete old log files") + runtime.LogTrace(appContext, "Attempting to sort log files") + + sort.Slice(files, func(i, j int) bool { + infoI, err := os.Stat(path.Join(logsFolder, files[i].Name())) + if err != nil { + return false + } + + infoJ, err := os.Stat(path.Join(logsFolder, files[j].Name())) + if err != nil { + return false + } + + return infoI.ModTime().Before(infoJ.ModTime()) + }) + + runtime.LogTrace(appContext, "Sorting log files complete") + + if len(files) > maxLogFiles { + runtime.LogDebug(appContext, fmt.Sprintf("Attempting to delete oldest %d log files", len(files)-maxLogFiles)) + for i := 0; i < len(files)-maxLogFiles; i++ { + os.Remove(path.Join(logsFolder, files[i].Name())) + } + } +} diff --git a/main.go b/main.go index 52194b2..1bb6bd3 100644 --- a/main.go +++ b/main.go @@ -21,11 +21,13 @@ func main() { // Create an instance of the app structure app := NewApp() + // Config err := config_init() if err != nil { log.Println(err) } + // Logger var fileLogger Logger if *config.EnableLogging { From 81d90a03f8699c2852ac89b2a6d43bd284f81643 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 19:05:22 +0300 Subject: [PATCH 055/221] add language detection on first run --- app.go | 3 + config.go | 4 +- .../public/locales/{en.json => en-US.json} | 0 .../public/locales/{tr.json => tr-TR.json} | 0 .../components/Settings/GeneralSettings.tsx | 2 +- frontend/src/i18n.ts | 4 +- frontend/src/locales.json | 4 +- go.mod | 5 +- go.sum | 2 + language.go | 55 +++++++++++++++++++ 10 files changed, 71 insertions(+), 8 deletions(-) rename frontend/public/locales/{en.json => en-US.json} (100%) rename frontend/public/locales/{tr.json => tr-TR.json} (100%) create mode 100644 language.go diff --git a/app.go b/app.go index 8e33cd8..88b2ba1 100644 --- a/app.go +++ b/app.go @@ -38,6 +38,9 @@ func (a *App) startup(ctx context.Context) { // Delete old log files runtime.LogInfo(appContext, "Deleting old log files") delete_old_logs() + + // Set default language in first run + set_system_language_first_run() } // domReady is called after front-end resources have been loaded diff --git a/config.go b/config.go index 9da2274..9d30e30 100644 --- a/config.go +++ b/config.go @@ -22,7 +22,7 @@ type Config struct { EnableError *bool `json:"enableError"` // true, false EnableFatal *bool `json:"enableFatal"` // true, false MaxLogFiles *int `json:"maxLogFiles"` // int - Language *string `json:"language"` // en, tr + Language *string `json:"language"` // en-US, tr-TR WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen WindowScale *int `json:"windowScale"` // % Opacity *int `json:"opacity"` // % @@ -40,7 +40,7 @@ func GetDefaultConfig() Config { defaultEnableError := true defaultEnableFatal := true defaultMaxLogFiles := 20 - defaultLanguage := "en" + defaultLanguage := "en-US" defaultWindowStartState := 0 defaultWindowScale := 100 defaultOpacity := 90 diff --git a/frontend/public/locales/en.json b/frontend/public/locales/en-US.json similarity index 100% rename from frontend/public/locales/en.json rename to frontend/public/locales/en-US.json diff --git a/frontend/public/locales/tr.json b/frontend/public/locales/tr-TR.json similarity index 100% rename from frontend/public/locales/tr.json rename to frontend/public/locales/tr-TR.json diff --git a/frontend/src/components/Settings/GeneralSettings.tsx b/frontend/src/components/Settings/GeneralSettings.tsx index e0618a9..5f178ce 100644 --- a/frontend/src/components/Settings/GeneralSettings.tsx +++ b/frontend/src/components/Settings/GeneralSettings.tsx @@ -23,7 +23,7 @@ export function GeneralSettings() { const [isLoading, setIsLoading] = useState(true); - const [language, setLanguage] = useState("en"); + const [language, setLanguage] = useState("en-US"); useEffect(() => { Promise.all([GetConfigField("Language")]) diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 83e6b31..16b0ca5 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -6,7 +6,7 @@ import HttpApi from "i18next-http-backend"; import locales from "@/locales.json"; const initializeI18n = async () => { - const language = (await GetConfigField("Language")) || "en"; + const language = (await GetConfigField("Language")) || "en-US"; const supportedLngs = locales.locales.map((language) => language.code); @@ -17,7 +17,7 @@ const initializeI18n = async () => { .init({ lng: language, supportedLngs: supportedLngs, // Add supported languages here - fallbackLng: "en", + fallbackLng: "en-US", debug: true, interpolation: { escapeValue: false, // React already safes from XSS diff --git a/frontend/src/locales.json b/frontend/src/locales.json index 768ddec..2b4b9da 100644 --- a/frontend/src/locales.json +++ b/frontend/src/locales.json @@ -2,12 +2,12 @@ "locales": [ { "name": "English", - "code": "en", + "code": "en-US", "authors": ["Bedirhan Yenilmez"] }, { "name": "Türkçe", - "code": "tr", + "code": "tr-TR", "authors": ["Bedirhan Yenilmez"] } ] diff --git a/go.mod b/go.mod index b9a8fdc..e57de7c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,10 @@ go 1.21 toolchain go1.22.0 -require github.com/wailsapp/wails/v2 v2.9.1 +require ( + github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 + github.com/wailsapp/wails/v2 v2.9.1 +) require ( github.com/bep/debounce v1.2.1 // indirect diff --git a/go.sum b/go.sum index 7e8f88d..16137fe 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 h1:Po+wkNdMmN+Zj1tDsJQy7mJlPlwGNQd9JZoPjObagf8= +github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49/go.mod h1:YiutDnxPRLk5DLUFj6Rw4pRBBURZY07GFr54NdV9mQg= github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= diff --git a/language.go b/language.go new file mode 100644 index 0000000..f3fd67b --- /dev/null +++ b/language.go @@ -0,0 +1,55 @@ +package main + +import ( + _ "embed" + + "encoding/json" + "os" + + "github.com/jeandeaual/go-locale" +) + +//go:embed frontend/src/locales.json +var localesJSON string + +func set_system_language_first_run() error { + // Check if configPath exists + if _, err := os.Stat(configPath); os.IsNotExist(err) { + userLocales, err := locale.GetLocales() + if err != nil { + return err + } + + var localesData struct { + Locales []struct { + Code string `json:"code"` + } `json:"locales"` + } + err = json.Unmarshal([]byte(localesJSON), &localesData) + if err != nil { + return err + } + availableLocales := make([]string, len(localesData.Locales)) + for i, l := range localesData.Locales { + availableLocales[i] = l.Code + } + + for _, l := range userLocales { + if contains(availableLocales, l) { + config.Language = &l + } + } + } + + return nil +} + +func contains(haystack []string, needle string) bool { + for _, v := range haystack { + if v == needle { + return true + } + } + + return false +} From e6e9022e681593cfae7c21698fdf0eef02195f16 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 19:12:59 +0300 Subject: [PATCH 056/221] add theme setting to app settings --- .../AdvancedSettings.tsx | 0 .../{Settings => SettingTabs}/AppSettings.tsx | 7 ++- .../GeneralSettings.tsx | 40 +-------------- .../components/SettingTabs/ThemeSetting.tsx | 51 +++++++++++++++++++ frontend/src/components/Settings.tsx | 6 +-- frontend/src/components/ui/settings-group.tsx | 2 +- 6 files changed, 63 insertions(+), 43 deletions(-) rename frontend/src/components/{Settings => SettingTabs}/AdvancedSettings.tsx (100%) rename frontend/src/components/{Settings => SettingTabs}/AppSettings.tsx (97%) rename frontend/src/components/{Settings => SettingTabs}/GeneralSettings.tsx (61%) create mode 100644 frontend/src/components/SettingTabs/ThemeSetting.tsx diff --git a/frontend/src/components/Settings/AdvancedSettings.tsx b/frontend/src/components/SettingTabs/AdvancedSettings.tsx similarity index 100% rename from frontend/src/components/Settings/AdvancedSettings.tsx rename to frontend/src/components/SettingTabs/AdvancedSettings.tsx diff --git a/frontend/src/components/Settings/AppSettings.tsx b/frontend/src/components/SettingTabs/AppSettings.tsx similarity index 97% rename from frontend/src/components/Settings/AppSettings.tsx rename to frontend/src/components/SettingTabs/AppSettings.tsx index 2cde84d..bcc47e7 100644 --- a/frontend/src/components/Settings/AppSettings.tsx +++ b/frontend/src/components/SettingTabs/AppSettings.tsx @@ -12,6 +12,7 @@ import { useTranslation } from "react-i18next"; import { Slider } from "../ui/my-slider"; import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; import { useStorage } from "@/contexts/storage-provider"; +import { ThemeSetting } from "./ThemeSetting"; export function AppSettings() { const { t } = useTranslation(); @@ -71,6 +72,8 @@ export function AppSettings() { return ( + +
@@ -151,7 +154,9 @@ export function AppSettings() {
diff --git a/frontend/src/components/Settings/GeneralSettings.tsx b/frontend/src/components/SettingTabs/GeneralSettings.tsx similarity index 61% rename from frontend/src/components/Settings/GeneralSettings.tsx rename to frontend/src/components/SettingTabs/GeneralSettings.tsx index 5f178ce..4254f39 100644 --- a/frontend/src/components/Settings/GeneralSettings.tsx +++ b/frontend/src/components/SettingTabs/GeneralSettings.tsx @@ -10,9 +10,7 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Combobox } from "../ui/combobox"; import locales from "@/locales.json"; -import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; -import { Monitor, Moon, Sun } from "lucide-react"; -import { useTheme } from "@/contexts/theme-provider"; +import {ThemeSetting} from "./ThemeSetting"; export function GeneralSettings() { const { t, i18n } = useTranslation(); @@ -38,8 +36,6 @@ export function GeneralSettings() { }); }, []); - const { theme, setTheme } = useTheme(); - return ( @@ -70,39 +66,7 @@ export function GeneralSettings() { - -
- {t("settings.general.theme.label")} - - {t("settings.general.theme.description")} - -
- - - setTheme("system")} - > - - - setTheme("light")} - > - - - setTheme("dark")} - > - - - - -
+
); } diff --git a/frontend/src/components/SettingTabs/ThemeSetting.tsx b/frontend/src/components/SettingTabs/ThemeSetting.tsx new file mode 100644 index 0000000..1f594d8 --- /dev/null +++ b/frontend/src/components/SettingTabs/ThemeSetting.tsx @@ -0,0 +1,51 @@ +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { useTranslation } from "react-i18next"; +import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; +import { Monitor, Moon, Sun } from "lucide-react"; +import { useTheme } from "@/contexts/theme-provider"; + +export function ThemeSetting() { + const { t } = useTranslation(); + const { theme, setTheme } = useTheme(); + + return ( + +
+ {t("settings.general.theme.label")} + + {t("settings.general.theme.description")} + +
+ + + setTheme("system")} + > + + + setTheme("light")} + > + + + setTheme("dark")} + > + + + + +
+ ); +} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 01f93a2..a42a5be 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -1,8 +1,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { AppSettings } from "./Settings/AppSettings"; -import { AdvancedSettings } from "./Settings/AdvancedSettings"; +import { AppSettings } from "./SettingTabs/AppSettings"; +import { AdvancedSettings } from "./SettingTabs/AdvancedSettings"; import { useTranslation } from "react-i18next"; -import { GeneralSettings } from "./Settings/GeneralSettings"; +import { GeneralSettings } from "./SettingTabs/GeneralSettings"; export default function Settings() { const { t } = useTranslation(); diff --git a/frontend/src/components/ui/settings-group.tsx b/frontend/src/components/ui/settings-group.tsx index 6602b23..41d803e 100644 --- a/frontend/src/components/ui/settings-group.tsx +++ b/frontend/src/components/ui/settings-group.tsx @@ -42,7 +42,7 @@ interface SettingsGroupSkeletonProps extends React.HTMLAttributes = ({ className, ...rest }) => ( -
+
From 4da84c3ed197d88b21bded8462674783a15c017f Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 21:24:22 +0300 Subject: [PATCH 057/221] add system notification function --- app.go | 25 +++++++++- app_paths.go | 13 +++++ .../SettingTabs/AdvancedSettings.tsx | 31 +++++++++++- frontend/src/index.css | 2 +- go.mod | 6 ++- go.sum | 12 ++++- language.go | 47 +++++++++---------- main.go | 3 ++ 8 files changed, 107 insertions(+), 32 deletions(-) diff --git a/app.go b/app.go index 88b2ba1..d091ae1 100644 --- a/app.go +++ b/app.go @@ -2,8 +2,10 @@ package main import ( "context" + "os" "strings" + "github.com/gen2brain/beeep" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/runtime" ) @@ -39,8 +41,10 @@ func (a *App) startup(ctx context.Context) { runtime.LogInfo(appContext, "Deleting old log files") delete_old_logs() - // Set default language in first run - set_system_language_first_run() + // Check if configPath exists + if _, err := os.Stat(configPath); os.IsNotExist(err) { + onFirstRun() + } } // domReady is called after front-end resources have been loaded @@ -92,6 +96,13 @@ func (a *App) onSecondInstanceLaunch(secondInstanceData options.SecondInstanceDa go runtime.EventsEmit(a.ctx, "launchArgs", secondInstanceArgs) } +func onFirstRun() { + runtime.LogInfo(appContext, "First run detected") + + runtime.LogInfo(appContext, "Setting default system language") + set_system_language() +} + // Log logs a message func (a *App) Log(msg string, level int) { switch level { @@ -132,3 +143,13 @@ func (a *App) Minimize() { runtime.LogInfo(a.ctx, "Minimizing window") runtime.WindowMinimise(a.ctx) } + +// Send notification +func (a *App) SendNotification(title string, message string) { + runtime.LogInfo(a.ctx, "Sending notification") + + err := beeep.Notify(title, message, appIconPath) + if err != nil { + runtime.LogError(a.ctx, "Error sending notification: "+err.Error()) + } +} diff --git a/app_paths.go b/app_paths.go index bfe8f75..fbbd710 100644 --- a/app_paths.go +++ b/app_paths.go @@ -11,6 +11,7 @@ import ( var packsFolder string var logsFolder string var configPath string +var appIconPath string func path_init() error { appData, err := os.UserConfigDir() @@ -27,6 +28,7 @@ func path_init() error { logsFolder = path.Join(appFolder, "logs") configPath = path.Join(appFolder, "config.json") + appIconPath = path.Join(appFolder, "appicon.png") runtime.LogTrace(appContext, "Attempting to create folders") err = create_folder(appFolder) @@ -43,6 +45,17 @@ func path_init() error { return err } + runtime.LogTrace(appContext, "Attempting to create appicon") + + // Create icon from embedded appIcon if it exists + if _, err := os.Stat(appIconPath); os.IsNotExist(err) { + runtime.LogTrace(appContext, "appicon not found, creating from embedded appIcon") + err = os.WriteFile(appIconPath, appIcon, 0o644) + if err != nil { + return err + } + } + return nil } diff --git a/frontend/src/components/SettingTabs/AdvancedSettings.tsx b/frontend/src/components/SettingTabs/AdvancedSettings.tsx index 40ba361..1465b7c 100644 --- a/frontend/src/components/SettingTabs/AdvancedSettings.tsx +++ b/frontend/src/components/SettingTabs/AdvancedSettings.tsx @@ -1,4 +1,9 @@ -import { GetConfigField, Log, SetConfigField } from "wailsjs/go/main/App"; +import { + GetConfigField, + Log, + SendNotification, + SetConfigField, +} from "wailsjs/go/main/App"; import { SettingsGroup, SettingsItem, @@ -11,6 +16,7 @@ import { Switch } from "../ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Input } from "../ui/input"; +import { Button } from "../ui/button"; export function AdvancedSettings() { const { t } = useTranslation(); @@ -218,6 +224,29 @@ export function AdvancedSettings() { /> + + +
+ + {t("settings.advanced.max_log_files.label")} + + + {t("settings.advanced.max_log_files.description") + + " (" + + t("settings.restart_the_app_for_changes_to_take_effect") + + ")"} + +
+ + + +
); } diff --git a/frontend/src/index.css b/frontend/src/index.css index 4715861..0ee9e53 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -35,7 +35,7 @@ --radius: 0.5rem; - --opacity: 0.85; + --opacity: 1; } .dark { diff --git a/go.mod b/go.mod index e57de7c..c98f3e8 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 toolchain go1.22.0 require ( + github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4 github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 github.com/wailsapp/wails/v2 v2.9.1 ) @@ -12,6 +13,7 @@ require ( require ( github.com/bep/debounce v1.2.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect @@ -23,10 +25,12 @@ require ( github.com/leaanthony/u v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/samber/lo v1.38.1 // indirect + github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect @@ -35,7 +39,7 @@ require ( golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect + golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.15.0 // indirect ) diff --git a/go.sum b/go.sum index 16137fe..5f0194b 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,12 @@ github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3IS github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4 h1:ygs9POGDQpQGLJPlq4+0LBUmMBNox1N4JSpw+OETcvI= +github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4/go.mod h1:0W7dI87PvXJ1Sjs0QPvWXKcQmNERY77e8l7GFhZB/s4= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE= +github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -37,6 +41,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -52,6 +58,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk= +github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o= github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -82,8 +90,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= diff --git a/language.go b/language.go index f3fd67b..81e163d 100644 --- a/language.go +++ b/language.go @@ -4,7 +4,6 @@ import ( _ "embed" "encoding/json" - "os" "github.com/jeandeaual/go-locale" ) @@ -12,32 +11,30 @@ import ( //go:embed frontend/src/locales.json var localesJSON string -func set_system_language_first_run() error { - // Check if configPath exists - if _, err := os.Stat(configPath); os.IsNotExist(err) { - userLocales, err := locale.GetLocales() - if err != nil { - return err - } +func set_system_language() error { - var localesData struct { - Locales []struct { - Code string `json:"code"` - } `json:"locales"` - } - err = json.Unmarshal([]byte(localesJSON), &localesData) - if err != nil { - return err - } - availableLocales := make([]string, len(localesData.Locales)) - for i, l := range localesData.Locales { - availableLocales[i] = l.Code - } + userLocales, err := locale.GetLocales() + if err != nil { + return err + } + + var localesData struct { + Locales []struct { + Code string `json:"code"` + } `json:"locales"` + } + err = json.Unmarshal([]byte(localesJSON), &localesData) + if err != nil { + return err + } + availableLocales := make([]string, len(localesData.Locales)) + for i, l := range localesData.Locales { + availableLocales[i] = l.Code + } - for _, l := range userLocales { - if contains(availableLocales, l) { - config.Language = &l - } + for _, l := range userLocales { + if contains(availableLocales, l) { + config.Language = &l } } diff --git a/main.go b/main.go index 1bb6bd3..dbe7e76 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,9 @@ import ( //go:embed all:frontend/dist var assets embed.FS +//go:embed build/appicon.png +var appIcon []byte + func main() { // Create an instance of the app structure app := NewApp() From 0744b65b945478f3d611206474364c5d69077d28 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 26 Jun 2024 21:43:08 +0300 Subject: [PATCH 058/221] add toaster for notification --- app.go | 13 +- frontend/package.json | 1 + frontend/src/App.tsx | 10 + .../SettingTabs/AdvancedSettings.tsx | 23 --- frontend/src/components/ui/toast.tsx | 127 ++++++++++++ frontend/src/components/ui/toaster.tsx | 33 +++ frontend/src/components/ui/use-toast.ts | 192 ++++++++++++++++++ frontend/src/global.d.ts | 8 + frontend/yarn.lock | 25 +++ 9 files changed, 406 insertions(+), 26 deletions(-) create mode 100644 frontend/src/components/ui/toast.tsx create mode 100644 frontend/src/components/ui/toaster.tsx create mode 100644 frontend/src/components/ui/use-toast.ts create mode 100644 frontend/src/global.d.ts diff --git a/app.go b/app.go index d091ae1..8962977 100644 --- a/app.go +++ b/app.go @@ -148,8 +148,15 @@ func (a *App) Minimize() { func (a *App) SendNotification(title string, message string) { runtime.LogInfo(a.ctx, "Sending notification") - err := beeep.Notify(title, message, appIconPath) - if err != nil { - runtime.LogError(a.ctx, "Error sending notification: "+err.Error()) + if runtime.WindowIsNormal(a.ctx) || runtime.WindowIsMaximised(a.ctx) || runtime.WindowIsFullscreen(a.ctx) { + runtime.WindowExecJS(a.ctx, `window.toast({ + title: "`+title+`", + description: "`+message+`" + });`) + } else { + err := beeep.Notify(title, message, appIconPath) + if err != nil { + runtime.LogError(a.ctx, "Error sending notification: "+err.Error()) + } } } diff --git a/frontend/package.json b/frontend/package.json index f48314d..8ab3646 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", + "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "class-variance-authority": "^0.7.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a1cb576..68a2659 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,9 +7,12 @@ import Settings from "./components/Settings"; import { useTranslation } from "react-i18next"; import { useEffect } from "react"; import { StorageProvider } from "./contexts/storage-provider"; +import { Toaster } from "./components/ui/toaster"; +import { useToast } from "./components/ui/use-toast"; function App() { const { t } = useTranslation(); + const { toast } = useToast(); useEffect(() => { Promise.all([ @@ -30,6 +33,12 @@ function App() { }); }, []); + window.toast = ({ ...props }: any) => { + toast({ + ...props, + }); + }; + return ( @@ -56,6 +65,7 @@ function App() {
+ ); diff --git a/frontend/src/components/SettingTabs/AdvancedSettings.tsx b/frontend/src/components/SettingTabs/AdvancedSettings.tsx index 1465b7c..75c29cc 100644 --- a/frontend/src/components/SettingTabs/AdvancedSettings.tsx +++ b/frontend/src/components/SettingTabs/AdvancedSettings.tsx @@ -224,29 +224,6 @@ export function AdvancedSettings() { /> - - -
- - {t("settings.advanced.max_log_files.label")} - - - {t("settings.advanced.max_log_files.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} - -
- - - -
); } diff --git a/frontend/src/components/ui/toast.tsx b/frontend/src/components/ui/toast.tsx new file mode 100644 index 0000000..a822477 --- /dev/null +++ b/frontend/src/components/ui/toast.tsx @@ -0,0 +1,127 @@ +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef + +type ToastActionElement = React.ReactElement + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} diff --git a/frontend/src/components/ui/toaster.tsx b/frontend/src/components/ui/toaster.tsx new file mode 100644 index 0000000..a2209ba --- /dev/null +++ b/frontend/src/components/ui/toaster.tsx @@ -0,0 +1,33 @@ +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast" +import { useToast } from "@/components/ui/use-toast" + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ) + })} + +
+ ) +} diff --git a/frontend/src/components/ui/use-toast.ts b/frontend/src/components/ui/use-toast.ts new file mode 100644 index 0000000..f2d6682 --- /dev/null +++ b/frontend/src/components/ui/use-toast.ts @@ -0,0 +1,192 @@ +// Inspired by react-hot-toast library +import * as React from "react" + +import type { + ToastActionElement, + ToastProps, +} from "@/components/ui/toast" + +const TOAST_LIMIT = 2 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts new file mode 100644 index 0000000..8e4ea21 --- /dev/null +++ b/frontend/src/global.d.ts @@ -0,0 +1,8 @@ +declare global { + interface Window { + toast: (props: ToastProps) => void; + } + } + + export {}; + \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 44a93b2..e1e8811 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -824,6 +824,24 @@ "@radix-ui/react-roving-focus" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0" +"@radix-ui/react-toast@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.2.1.tgz#4bde231ed27d007dcd0455a446565ca619f92a2d" + integrity sha512-5trl7piMXcZiCq7MW6r8YYmu0bK5qDpTWz+FdEPdKyft2UixkspheYbjbrLXVN5NGKHFbOP7lm8eD0biiSqZqg== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-portal" "1.1.1" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-visually-hidden" "1.1.0" + "@radix-ui/react-toggle-group@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.0.tgz#28714c4d1ff4961a8fd259b1feef58b4cac92f80" @@ -919,6 +937,13 @@ dependencies: "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-visually-hidden@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2" + integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ== + dependencies: + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/rect@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" From 833cfd2c1bf27342cdb367ff28ad92cd5e376c07 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 27 Jun 2024 11:05:53 +0300 Subject: [PATCH 059/221] Update build.yml --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c05a1bd..e33b685 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,6 +5,7 @@ on: branches: - development - main + - rewrite-2 pull_request: branches: - development @@ -65,7 +66,7 @@ jobs: path: build/bin/* extract-version: - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/rewrite-2' name: Extract version runs-on: ubuntu-latest @@ -83,7 +84,7 @@ jobs: echo "version=$version" >> $GITHUB_OUTPUT create-release: - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/rewrite-2' name: Create release needs: [extract-version, package] runs-on: ubuntu-latest From 40faa3d652f16616c542be3b43b33b499762e5c3 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 27 Jun 2024 11:13:59 +0300 Subject: [PATCH 060/221] Update AdvancedSettings.tsx --- frontend/src/components/SettingTabs/AdvancedSettings.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/components/SettingTabs/AdvancedSettings.tsx b/frontend/src/components/SettingTabs/AdvancedSettings.tsx index 75c29cc..70d691c 100644 --- a/frontend/src/components/SettingTabs/AdvancedSettings.tsx +++ b/frontend/src/components/SettingTabs/AdvancedSettings.tsx @@ -1,7 +1,6 @@ import { GetConfigField, Log, - SendNotification, SetConfigField, } from "wailsjs/go/main/App"; import { @@ -16,7 +15,6 @@ import { Switch } from "../ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Input } from "../ui/input"; -import { Button } from "../ui/button"; export function AdvancedSettings() { const { t } = useTranslation(); From 849d5e285e3ecd2ae3bbdbb03e91544c08f8f745 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 27 Jun 2024 14:13:22 +0300 Subject: [PATCH 061/221] Update build.yml --- .github/workflows/build.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e33b685..c05a1bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,6 @@ on: branches: - development - main - - rewrite-2 pull_request: branches: - development @@ -66,7 +65,7 @@ jobs: path: build/bin/* extract-version: - if: github.ref == 'refs/heads/rewrite-2' + if: github.ref == 'refs/heads/main' name: Extract version runs-on: ubuntu-latest @@ -84,7 +83,7 @@ jobs: echo "version=$version" >> $GITHUB_OUTPUT create-release: - if: github.ref == 'refs/heads/rewrite-2' + if: github.ref == 'refs/heads/main' name: Create release needs: [extract-version, package] runs-on: ubuntu-latest From 4577b81f6a56330740927d323c702dc1ac3b7aee Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 27 Jun 2024 14:16:27 +0300 Subject: [PATCH 062/221] update setting items directory --- .../components/{SettingTabs => SettingItems}/ThemeSetting.tsx | 0 frontend/src/components/SettingTabs/AppSettings.tsx | 2 +- frontend/src/components/SettingTabs/GeneralSettings.tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename frontend/src/components/{SettingTabs => SettingItems}/ThemeSetting.tsx (100%) diff --git a/frontend/src/components/SettingTabs/ThemeSetting.tsx b/frontend/src/components/SettingItems/ThemeSetting.tsx similarity index 100% rename from frontend/src/components/SettingTabs/ThemeSetting.tsx rename to frontend/src/components/SettingItems/ThemeSetting.tsx diff --git a/frontend/src/components/SettingTabs/AppSettings.tsx b/frontend/src/components/SettingTabs/AppSettings.tsx index bcc47e7..c98a4a3 100644 --- a/frontend/src/components/SettingTabs/AppSettings.tsx +++ b/frontend/src/components/SettingTabs/AppSettings.tsx @@ -12,7 +12,7 @@ import { useTranslation } from "react-i18next"; import { Slider } from "../ui/my-slider"; import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; import { useStorage } from "@/contexts/storage-provider"; -import { ThemeSetting } from "./ThemeSetting"; +import { ThemeSetting } from "../SettingItems/ThemeSetting"; export function AppSettings() { const { t } = useTranslation(); diff --git a/frontend/src/components/SettingTabs/GeneralSettings.tsx b/frontend/src/components/SettingTabs/GeneralSettings.tsx index 4254f39..6437f95 100644 --- a/frontend/src/components/SettingTabs/GeneralSettings.tsx +++ b/frontend/src/components/SettingTabs/GeneralSettings.tsx @@ -10,7 +10,7 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Combobox } from "../ui/combobox"; import locales from "@/locales.json"; -import {ThemeSetting} from "./ThemeSetting"; +import {ThemeSetting} from "../SettingItems/ThemeSetting"; export function GeneralSettings() { const { t, i18n } = useTranslation(); From d1e866350a7c9b978d25cf7b58fa63705af9a807 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 27 Jun 2024 17:09:16 +0300 Subject: [PATCH 063/221] add caching for frontend config --- frontend/src/App.tsx | 4 +- .../SettingTabs/AdvancedSettings.tsx | 7 +-- .../components/SettingTabs/AppSettings.tsx | 2 +- .../SettingTabs/GeneralSettings.tsx | 2 +- frontend/src/contexts/theme-provider.tsx | 3 +- frontend/src/lib/config.ts | 58 +++++++++++++++++++ 6 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 frontend/src/lib/config.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 68a2659..fe56b85 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { GetConfigField } from "wailsjs/go/main/App"; +import { GetConfigField, InitConfigCache } from "@/lib/config"; import { ThemeProvider } from "./contexts/theme-provider"; import ModeToggle from "@/components/ModeToggle"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -15,6 +15,8 @@ function App() { const { toast } = useToast(); useEffect(() => { + InitConfigCache(); + Promise.all([ GetConfigField("WindowScale"), GetConfigField("Opacity"), diff --git a/frontend/src/components/SettingTabs/AdvancedSettings.tsx b/frontend/src/components/SettingTabs/AdvancedSettings.tsx index 70d691c..c798cb0 100644 --- a/frontend/src/components/SettingTabs/AdvancedSettings.tsx +++ b/frontend/src/components/SettingTabs/AdvancedSettings.tsx @@ -1,8 +1,4 @@ -import { - GetConfigField, - Log, - SetConfigField, -} from "wailsjs/go/main/App"; +import { Log } from "wailsjs/go/main/App"; import { SettingsGroup, SettingsItem, @@ -15,6 +11,7 @@ import { Switch } from "../ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Input } from "../ui/input"; +import { GetConfigField, SetConfigField } from "@/lib/config"; export function AdvancedSettings() { const { t } = useTranslation(); diff --git a/frontend/src/components/SettingTabs/AppSettings.tsx b/frontend/src/components/SettingTabs/AppSettings.tsx index c98a4a3..1dc6aee 100644 --- a/frontend/src/components/SettingTabs/AppSettings.tsx +++ b/frontend/src/components/SettingTabs/AppSettings.tsx @@ -1,4 +1,4 @@ -import { GetConfigField, SetConfigField } from "wailsjs/go/main/App"; +import { GetConfigField, SetConfigField } from "@/lib/config"; import { SettingsGroup, SettingsItem, diff --git a/frontend/src/components/SettingTabs/GeneralSettings.tsx b/frontend/src/components/SettingTabs/GeneralSettings.tsx index 6437f95..59e5f9d 100644 --- a/frontend/src/components/SettingTabs/GeneralSettings.tsx +++ b/frontend/src/components/SettingTabs/GeneralSettings.tsx @@ -1,4 +1,4 @@ -import { SetConfigField, GetConfigField } from "wailsjs/go/main/App"; +import { SetConfigField, GetConfigField } from "@/lib/config"; import { SettingsGroup, SettingsItem, diff --git a/frontend/src/contexts/theme-provider.tsx b/frontend/src/contexts/theme-provider.tsx index 39fca34..7c3fdb8 100644 --- a/frontend/src/contexts/theme-provider.tsx +++ b/frontend/src/contexts/theme-provider.tsx @@ -1,5 +1,6 @@ -import { GetConfigField, Log, SetConfigField, SetTheme } from "wailsjs/go/main/App"; +import { Log, SetTheme } from "wailsjs/go/main/App"; import { createContext, useContext, useEffect, useState } from "react"; +import { GetConfigField, SetConfigField } from "@/lib/config"; type Theme = "dark" | "light" | "system"; diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts new file mode 100644 index 0000000..9e093c1 --- /dev/null +++ b/frontend/src/lib/config.ts @@ -0,0 +1,58 @@ +import { + GetConfigField as GetConfigField_backend, + SetConfigField as SetConfigField_backend, +} from "wailsjs/go/main/App"; + +interface ConfigCache { + [key: string]: string; +} + +// Initialize an empty cache object +let configCache: ConfigCache = {}; + +// Function to retrieve config field, using cache if available +export async function GetConfigField(key: string): Promise { + // Check if value exists in cache + if (configCache[key] !== undefined) { + return configCache[key]; + } + + // Call GetConfigField_backend to fetch value from backend + const value = await GetConfigField_backend(key); + + // Cache the fetched value + configCache[key] = value; + + return value; +} + +// Function to set config field and update cache +export async function SetConfigField( + key: string, + value: string +): Promise { + // Call SetConfigField_backend to update backend + await SetConfigField_backend(key, value); + + // Update cache with new value + configCache[key] = value; +} + +// Function to initialize config cache +export function InitConfigCache(): void { + GetConfigField("Theme"); + GetConfigField("UseSystemTitleBar"); + GetConfigField("EnableLogging"); + GetConfigField("EnableTrace"); + GetConfigField("EnableDebug"); + GetConfigField("EnableInfo"); + GetConfigField("EnableWarn"); + GetConfigField("EnableError"); + GetConfigField("EnableFatal"); + GetConfigField("MaxLogFiles"); + GetConfigField("Language"); + GetConfigField("WindowStartState"); + GetConfigField("WindowScale"); + GetConfigField("Opacity"); + GetConfigField("WindowEffect"); +} From cc376edae2b7d8fe4c9d281989c31d92c5ce9a2f Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 27 Jun 2024 18:09:08 +0300 Subject: [PATCH 064/221] update settings --- frontend/public/locales/en-US.json | 56 +++-- frontend/public/locales/tr-TR.json | 54 ++--- .../SettingItems/EnableLoggingSetting.tsx | 47 ++++ .../components/SettingItems/LocaleSetting.tsx | 60 +++++ .../SettingItems/LogLevelSetting.tsx | 154 ++++++++++++ .../SettingItems/MaxLogFilesSetting.tsx | 58 +++++ .../components/SettingItems/ThemeSetting.tsx | 4 +- .../SettingItems/UseSystemTitleBarSetting.tsx | 49 ++++ .../WindowEffectOpacitySetting.tsx} | 120 ++-------- .../SettingItems/WindowScaleSetting.tsx | 59 +++++ .../SettingTabs/AdvancedSettings.tsx | 224 ------------------ .../SettingTabs/GeneralSettings.tsx | 72 ------ frontend/src/components/Settings.tsx | 38 ++- 13 files changed, 531 insertions(+), 464 deletions(-) create mode 100644 frontend/src/components/SettingItems/EnableLoggingSetting.tsx create mode 100644 frontend/src/components/SettingItems/LocaleSetting.tsx create mode 100644 frontend/src/components/SettingItems/LogLevelSetting.tsx create mode 100644 frontend/src/components/SettingItems/MaxLogFilesSetting.tsx create mode 100644 frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx rename frontend/src/components/{SettingTabs/AppSettings.tsx => SettingItems/WindowEffectOpacitySetting.tsx} (56%) create mode 100644 frontend/src/components/SettingItems/WindowScaleSetting.tsx delete mode 100644 frontend/src/components/SettingTabs/AdvancedSettings.tsx delete mode 100644 frontend/src/components/SettingTabs/GeneralSettings.tsx diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index b9e4281..ea2e387 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -8,68 +8,66 @@ "settings": { "restart_the_app_for_changes_to_take_effect": "Restart the app for changes to take effect.", - "general": { - "label": "General", + "categories": { + "general": "General", + "application": "Application", + "system": "System", + "advanced": "Advanced" + }, + "setting": { "language": { "label": "Language", - "description": "Select the language.", + "description": "Select your preferred language.", "select_language": "Select language...", "search_language": "Search language...", "no_languages_found": "No languages found." }, + "theme": { "label": "Theme", - "description": "Select a color scheme for the interface." - } - }, - - "application": { - "label": "Application", + "description": "Choose a color scheme for the interface." + }, "window_effect": { "label": "Window Effect", - "description": "Choose the effect for the window.", - + "description": "Select the window effect.", + "auto": "Auto", "none": "None", "acrylic": "Acrylic", "mica": "Mica", "tabbed": "Tabbed" }, + "window_opacity": { - "label": "Background Opacity", - "description": "Sets the opacity of the window. (This setting will be ignored if the window effect is set to 'None'.)" + "label": "Window Opacity", + "description": "Adjust the window opacity. (This setting is ignored if 'None' is selected for window effect.)" }, "window_scale": { - "label": "Scale", - "description": "Select the window scale." + "label": "Window Scale", + "description": "Adjust the window scale." }, + "use_system_title_bar": { "label": "Use System Title Bar", - "description": "Switch to the default system title bar instead of the custom one." - } - }, - - "system": { - "label": "System" - }, - - "advanced": { - "label": "Advanced", + "description": "Use the default system title bar instead of a custom one." + }, "logging": { "label": "Enable Logging", "description": "Enable logging to files." }, + "log_levels": { - "label": "Selected Log Levels", - "description": "Choose the log levels to be recorded." + "label": "Log Levels", + "description": "Select the log levels to record." }, - "max_log_files":{ + + "max_log_files": { "label": "Max Log Files", - "description": "Set the maximum number of log files to keep." + "description": "Set the maximum number of log files to retain." } } } diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index 800a05e..ece3054 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -8,24 +8,26 @@ "settings": { "restart_the_app_for_changes_to_take_effect": "Değişikliklerin etkili olması için uygulamayı yeniden başlatın.", - "general": { - "label": "Genel", + "categories": { + "general": "Genel", + "application": "Uygulama", + "system": "Sistem", + "advanced": "Gelişmiş" + }, + "setting": { "language": { "label": "Dil", - "description": "Uygulama dilini seçin.", + "description": "Tercih ettiğiniz dili seçin.", "select_language": "Dil seçin...", "search_language": "Dil ara...", - "no_languages_found": "Dil bulunamadı." + "no_languages_found": "Hiçbir dil bulunamadı." }, + "theme": { "label": "Tema", "description": "Arayüz için bir tema seçin." - } - }, - - "application": { - "label": "Uygulama", + }, "window_effect": { "label": "Pencere Efekti", @@ -35,41 +37,37 @@ "none": "Yok", "acrylic": "Akrilik", "mica": "Mika", - "tabbed": "Sekmeli" + "tabbed": "Tabbed" }, + "window_opacity": { "label": "Pencere Opaklığı", - "description": "Pencere opaklığını ayarlayın. (Efekt 'Yok' olarak ayarlandığında yok sayılacaktır.)" + "description": "Pencere opaklığını ayarlayın. (Pencere efekti için 'Yok' seçilmişse bu ayar yok sayılır.)" }, "window_scale": { - "label": "Yakınlaştırma", - "description": "Pencere ölçeğini seçin." + "label": "Pencere Ölçeği", + "description": "Pencere ölçeğini ayarlayın." }, + "use_system_title_bar": { "label": "Sistem Başlık Çubuğunu Kullan", - "description": "Özel başlık çubuğu yerine varsayılan sistem başlık çubuğuna geçin." - } - }, - - "system": { - "label": "Sistem" - }, - - "advanced": { - "label": "Gelişmiş", + "description": "Özel başlık çubuğu yerine varsayılan sistem başlık çubuğunu kullanın." + }, "logging": { - "label": "Günlük Kaydını Etkinleştir", - "description": "Dosyalara günlük kaydını etkinleştirin." + "label": "Günlüğe Kaydetmeyi Etkinleştir", + "description": "Dosyalara günlüğe kaydetmeyi etkinleştir." }, + "log_levels": { - "label": "Seçilen Günlük Seviyeleri", + "label": "Günlük Seviyeleri", "description": "Kaydedilecek günlük seviyelerini seçin." }, + "max_log_files": { - "label": "Maksimum Günlük Dosyası Sayısı", - "description": "Saklanacak maksimum günlük dosyası sayısını ayarlayın." + "label": "Maksimum Günlük Dosyaları", + "description": "Tutulacak maksimum günlük dosyası sayısını ayarlayın." } } } diff --git a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx new file mode 100644 index 0000000..60acfd0 --- /dev/null +++ b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx @@ -0,0 +1,47 @@ +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { Switch } from "../ui/switch"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { GetConfigField, SetConfigField } from "@/lib/config"; + +export function EnableLoggingSetting() { + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(true); + const [enableLogging, setEnableLogging] = useState(false); + + useEffect(() => { + GetConfigField("EnableLogging").then((value) => { + setEnableLogging(value === "true"); + setIsLoading(false); + }); + }, []); + + return ( + +
+ {t("settings.setting.logging.label")} + + {t("settings.setting.logging.description") + + " (" + + t("settings.restart_the_app_for_changes_to_take_effect") + + ")"} + +
+ + { + SetConfigField("EnableLogging", String(!enableLogging)).then(() => { + setEnableLogging(!enableLogging); + }); + }} + /> + +
+ ); +} diff --git a/frontend/src/components/SettingItems/LocaleSetting.tsx b/frontend/src/components/SettingItems/LocaleSetting.tsx new file mode 100644 index 0000000..4e9b318 --- /dev/null +++ b/frontend/src/components/SettingItems/LocaleSetting.tsx @@ -0,0 +1,60 @@ +import { SetConfigField, GetConfigField } from "@/lib/config"; +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Combobox } from "../ui/combobox"; +import locales from "@/locales.json"; + +export function LocaleSetting() { + const { t, i18n } = useTranslation(); + const [isLoading, setIsLoading] = useState(true); + const [language, setLanguage] = useState("en-US"); + + useEffect(() => { + Promise.all([GetConfigField("Language")]) + .then(([value]) => { + setLanguage(value); + + setIsLoading(false); // Mark loading as complete + }) + .catch((error) => { + console.error("Error fetching configuration:", error); + setIsLoading(false); // Handle loading error + }); + }, []); + + return ( + +
+ {t("settings.setting.language.label")} + + {t("settings.setting.language.description")} + +
+ + ({ + value: language.code, + label: language.name, + }))} + placeholder={t("settings.setting.language.select_language")} + searchPlaceholder={t("settings.setting.language.search_language")} + nothingFoundMessage={t( + "settings.setting.language.no_languages_found" + )} + onChange={(value) => { + SetConfigField("Language", value); + i18n.changeLanguage(value); + }} + /> + +
+ ); +} diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx new file mode 100644 index 0000000..cd75699 --- /dev/null +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -0,0 +1,154 @@ +import { Log } from "wailsjs/go/main/App"; +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { GetConfigField, SetConfigField } from "@/lib/config"; + +export function LogLevelSetting() { + const { t } = useTranslation(); + + const [isLoading, setIsLoading] = useState(true); + + const [enableTrace, setEnableTrace] = useState(false); + const [enableDebug, setEnableDebug] = useState(false); + const [enableInfo, setEnableInfo] = useState(false); + const [enableWarn, setEnableWarn] = useState(false); + const [enableError, setEnableError] = useState(false); + const [enableFatal, setEnableFatal] = useState(false); + + useEffect(() => { + Promise.all([ + GetConfigField("EnableTrace"), + GetConfigField("EnableDebug"), + GetConfigField("EnableInfo"), + GetConfigField("EnableWarn"), + GetConfigField("EnableError"), + GetConfigField("EnableFatal"), + ]) + .then( + ([ + enableTrace, + enableDebug, + enableInfo, + enableWarn, + enableError, + enableFatal, + ]) => { + setEnableTrace(enableTrace === "true"); + setEnableDebug(enableDebug === "true"); + setEnableInfo(enableInfo === "true"); + setEnableWarn(enableWarn === "true"); + setEnableError(enableError === "true"); + setEnableFatal(enableFatal === "true"); + + setIsLoading(false); + } + ) + .catch((error) => { + Log("Error while loading advanced settings: " + error, 4); + setIsLoading(false); + }); + }, []); + + return ( + +
+ {t("settings.setting.log_levels.label")} + + {t("settings.setting.log_levels.description")} + +
+ + + { + SetConfigField("EnableTrace", String(!enableTrace)).then(() => { + setEnableTrace(!enableTrace); + }); + }} + > + Trace + + + { + SetConfigField("EnableDebug", String(!enableDebug)).then(() => { + setEnableDebug(!enableDebug); + }); + }} + > + Debug + + + { + SetConfigField("EnableInfo", String(!enableInfo)).then(() => { + setEnableInfo(!enableInfo); + }); + }} + > + Info + + + { + SetConfigField("EnableWarn", String(!enableWarn)).then(() => { + setEnableWarn(!enableWarn); + }); + }} + > + Warn + + + { + SetConfigField("EnableError", String(!enableError)).then(() => { + setEnableError(!enableError); + }); + }} + > + Error + + + { + SetConfigField("EnableFatal", String(!enableFatal)).then(() => { + setEnableFatal(!enableFatal); + }); + }} + > + Fatal + + + +
+ ); +} diff --git a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx new file mode 100644 index 0000000..4c6db3c --- /dev/null +++ b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx @@ -0,0 +1,58 @@ +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Input } from "../ui/input"; +import { GetConfigField, SetConfigField } from "@/lib/config"; + +export function MaxLogFilesSetting() { + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(true); + const [maxLogFiles, setMaxLogFiles] = useState(-1); + + useEffect(() => { + GetConfigField("MaxLogFiles").then((value) => { + setMaxLogFiles(parseInt(value)); + setIsLoading(false); + }); + }, []); + + return ( + +
+ + {t("settings.setting.max_log_files.label")} + + + {t("settings.setting.max_log_files.description") + + " (" + + t("settings.restart_the_app_for_changes_to_take_effect") + + ")"} + +
+ + { + const value = Math.max( + 1, + Math.min(10000, parseInt(e.target.value)) + ); + const targetValue = isNaN(parseInt(e.target.value)) ? 20 : value; + SetConfigField("MaxLogFiles", String(targetValue)).then(() => { + setMaxLogFiles(value); + }); + }} + min={1} + max={10000} + /> + +
+ ); +} diff --git a/frontend/src/components/SettingItems/ThemeSetting.tsx b/frontend/src/components/SettingItems/ThemeSetting.tsx index 1f594d8..8d13f12 100644 --- a/frontend/src/components/SettingItems/ThemeSetting.tsx +++ b/frontend/src/components/SettingItems/ThemeSetting.tsx @@ -16,9 +16,9 @@ export function ThemeSetting() { return (
- {t("settings.general.theme.label")} + {t("settings.setting.theme.label")} - {t("settings.general.theme.description")} + {t("settings.setting.theme.description")}
diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx new file mode 100644 index 0000000..cf74e4b --- /dev/null +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -0,0 +1,49 @@ +import { GetConfigField, SetConfigField } from "@/lib/config"; +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { Switch } from "@/components/ui/switch"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; + +export function UseSystemTitleBarSetting() { + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(true); + const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); + + useEffect(() => { + GetConfigField("UseSystemTitleBar").then((value) => { + setUseSystemTitleBar(value === "true"); + setIsLoading(false); + }); + }, []); + + return ( + +
+ + {t("settings.setting.use_system_title_bar.label")} + + + {t("settings.setting.use_system_title_bar.description") + + " (" + + t("settings.restart_the_app_for_changes_to_take_effect") + + ")"} + +
+ + { + SetConfigField("UseSystemTitleBar", String(value)).then(() => { + setUseSystemTitleBar(value); + }); + }} + /> + +
+ ); +} diff --git a/frontend/src/components/SettingTabs/AppSettings.tsx b/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx similarity index 56% rename from frontend/src/components/SettingTabs/AppSettings.tsx rename to frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx index 1dc6aee..f4f55d5 100644 --- a/frontend/src/components/SettingTabs/AppSettings.tsx +++ b/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx @@ -1,54 +1,36 @@ import { GetConfigField, SetConfigField } from "@/lib/config"; import { - SettingsGroup, SettingsItem, SettingContent, SettingDescription, SettingLabel, } from "../ui/settings-group"; -import { Switch } from "@/components/ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Slider } from "../ui/my-slider"; import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; import { useStorage } from "@/contexts/storage-provider"; -import { ThemeSetting } from "../SettingItems/ThemeSetting"; +import React from "react"; -export function AppSettings() { +export function WindowEffectOpacitySetting() { const { t } = useTranslation(); const { getValue, setValue } = useStorage(); const [isLoading, setIsLoading] = useState(true); - const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); - const [useScale, setUseScale] = useState(-1); const [useOpacity, setUseOpacity] = useState(-1); const [useWindowEffect, setUseWindowEffect] = useState(""); useEffect(() => { - Promise.all([ - GetConfigField("UseSystemTitleBar"), - GetConfigField("WindowScale"), - GetConfigField("Opacity"), - GetConfigField("WindowEffect"), - ]) - .then( - ([ - useSystemTitleBarValue, - windowScaleValue, - windowOpacityValue, - windowEffectValue, - ]) => { - setUseSystemTitleBar(useSystemTitleBarValue === "true"); - setUseScale(parseInt(windowScaleValue)); - setUseOpacity(parseInt(windowOpacityValue)); - setUseWindowEffect(windowEffectValue); - if (getValue("initialWindowEffect") === undefined) { - setValue("initialWindowEffect", windowEffectValue); - } - setIsLoading(false); // Mark loading as complete + Promise.all([GetConfigField("Opacity"), GetConfigField("WindowEffect")]) + .then(([windowOpacityValue, windowEffectValue]) => { + setUseOpacity(parseInt(windowOpacityValue)); + setUseWindowEffect(windowEffectValue); + if (getValue("initialWindowEffect") === undefined) { + setValue("initialWindowEffect", windowEffectValue); } - ) + setIsLoading(false); // Mark loading as complete + }) .catch((error) => { console.error("Error fetching configuration:", error); setIsLoading(false); // Handle loading error @@ -71,16 +53,14 @@ export function AppSettings() { }, [useWindowEffect]); return ( - - - +
- {t("settings.application.window_effect.label")} + {t("settings.setting.window_effect.label")} - {t("settings.application.window_effect.description") + + {t("settings.setting.window_effect.description") + " (" + t("settings.restart_the_app_for_changes_to_take_effect") + ")"} @@ -97,7 +77,7 @@ export function AppSettings() { }); }} > - {t("settings.application.window_effect.none")} + {t("settings.setting.window_effect.none")} - {t("settings.application.window_effect.auto")} + {t("settings.setting.window_effect.auto")} - {t("settings.application.window_effect.mica")} + {t("settings.setting.window_effect.mica")} - {t("settings.application.window_effect.acrylic")} + {t("settings.setting.window_effect.acrylic")} - {t("settings.application.window_effect.tabbed")} + {t("settings.setting.window_effect.tabbed")} @@ -160,10 +140,10 @@ export function AppSettings() { >
- {t("settings.application.window_opacity.label")} + {t("settings.setting.window_opacity.label")} - {t("settings.application.window_opacity.description")} + {t("settings.setting.window_opacity.description")}
@@ -196,64 +176,6 @@ export function AppSettings() {
- - -
- - {t("settings.application.window_scale.label")} - - - {t("settings.application.window_scale.description")} - -
- -
-
50%
- { - setUseScale(value[0]); - }} - onPointerUp={() => { - document.documentElement.style.fontSize = - useScale * (16 / 100) + "px"; - - SetConfigField("WindowScale", String(useScale)); - }} - defaultValue={[useScale]} - min={50} - max={150} - step={10} - className={"w-64 cursor-pointer"} - /> -
150%
-
({useScale}%)
-
-
-
- - -
- - {t("settings.application.use_system_title_bar.label")} - - - {t("settings.application.use_system_title_bar.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} - -
- - { - SetConfigField("UseSystemTitleBar", String(value)).then(() => { - setUseSystemTitleBar(value); - }); - }} - /> - -
- + ); } diff --git a/frontend/src/components/SettingItems/WindowScaleSetting.tsx b/frontend/src/components/SettingItems/WindowScaleSetting.tsx new file mode 100644 index 0000000..b591d1c --- /dev/null +++ b/frontend/src/components/SettingItems/WindowScaleSetting.tsx @@ -0,0 +1,59 @@ +import { GetConfigField, SetConfigField } from "@/lib/config"; +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Slider } from "../ui/my-slider"; + +export function WindowScaleSetting() { + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(true); + const [useScale, setUseScale] = useState(-1); + + useEffect(() => { + GetConfigField("WindowScale").then((value) => { + setUseScale(parseInt(value)); + setIsLoading(false); + }) + }, []); + + return ( + +
+ + {t("settings.setting.window_scale.label")} + + + {t("settings.setting.window_scale.description")} + +
+ +
+
50%
+ { + setUseScale(value[0]); + }} + onPointerUp={() => { + document.documentElement.style.fontSize = + useScale * (16 / 100) + "px"; + + SetConfigField("WindowScale", String(useScale)); + }} + defaultValue={[useScale]} + min={50} + max={150} + step={10} + className={"w-64 cursor-pointer"} + /> +
150%
+
({useScale}%)
+
+
+
+ ); +} diff --git a/frontend/src/components/SettingTabs/AdvancedSettings.tsx b/frontend/src/components/SettingTabs/AdvancedSettings.tsx deleted file mode 100644 index c798cb0..0000000 --- a/frontend/src/components/SettingTabs/AdvancedSettings.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { Log } from "wailsjs/go/main/App"; -import { - SettingsGroup, - SettingsItem, - SettingContent, - SettingDescription, - SettingLabel, -} from "../ui/settings-group"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; -import { Switch } from "../ui/switch"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Input } from "../ui/input"; -import { GetConfigField, SetConfigField } from "@/lib/config"; - -export function AdvancedSettings() { - const { t } = useTranslation(); - - const [isLoading, setIsLoading] = useState(true); - - const [enableLogging, setEnableLogging] = useState(false); - const [enableTrace, setEnableTrace] = useState(false); - const [enableDebug, setEnableDebug] = useState(false); - const [enableInfo, setEnableInfo] = useState(false); - const [enableWarn, setEnableWarn] = useState(false); - const [enableError, setEnableError] = useState(false); - const [enableFatal, setEnableFatal] = useState(false); - const [maxLogFiles, setMaxLogFiles] = useState(-1); - - useEffect(() => { - Promise.all([ - GetConfigField("EnableLogging"), - GetConfigField("EnableTrace"), - GetConfigField("EnableDebug"), - GetConfigField("EnableInfo"), - GetConfigField("EnableWarn"), - GetConfigField("EnableError"), - GetConfigField("EnableFatal"), - GetConfigField("MaxLogFiles"), - ]) - .then( - ([ - enableLogging, - enableTrace, - enableDebug, - enableInfo, - enableWarn, - enableError, - enableFatal, - maxLogFiles, - ]) => { - setEnableLogging(enableLogging === "true"); - setEnableTrace(enableTrace === "true"); - setEnableDebug(enableDebug === "true"); - setEnableInfo(enableInfo === "true"); - setEnableWarn(enableWarn === "true"); - setEnableError(enableError === "true"); - setEnableFatal(enableFatal === "true"); - setMaxLogFiles(parseInt(maxLogFiles)); - - setIsLoading(false); - } - ) - .catch((error) => { - Log("Error while loading advanced settings: " + error, 4); - setIsLoading(false); - }); - }, []); - - return ( - - -
- {t("settings.advanced.logging.label")} - - {t("settings.advanced.logging.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} - -
- - { - SetConfigField("EnableLogging", String(!enableLogging)).then( - () => { - setEnableLogging(!enableLogging); - } - ); - }} - /> - -
- - -
- {t("settings.advanced.log_levels.label")} - - {t("settings.advanced.log_levels.description")} - -
- - - { - SetConfigField("EnableTrace", String(!enableTrace)).then(() => { - setEnableTrace(!enableTrace); - }); - }} - > - Trace - - - { - SetConfigField("EnableDebug", String(!enableDebug)).then(() => { - setEnableDebug(!enableDebug); - }); - }} - > - Debug - - - { - SetConfigField("EnableInfo", String(!enableInfo)).then(() => { - setEnableInfo(!enableInfo); - }); - }} - > - Info - - - { - SetConfigField("EnableWarn", String(!enableWarn)).then(() => { - setEnableWarn(!enableWarn); - }); - }} - > - Warn - - - { - SetConfigField("EnableError", String(!enableError)).then(() => { - setEnableError(!enableError); - }); - }} - > - Error - - - { - SetConfigField("EnableFatal", String(!enableFatal)).then(() => { - setEnableFatal(!enableFatal); - }); - }} - > - Fatal - - - -
- - -
- - {t("settings.advanced.max_log_files.label")} - - - {t("settings.advanced.max_log_files.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} - -
- - { - const value = Math.max( - 1, - Math.min(10000, parseInt(e.target.value)) - ); - const targetValue = isNaN(parseInt(e.target.value)) ? 20 : value; - SetConfigField("MaxLogFiles", String(targetValue)).then(() => { - setMaxLogFiles(value); - }); - }} - min={1} - max={10000} - /> - -
-
- ); -} diff --git a/frontend/src/components/SettingTabs/GeneralSettings.tsx b/frontend/src/components/SettingTabs/GeneralSettings.tsx deleted file mode 100644 index 59e5f9d..0000000 --- a/frontend/src/components/SettingTabs/GeneralSettings.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { SetConfigField, GetConfigField } from "@/lib/config"; -import { - SettingsGroup, - SettingsItem, - SettingContent, - SettingDescription, - SettingLabel, -} from "../ui/settings-group"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Combobox } from "../ui/combobox"; -import locales from "@/locales.json"; -import {ThemeSetting} from "../SettingItems/ThemeSetting"; - -export function GeneralSettings() { - const { t, i18n } = useTranslation(); - - const changeLanguage = (lng: string) => { - i18n.changeLanguage(lng); - }; - - const [isLoading, setIsLoading] = useState(true); - - const [language, setLanguage] = useState("en-US"); - - useEffect(() => { - Promise.all([GetConfigField("Language")]) - .then(([value]) => { - setLanguage(value); - - setIsLoading(false); // Mark loading as complete - }) - .catch((error) => { - console.error("Error fetching configuration:", error); - setIsLoading(false); // Handle loading error - }); - }, []); - - return ( - - -
- {t("settings.general.language.label")} - - {t("settings.general.language.description")} - -
- - ({ - value: language.code, - label: language.name, - }))} - placeholder={t("settings.general.language.select_language")} - searchPlaceholder={t("settings.general.language.search_language")} - nothingFoundMessage={t( - "settings.general.language.no_languages_found" - )} - onChange={(value) => { - SetConfigField("Language", value); - changeLanguage(value); - }} - /> - -
- - -
- ); -} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index a42a5be..50419c8 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -1,8 +1,14 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { AppSettings } from "./SettingTabs/AppSettings"; -import { AdvancedSettings } from "./SettingTabs/AdvancedSettings"; import { useTranslation } from "react-i18next"; -import { GeneralSettings } from "./SettingTabs/GeneralSettings"; +import { LocaleSetting } from "./SettingItems/LocaleSetting"; +import { ThemeSetting } from "./SettingItems/ThemeSetting"; +import { SettingsGroup } from "./ui/settings-group"; +import { UseSystemTitleBarSetting } from "./SettingItems/UseSystemTitleBarSetting"; +import { WindowEffectOpacitySetting } from "./SettingItems/WindowEffectOpacitySetting"; +import { WindowScaleSetting } from "./SettingItems/WindowScaleSetting"; +import { MaxLogFilesSetting } from "./SettingItems/MaxLogFilesSetting"; +import { EnableLoggingSetting } from "./SettingItems/EnableLoggingSetting"; +import { LogLevelSetting } from "./SettingItems/LogLevelSetting"; export default function Settings() { const { t } = useTranslation(); @@ -11,30 +17,42 @@ export default function Settings() { - {t("settings.general.label")} + {t("settings.categories.general")} - {t("settings.application.label")} + {t("settings.categories.application")} - {t("settings.system.label")} + {t("settings.categories.system")} - {t("settings.advanced.label")} + {t("settings.categories.advanced")} - + + + + - + + + + + + Edit your system settings here. - + + + + + ); From f86300f777ed496eb11318c075074cdb76585d47 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 27 Jun 2024 18:17:23 +0300 Subject: [PATCH 065/221] replace runtime functions --- app.go | 41 ------------------- frontend/src/components/ModeToggle.tsx | 2 - .../SettingItems/LogLevelSetting.tsx | 40 ++++++++---------- frontend/src/components/TitleBar.tsx | 13 ++++-- frontend/src/contexts/theme-provider.tsx | 8 ++-- 5 files changed, 31 insertions(+), 73 deletions(-) diff --git a/app.go b/app.go index 8962977..fe1d469 100644 --- a/app.go +++ b/app.go @@ -103,47 +103,6 @@ func onFirstRun() { set_system_language() } -// Log logs a message -func (a *App) Log(msg string, level int) { - switch level { - case 1: - runtime.LogTrace(a.ctx, msg) - case 2: - runtime.LogDebug(a.ctx, msg) - case 3: - runtime.LogInfo(a.ctx, msg) - case 4: - runtime.LogWarning(a.ctx, msg) - case 5: - runtime.LogError(a.ctx, msg) - default: - runtime.LogFatal(a.ctx, msg) - } -} - -// Quits the application -func (a *App) Quit() { - runtime.LogInfo(a.ctx, "Quitting application") - runtime.Quit(a.ctx) -} - -// Maximize the application -func (a *App) Maximize() { - if runtime.WindowIsMaximised(a.ctx) { - runtime.LogInfo(a.ctx, "Unmaximizing window") - runtime.WindowUnmaximise(a.ctx) - } else { - runtime.LogInfo(a.ctx, "Maximizing window") - runtime.WindowMaximise(a.ctx) - } -} - -// Minimize the application -func (a *App) Minimize() { - runtime.LogInfo(a.ctx, "Minimizing window") - runtime.WindowMinimise(a.ctx) -} - // Send notification func (a *App) SendNotification(title string, message string) { runtime.LogInfo(a.ctx, "Sending notification") diff --git a/frontend/src/components/ModeToggle.tsx b/frontend/src/components/ModeToggle.tsx index d452345..4f07345 100644 --- a/frontend/src/components/ModeToggle.tsx +++ b/frontend/src/components/ModeToggle.tsx @@ -1,4 +1,3 @@ -import { Log } from "wailsjs/go/main/App"; import { Moon, Sun } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTheme } from "@/contexts/theme-provider"; @@ -13,7 +12,6 @@ export default function ModeToggle() { onClick={() => { const themeToSet = theme === "light" ? "dark" : "light"; setTheme(themeToSet); - Log("Setted theme to " + themeToSet, 1); }} className="bg-transparent hover:bg-transparent duration-0" > diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx index cd75699..7dbdeb1 100644 --- a/frontend/src/components/SettingItems/LogLevelSetting.tsx +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -1,4 +1,3 @@ -import { Log } from "wailsjs/go/main/App"; import { SettingsItem, SettingContent, @@ -30,30 +29,25 @@ export function LogLevelSetting() { GetConfigField("EnableWarn"), GetConfigField("EnableError"), GetConfigField("EnableFatal"), - ]) - .then( - ([ - enableTrace, - enableDebug, - enableInfo, - enableWarn, - enableError, - enableFatal, - ]) => { - setEnableTrace(enableTrace === "true"); - setEnableDebug(enableDebug === "true"); - setEnableInfo(enableInfo === "true"); - setEnableWarn(enableWarn === "true"); - setEnableError(enableError === "true"); - setEnableFatal(enableFatal === "true"); + ]).then( + ([ + enableTrace, + enableDebug, + enableInfo, + enableWarn, + enableError, + enableFatal, + ]) => { + setEnableTrace(enableTrace === "true"); + setEnableDebug(enableDebug === "true"); + setEnableInfo(enableInfo === "true"); + setEnableWarn(enableWarn === "true"); + setEnableError(enableError === "true"); + setEnableFatal(enableFatal === "true"); - setIsLoading(false); - } - ) - .catch((error) => { - Log("Error while loading advanced settings: " + error, 4); setIsLoading(false); - }); + } + ); }, []); return ( diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 96adae7..508dca5 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -1,4 +1,9 @@ -import { Minimize, Maximize, Quit, GetConfigField } from "wailsjs/go/main/App"; +import { GetConfigField } from "wailsjs/go/main/App"; +import { + WindowMinimise, + WindowToggleMaximise, + Quit, +} from "wailsjs/runtime/runtime"; import { Minus, Copy, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import icon from "../assets/appicon.png"; @@ -17,7 +22,7 @@ export default function TitleBar() { !useSystemTitleBar && (
Maximize()} + onDoubleClick={() => WindowToggleMaximise()} >

@@ -26,7 +31,7 @@ export default function TitleBar() {
+ + ) : undefined, + variant: variant, }); }; diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx new file mode 100644 index 0000000..3f9c5d8 --- /dev/null +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -0,0 +1,66 @@ +import { + GetLoadConfigPath, + ReadConfig, + SaveConfigDialog, +} from "wailsjs/go/main/App"; +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { Button } from "../ui/button"; +import { AreYouSureDialog, AreYouSureDialogRef } from "../ui/are-you-sure"; +import { useRef, useState } from "react"; +import { LogDebug, WindowReload } from "wailsjs/runtime/runtime"; +import { InitConfigCache } from "@/lib/config"; + +export function ImportExportSetting() { + const dialogRef = useRef(null); + const [usePath, setUsePath] = useState(""); + + return ( + +
+ Import/Export Settings + + Import or export your settings from/to a JSON file + +
+ +
+ + { + LogDebug("Attempting to read config from " + usePath); + ReadConfig(usePath).then(() => { + InitConfigCache().then(() => { + WindowReload(); + }); + }); + }} + > + gdfgdfgdfg + + +
+
+
+ ); +} diff --git a/frontend/src/components/SettingItems/LocaleSetting.tsx b/frontend/src/components/SettingItems/LocaleSetting.tsx index 4e9b318..5d0210d 100644 --- a/frontend/src/components/SettingItems/LocaleSetting.tsx +++ b/frontend/src/components/SettingItems/LocaleSetting.tsx @@ -28,6 +28,10 @@ export function LocaleSetting() { }); }, []); + useEffect(() => { + i18n.changeLanguage(language); + }, [language]); + return (
@@ -50,8 +54,9 @@ export function LocaleSetting() { "settings.setting.language.no_languages_found" )} onChange={(value) => { - SetConfigField("Language", value); - i18n.changeLanguage(value); + SetConfigField("Language", value).then(() => { + setLanguage(value); + }); }} /> diff --git a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx index 4c6db3c..5961bdd 100644 --- a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx +++ b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx @@ -24,9 +24,7 @@ export function MaxLogFilesSetting() { return (
- - {t("settings.setting.max_log_files.label")} - + {t("settings.setting.max_log_files.label")} {t("settings.setting.max_log_files.description") + " (" + diff --git a/frontend/src/components/SettingItems/WindowScaleSetting.tsx b/frontend/src/components/SettingItems/WindowScaleSetting.tsx index b591d1c..bbf8131 100644 --- a/frontend/src/components/SettingItems/WindowScaleSetting.tsx +++ b/frontend/src/components/SettingItems/WindowScaleSetting.tsx @@ -18,15 +18,13 @@ export function WindowScaleSetting() { GetConfigField("WindowScale").then((value) => { setUseScale(parseInt(value)); setIsLoading(false); - }) + }); }, []); return (
- - {t("settings.setting.window_scale.label")} - + {t("settings.setting.window_scale.label")} {t("settings.setting.window_scale.description")} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 50419c8..f4781ec 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -9,6 +9,7 @@ import { WindowScaleSetting } from "./SettingItems/WindowScaleSetting"; import { MaxLogFilesSetting } from "./SettingItems/MaxLogFilesSetting"; import { EnableLoggingSetting } from "./SettingItems/EnableLoggingSetting"; import { LogLevelSetting } from "./SettingItems/LogLevelSetting"; +import { ImportExportSetting } from "./SettingItems/ImportExportSetting"; export default function Settings() { const { t } = useTranslation(); @@ -52,6 +53,7 @@ export default function Settings() { + diff --git a/frontend/src/components/ui/are-you-sure.tsx b/frontend/src/components/ui/are-you-sure.tsx new file mode 100644 index 0000000..fb5085e --- /dev/null +++ b/frontend/src/components/ui/are-you-sure.tsx @@ -0,0 +1,88 @@ +import { useState, forwardRef, useImperativeHandle, ReactNode } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; + +interface AreYouSureDialogProps { + title: string; + description: string; + cancelText: string; + acceptText: string; + onCancel?: () => void; + onAccept?: () => void; + children: ReactNode; +} + +export interface AreYouSureDialogRef { + openDialog: () => void; + closeDialog: () => void; +} + +export const AreYouSureDialog = forwardRef( + ({ title, description, cancelText, acceptText, onCancel, onAccept, children }, ref) => { + const [isOpen, setIsOpen] = useState(false); + + const openDialog = () => setIsOpen(true); + const closeDialog = () => setIsOpen(false); + + useImperativeHandle(ref, () => ({ + openDialog, + closeDialog, + })); + + return ( + + + + + + + {title} + {description} + +
+ {children} +
+ + + + + + +
+
+ ); + } +); + +AreYouSureDialog.displayName = "AreYouSureDialog"; diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index 9e093c1..542c1a8 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -39,20 +39,40 @@ export async function SetConfigField( } // Function to initialize config cache -export function InitConfigCache(): void { - GetConfigField("Theme"); - GetConfigField("UseSystemTitleBar"); - GetConfigField("EnableLogging"); - GetConfigField("EnableTrace"); - GetConfigField("EnableDebug"); - GetConfigField("EnableInfo"); - GetConfigField("EnableWarn"); - GetConfigField("EnableError"); - GetConfigField("EnableFatal"); - GetConfigField("MaxLogFiles"); - GetConfigField("Language"); - GetConfigField("WindowStartState"); - GetConfigField("WindowScale"); - GetConfigField("Opacity"); - GetConfigField("WindowEffect"); +// Function to initialize config cache +export async function InitConfigCache(): Promise { + // Reset cache + configCache = {}; + + // Array of keys to fetch + const keys = [ + "Theme", + "UseSystemTitleBar", + "EnableLogging", + "EnableTrace", + "EnableDebug", + "EnableInfo", + "EnableWarn", + "EnableError", + "EnableFatal", + "MaxLogFiles", + "Language", + "WindowStartState", + "WindowScale", + "Opacity", + "WindowEffect", + ]; + + // Array to store promises + const fetchPromises: Promise[] = keys.map((key) => GetConfigField(key)); + + // Wait for all promises to resolve + const results = await Promise.all(fetchPromises); + + // Update cache with fetched values + keys.forEach((key, index) => { + configCache[key] = results[index]; + }); + + // Optional: Log or perform additional actions after fetching all values } diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index a027233..afd7703 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -3,7 +3,15 @@ export function GetConfigField(arg1:string):Promise; -export function SendNotification(arg1:string,arg2:string):Promise; +export function GetLoadConfigPath():Promise; + +export function OpenFileInExplorer(arg1:string):Promise; + +export function ReadConfig(arg1:string):Promise; + +export function SaveConfigDialog():Promise; + +export function SendNotification(arg1:string,arg2:string,arg3:string,arg4:string):Promise; export function SetConfigField(arg1:string,arg2:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 1ca93f1..5722039 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -6,8 +6,24 @@ export function GetConfigField(arg1) { return window['go']['main']['App']['GetConfigField'](arg1); } -export function SendNotification(arg1, arg2) { - return window['go']['main']['App']['SendNotification'](arg1, arg2); +export function GetLoadConfigPath() { + return window['go']['main']['App']['GetLoadConfigPath'](); +} + +export function OpenFileInExplorer(arg1) { + return window['go']['main']['App']['OpenFileInExplorer'](arg1); +} + +export function ReadConfig(arg1) { + return window['go']['main']['App']['ReadConfig'](arg1); +} + +export function SaveConfigDialog() { + return window['go']['main']['App']['SaveConfigDialog'](); +} + +export function SendNotification(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['SendNotification'](arg1, arg2, arg3, arg4); } export function SetConfigField(arg1, arg2) { From 5ead2ac95ef46e78e6d7d9fc7947349125349bcb Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 28 Jun 2024 17:47:07 +0300 Subject: [PATCH 068/221] add translation --- dialog.go | 8 ++++-- frontend/public/locales/en-US.json | 15 +++++++++++ frontend/public/locales/tr-TR.json | 15 +++++++++++ frontend/src/App.tsx | 8 +++--- .../SettingItems/ImportExportSetting.tsx | 27 +++++++++---------- frontend/src/components/ui/are-you-sure.tsx | 10 +++---- 6 files changed, 58 insertions(+), 25 deletions(-) diff --git a/dialog.go b/dialog.go index 01cf0f6..755e91b 100644 --- a/dialog.go +++ b/dialog.go @@ -29,14 +29,18 @@ func (a *App) SaveConfigDialog() { err = WriteConfig(path) if err != nil { + if path == "" { + runtime.LogInfo(a.ctx, "No path given, not saving config") + return + } runtime.LogWarning(a.ctx, err.Error()) - a.SendNotification("", "There was an error saving the config", "", "destructive") + a.SendNotification("", "settings.there_was_an_error_saving_the_config", "", "destructive") return } runtime.LogInfo(a.ctx, "Config saved to "+path) path = strings.ReplaceAll(path, "\\", "\\\\") - a.SendNotification("", "Config saved", path, "") + a.SendNotification("", "settings.config_saved", path, "") } func (a *App) GetLoadConfigPath() string { diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index ea2e387..29883c5 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -1,4 +1,11 @@ { + "cancel": "Cancel", + "yes": "Yes", + "no": "No", + "show_in_explorer": "Show in Explorer", + "import": "Import", + "export": "Export", + "nav": { "my_packs": "My Packs", "edit": "Edit", @@ -7,6 +14,9 @@ "settings": { "restart_the_app_for_changes_to_take_effect": "Restart the app for changes to take effect.", + "are_you_sure_you_want_to_import_this_config": "Are you sure you want to import this config?", + "config_saved": "Config saved", + "there_was_an_error_saving_the_config": "There was an error saving the config.", "categories": { "general": "General", @@ -68,6 +78,11 @@ "max_log_files": { "label": "Max Log Files", "description": "Set the maximum number of log files to retain." + }, + + "import_export": { + "label": "Import/Export Settings", + "description": "Import or export settings your settings from/to a JSON file." } } } diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index ece3054..3969c0d 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -1,4 +1,11 @@ { + "cancel": "İptal", + "yes": "Evet", + "no": "Hayır", + "show_in_explorer": "Klasörü aç", + "import": "İçe Aktar", + "export": "Dışa Aktar", + "nav": { "my_packs": "Paketlerim", "edit": "Düzenle", @@ -7,6 +14,9 @@ "settings": { "restart_the_app_for_changes_to_take_effect": "Değişikliklerin etkili olması için uygulamayı yeniden başlatın.", + "are_you_sure_you_want_to_import_this_config": "Bu ayarları yüklemek istediğinize emin misiniz?", + "config_saved": "Ayarlar kaydedildi", + "there_was_an_error_saving_the_config": "Ayarlar kaydedilirken bir hata oluştu.", "categories": { "general": "Genel", @@ -68,6 +78,11 @@ "max_log_files": { "label": "Maksimum Günlük Dosyaları", "description": "Tutulacak maksimum günlük dosyası sayısını ayarlayın." + }, + + "import_export": { + "label": "Ayarları İçe/Dışa Aktar", + "description": "Ayarlarınızı bir JSON dosyasından içe veya dışa aktarın." } } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4a440c5..037154a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -40,12 +40,12 @@ function App() { window.toast = ({ title, description, path, variant }: any) => { toast({ - title: title, - description: description, + title: t(title), + description: t(description), action: path ? ( - + ) : undefined, diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index 3f9c5d8..0a4c25c 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -12,23 +12,25 @@ import { import { Button } from "../ui/button"; import { AreYouSureDialog, AreYouSureDialogRef } from "../ui/are-you-sure"; import { useRef, useState } from "react"; -import { LogDebug, WindowReload } from "wailsjs/runtime/runtime"; +import { LogDebug, WindowReloadApp } from "wailsjs/runtime/runtime"; import { InitConfigCache } from "@/lib/config"; +import { useTranslation } from "react-i18next"; export function ImportExportSetting() { + const { t } = useTranslation(); const dialogRef = useRef(null); const [usePath, setUsePath] = useState(""); return (
- Import/Export Settings + {t("settings.setting.import_export.label")} - Import or export your settings from/to a JSON file + {t("settings.setting.import_export.description")}
-
+
{ LogDebug("Attempting to read config from " + usePath); ReadConfig(usePath).then(() => { InitConfigCache().then(() => { - WindowReload(); + WindowReloadApp(); }); }); }} - > - gdfgdfgdfg - - + /> +
diff --git a/frontend/src/components/ui/are-you-sure.tsx b/frontend/src/components/ui/are-you-sure.tsx index fb5085e..1edde9c 100644 --- a/frontend/src/components/ui/are-you-sure.tsx +++ b/frontend/src/components/ui/are-you-sure.tsx @@ -12,13 +12,13 @@ import { } from "@/components/ui/dialog"; interface AreYouSureDialogProps { - title: string; - description: string; + title?: string; + description?: string; cancelText: string; acceptText: string; onCancel?: () => void; onAccept?: () => void; - children: ReactNode; + children?: ReactNode; } export interface AreYouSureDialogRef { @@ -51,11 +51,11 @@ export const AreYouSureDialog = forwardRef {children}
- + + diff --git a/frontend/src/contexts/restart-provider.tsx b/frontend/src/contexts/restart-provider.tsx new file mode 100644 index 0000000..6b2af3b --- /dev/null +++ b/frontend/src/contexts/restart-provider.tsx @@ -0,0 +1,61 @@ +import React, { createContext, useContext, useState, ReactNode } from "react"; + +// Define types for context values +interface RestartContextType { + restartRequired: boolean; + addRestartRequired: (item: string) => void; + removeRestartRequired: (item: string) => void; +} + +// Create context with initial empty values +const RestartContext = createContext({ + restartRequired: false, + addRestartRequired: () => {}, + removeRestartRequired: () => {}, +}); + +// Custom hook to use RestartContext +export function useRestart(): RestartContextType { + return useContext(RestartContext); +} + +// RestartProvider component +export const RestartProvider: React.FC<{ children: ReactNode }> = ({ + children, +}) => { + const [_, setRestartRequiredArray] = useState([]); + const [restartRequired, setRestartRequired] = useState(false); + + // Function to add a restart requirement + const addRestartRequired = (item: string) => { + setRestartRequiredArray((prevArray) => { + if (!prevArray.includes(item)) { + const newArray = [...prevArray, item]; + setRestartRequired(newArray.length > 0); + return newArray; + } + return prevArray; + }); + }; + + // Function to remove a restart requirement + const removeRestartRequired = (item: string) => { + setRestartRequiredArray((prevArray) => { + const newArray = prevArray.filter((i) => i !== item); + setRestartRequired(newArray.length > 0); + return newArray; + }); + }; + + return ( + + {children} + + ); +}; diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 16b0ca5..646a05f 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -15,6 +15,7 @@ const initializeI18n = async () => { .use(LanguageDetector) .use(initReactI18next) .init({ + load: "currentOnly", lng: language, supportedLngs: supportedLngs, // Add supported languages here fallbackLng: "en-US", From 8717c8651121e07a0b1c186cf2f7d33036032b02 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sat, 29 Jun 2024 17:34:02 +0300 Subject: [PATCH 073/221] move useRestart inside settingsItem --- .../SettingItems/EnableLoggingSetting.tsx | 9 +----- .../SettingItems/LogLevelSetting.tsx | 31 ++++++------------- .../SettingItems/MaxLogFilesSetting.tsx | 14 ++++----- .../SettingItems/UseSystemTitleBarSetting.tsx | 9 +----- .../WindowEffectOpacitySetting.tsx | 15 ++++----- frontend/src/components/ui/settings-group.tsx | 20 ++++++++++-- frontend/src/contexts/storage-provider.tsx | 8 +++-- 7 files changed, 46 insertions(+), 60 deletions(-) diff --git a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx index 6deedc9..26fd513 100644 --- a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx +++ b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx @@ -9,12 +9,10 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { GetConfigField, SetConfigField } from "@/lib/config"; import { useStorage } from "@/contexts/storage-provider"; -import { useRestart } from "@/contexts/restart-provider"; export function EnableLoggingSetting() { const { t } = useTranslation(); const { getValue, setValue } = useStorage(); - const { addRestartRequired, removeRestartRequired } = useRestart(); const [isLoading, setIsLoading] = useState(true); const [enableLogging, setEnableLogging] = useState(false); @@ -30,7 +28,7 @@ export function EnableLoggingSetting() { }, []); return ( - +
{t("settings.setting.logging.label")} @@ -46,11 +44,6 @@ export function EnableLoggingSetting() { onCheckedChange={() => { SetConfigField("EnableLogging", String(!enableLogging)).then(() => { setEnableLogging(!enableLogging); - if (String(enableLogging) === getValue("initialEnableLogging")) { - addRestartRequired("EnableLogging"); - } else { - removeRestartRequired("EnableLogging"); - } }); }} /> diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx index 67dd50a..04b7022 100644 --- a/frontend/src/components/SettingItems/LogLevelSetting.tsx +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -9,12 +9,10 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { GetConfigField, SetConfigField } from "@/lib/config"; import { useStorage } from "@/contexts/storage-provider"; -import { useRestart } from "@/contexts/restart-provider"; export function LogLevelSetting() { const { t } = useTranslation(); const { getValue, setValue } = useStorage(); - const { addRestartRequired, removeRestartRequired } = useRestart(); const [isLoading, setIsLoading] = useState(true); @@ -66,31 +64,20 @@ export function LogLevelSetting() { ); }, []); - useEffect(() => { - if ( - getValue("initialEnableLogLevel") === - String(enableTrace) + + return ( + + } + >
{t("settings.setting.log_levels.label")} diff --git a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx index e715c59..1de0823 100644 --- a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx +++ b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx @@ -9,12 +9,10 @@ import { useTranslation } from "react-i18next"; import { Input } from "../ui/input"; import { GetConfigField, SetConfigField } from "@/lib/config"; import { useStorage } from "@/contexts/storage-provider"; -import { useRestart } from "@/contexts/restart-provider"; export function MaxLogFilesSetting() { const { t } = useTranslation(); const { getValue, setValue } = useStorage(); - const { addRestartRequired, removeRestartRequired } = useRestart(); const [isLoading, setIsLoading] = useState(true); const [maxLogFiles, setMaxLogFiles] = useState(-1); @@ -30,7 +28,12 @@ export function MaxLogFilesSetting() { }, []); return ( - +
{t("settings.setting.max_log_files.label")} @@ -53,11 +56,6 @@ export function MaxLogFilesSetting() { const targetValue = isNaN(parseInt(e.target.value)) ? 20 : value; SetConfigField("MaxLogFiles", String(targetValue)).then(() => { setMaxLogFiles(value); - if (String(targetValue) === getValue("initialMaxLogFiles")) { - removeRestartRequired("MaxLogFiles"); - } else { - addRestartRequired("MaxLogFiles"); - } }); }} min={1} diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx index 10d2464..f5f61fb 100644 --- a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -9,12 +9,10 @@ import { Switch } from "@/components/ui/switch"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useStorage } from "@/contexts/storage-provider"; -import { useRestart } from "@/contexts/restart-provider"; export function UseSystemTitleBarSetting() { const { t } = useTranslation(); const { getValue, setValue } = useStorage(); - const { addRestartRequired, removeRestartRequired } = useRestart(); const [isLoading, setIsLoading] = useState(true); const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); @@ -30,7 +28,7 @@ export function UseSystemTitleBarSetting() { }, []); return ( - +
{t("settings.setting.use_system_title_bar.label")} @@ -48,11 +46,6 @@ export function UseSystemTitleBarSetting() { onCheckedChange={(value) => { SetConfigField("UseSystemTitleBar", String(value)).then(() => { setUseSystemTitleBar(value); - if (String(value) === getValue("initialUseSystemTitleBar")) { - removeRestartRequired("UseSystemTitleBar"); - } else { - addRestartRequired("UseSystemTitleBar"); - } }); }} /> diff --git a/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx b/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx index 7d72687..d58ba0e 100644 --- a/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx +++ b/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx @@ -11,12 +11,10 @@ import { Slider } from "../ui/my-slider"; import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; import { useStorage } from "@/contexts/storage-provider"; import React from "react"; -import { useRestart } from "@/contexts/restart-provider"; export function WindowEffectOpacitySetting() { const { t } = useTranslation(); const { getValue, setValue } = useStorage(); - const { addRestartRequired, removeRestartRequired } = useRestart(); const [isLoading, setIsLoading] = useState(true); @@ -51,18 +49,17 @@ export function WindowEffectOpacitySetting() { ) / 100 ) ); - - if (useWindowEffect === getValue("initialWindowEffect")) { - removeRestartRequired("WindowEffect"); - } else { - addRestartRequired("WindowEffect"); - } } }, [useWindowEffect]); return ( - +
{t("settings.setting.window_effect.label")} diff --git a/frontend/src/components/ui/settings-group.tsx b/frontend/src/components/ui/settings-group.tsx index 41d803e..bd5c446 100644 --- a/frontend/src/components/ui/settings-group.tsx +++ b/frontend/src/components/ui/settings-group.tsx @@ -1,5 +1,6 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode, useEffect } from "react"; import { Skeleton } from "@/components/ui/skeleton" +import { useRestart } from "@/contexts/restart-provider"; interface SettingsComponentProps extends React.HTMLAttributes { children: ReactNode; @@ -14,9 +15,24 @@ interface SettingsItemProps extends React.HTMLAttributes { vertical?: boolean; disabled?: boolean; children: ReactNode; + initialValue?: any; + value?: any; + name?: string; } -export const SettingsItem: React.FC = ({ children, className, disabled, loading, vertical, ...rest }) => { +export const SettingsItem: React.FC = ({ children, className, disabled, loading, vertical, initialValue, name, value, ...rest }) => { + const { addRestartRequired, removeRestartRequired } = useRestart(); + + useEffect(() => { + if(initialValue && value && name && !loading){ + if(value === initialValue){ + removeRestartRequired(name); + }else{ + addRestartRequired(name); + } + } + }, [initialValue, value]); + if (loading) { return } else { diff --git a/frontend/src/contexts/storage-provider.tsx b/frontend/src/contexts/storage-provider.tsx index 6bae6f6..3e0ad91 100644 --- a/frontend/src/contexts/storage-provider.tsx +++ b/frontend/src/contexts/storage-provider.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useState, ReactNode } from 'react'; +import React, { createContext, useContext, useState, ReactNode } from "react"; interface StorageContextType { setValue: (key: string, value: any) => void; @@ -11,7 +11,9 @@ interface StorageProviderProps { const StorageContext = createContext(undefined); -export const StorageProvider: React.FC = ({ children }) => { +export const StorageProvider: React.FC = ({ + children, +}) => { const [storage, setStorage] = useState<{ [key: string]: any }>({}); const setValue = (key: string, value: any) => { @@ -40,7 +42,7 @@ export const StorageProvider: React.FC = ({ children }) => export const useStorage = () => { const context = useContext(StorageContext); if (!context) { - throw new Error('useStorage must be used within a StorageProvider'); + throw new Error("useStorage must be used within a StorageProvider"); } return context; }; From 903590532f5a437bf1cd18b29a3e9087a1bdb84a Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sat, 29 Jun 2024 18:03:27 +0300 Subject: [PATCH 074/221] update storage --- frontend/public/locales/en-US.json | 1 + frontend/public/locales/tr-TR.json | 1 + .../SettingItems/EnableLoggingSetting.tsx | 14 +++++++----- .../SettingItems/ImportExportSetting.tsx | 1 + .../SettingItems/LogLevelSetting.tsx | 22 +++++++++---------- .../SettingItems/MaxLogFilesSetting.tsx | 6 ++--- .../SettingItems/UseSystemTitleBarSetting.tsx | 13 ++++++----- .../WindowEffectOpacitySetting.tsx | 6 ++--- frontend/src/contexts/storage-provider.tsx | 8 +++++++ 9 files changed, 42 insertions(+), 30 deletions(-) diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index 29883c5..835b568 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -15,6 +15,7 @@ "settings": { "restart_the_app_for_changes_to_take_effect": "Restart the app for changes to take effect.", "are_you_sure_you_want_to_import_this_config": "Are you sure you want to import this config?", + "the_app_will_restart_to_load_the_new_config": "The app will be restarted to load the new config.", "config_saved": "Config saved", "there_was_an_error_saving_the_config": "There was an error saving the config.", diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index 3969c0d..7422fb6 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -15,6 +15,7 @@ "settings": { "restart_the_app_for_changes_to_take_effect": "Değişikliklerin etkili olması için uygulamayı yeniden başlatın.", "are_you_sure_you_want_to_import_this_config": "Bu ayarları yüklemek istediğinize emin misiniz?", + "the_app_will_restart_to_load_the_new_config": "Uygulama, yeni ayarları yüklemek için yeniden başlatılacak.", "config_saved": "Ayarlar kaydedildi", "there_was_an_error_saving_the_config": "Ayarlar kaydedilirken bir hata oluştu.", diff --git a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx index 26fd513..1d176d6 100644 --- a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx +++ b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx @@ -12,7 +12,7 @@ import { useStorage } from "@/contexts/storage-provider"; export function EnableLoggingSetting() { const { t } = useTranslation(); - const { getValue, setValue } = useStorage(); + const { getValue, setValueIfUndefined } = useStorage(); const [isLoading, setIsLoading] = useState(true); const [enableLogging, setEnableLogging] = useState(false); @@ -20,15 +20,19 @@ export function EnableLoggingSetting() { useEffect(() => { GetConfigField("EnableLogging").then((value) => { setEnableLogging(value === "true"); - if (getValue("initialEnableLogging") === undefined) { - setValue("initialEnableLogging", value); - } + setValueIfUndefined("initialEnableLogging", value); + setIsLoading(false); }); }, []); return ( - +
{t("settings.setting.logging.label")} diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index 84d79f6..ccbc6ba 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -47,6 +47,7 @@ export function ImportExportSetting() { { diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx index 04b7022..34f018d 100644 --- a/frontend/src/components/SettingItems/LogLevelSetting.tsx +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -12,7 +12,7 @@ import { useStorage } from "@/contexts/storage-provider"; export function LogLevelSetting() { const { t } = useTranslation(); - const { getValue, setValue } = useStorage(); + const { getValue, setValueIfUndefined } = useStorage(); const [isLoading, setIsLoading] = useState(true); @@ -47,17 +47,15 @@ export function LogLevelSetting() { setEnableError(enableError === "true"); setEnableFatal(enableFatal === "true"); - if (getValue("initialEnableLogLevel") === undefined) { - setValue( - "initialEnableLogLevel", - enableTrace + - enableDebug + - enableInfo + - enableWarn + - enableError + - enableFatal - ); - } + setValueIfUndefined( + "initialEnableLogLevel", + enableTrace + + enableDebug + + enableInfo + + enableWarn + + enableError + + enableFatal + ); setIsLoading(false); } diff --git a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx index 1de0823..cd26760 100644 --- a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx +++ b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx @@ -12,7 +12,7 @@ import { useStorage } from "@/contexts/storage-provider"; export function MaxLogFilesSetting() { const { t } = useTranslation(); - const { getValue, setValue } = useStorage(); + const { getValue, setValueIfUndefined } = useStorage(); const [isLoading, setIsLoading] = useState(true); const [maxLogFiles, setMaxLogFiles] = useState(-1); @@ -20,9 +20,7 @@ export function MaxLogFilesSetting() { useEffect(() => { GetConfigField("MaxLogFiles").then((value) => { setMaxLogFiles(parseInt(value)); - if (getValue("initialMaxLogFiles") === undefined) { - setValue("initialMaxLogFiles", value); - } + setValueIfUndefined("initialMaxLogFiles", value); setIsLoading(false); }); }, []); diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx index f5f61fb..c9b0565 100644 --- a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -12,7 +12,7 @@ import { useStorage } from "@/contexts/storage-provider"; export function UseSystemTitleBarSetting() { const { t } = useTranslation(); - const { getValue, setValue } = useStorage(); + const { getValue, setValueIfUndefined } = useStorage(); const [isLoading, setIsLoading] = useState(true); const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); @@ -20,15 +20,18 @@ export function UseSystemTitleBarSetting() { useEffect(() => { GetConfigField("UseSystemTitleBar").then((value) => { setUseSystemTitleBar(value === "true"); - if (getValue("initialUseSystemTitleBar") === undefined) { - setValue("initialUseSystemTitleBar", value); - } + setValueIfUndefined("initialUseSystemTitleBar", value); setIsLoading(false); }); }, []); return ( - +
{t("settings.setting.use_system_title_bar.label")} diff --git a/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx b/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx index d58ba0e..a23276c 100644 --- a/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx +++ b/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx @@ -14,7 +14,7 @@ import React from "react"; export function WindowEffectOpacitySetting() { const { t } = useTranslation(); - const { getValue, setValue } = useStorage(); + const { getValue, setValueIfUndefined } = useStorage(); const [isLoading, setIsLoading] = useState(true); @@ -26,9 +26,7 @@ export function WindowEffectOpacitySetting() { .then(([windowOpacityValue, windowEffectValue]) => { setUseOpacity(parseInt(windowOpacityValue)); setUseWindowEffect(windowEffectValue); - if (getValue("initialWindowEffect") === undefined) { - setValue("initialWindowEffect", windowEffectValue); - } + setValueIfUndefined("initialWindowEffect", windowEffectValue); setIsLoading(false); // Mark loading as complete }) .catch((error) => { diff --git a/frontend/src/contexts/storage-provider.tsx b/frontend/src/contexts/storage-provider.tsx index 3e0ad91..949d379 100644 --- a/frontend/src/contexts/storage-provider.tsx +++ b/frontend/src/contexts/storage-provider.tsx @@ -3,6 +3,7 @@ import React, { createContext, useContext, useState, ReactNode } from "react"; interface StorageContextType { setValue: (key: string, value: any) => void; getValue: (key: string) => any; + setValueIfUndefined: (key: string, value: any) => void; } interface StorageProviderProps { @@ -27,9 +28,16 @@ export const StorageProvider: React.FC = ({ return storage[key]; }; + const setValueIfUndefined = (key: string, value: any) => { + if (storage[key] === undefined) { + setValue(key, value); + } + }; + const contextValue: StorageContextType = { setValue, getValue, + setValueIfUndefined, }; return ( From 70fb331cf91b2034bf3c6096c65a6c58731ad116 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 30 Jun 2024 18:11:56 +0300 Subject: [PATCH 075/221] refactor setting items --- .../SettingItems/EnableLoggingSetting.tsx | 12 +- .../SettingItems/ImportExportSetting.tsx | 44 ++-- .../components/SettingItems/LocaleSetting.tsx | 29 +-- .../SettingItems/LogLevelSetting.tsx | 196 ++++++------------ .../SettingItems/MaxLogFilesSetting.tsx | 20 +- .../SettingItems/UseSystemTitleBarSetting.tsx | 12 +- .../WindowEffectOpacitySetting.tsx | 184 ---------------- .../SettingItems/WindowEffectSetting.tsx | 72 +++++++ .../SettingItems/WindowOpacitySetting.tsx | 75 +++++++ .../SettingItems/WindowScaleSetting.tsx | 17 +- frontend/src/components/Settings.tsx | 6 +- frontend/tailwind.config.js | 22 +- 12 files changed, 300 insertions(+), 389 deletions(-) delete mode 100644 frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx create mode 100644 frontend/src/components/SettingItems/WindowEffectSetting.tsx create mode 100644 frontend/src/components/SettingItems/WindowOpacitySetting.tsx diff --git a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx index 1d176d6..dfdb5cd 100644 --- a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx +++ b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx @@ -26,6 +26,12 @@ export function EnableLoggingSetting() { }); }, []); + const handleEnableLoggingChange = (value: boolean) => { + SetConfigField("EnableLogging", String(value)).then(() => { + setEnableLogging(value); + }); + }; + return ( { - SetConfigField("EnableLogging", String(!enableLogging)).then(() => { - setEnableLogging(!enableLogging); - }); - }} + onCheckedChange={() => handleEnableLoggingChange(!enableLogging)} /> diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index ccbc6ba..ed88075 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -14,7 +14,6 @@ import { Button } from "../ui/button"; import { AreYouSureDialog, AreYouSureDialogRef } from "../ui/are-you-sure"; import { useRef, useState } from "react"; import { LogDebug } from "wailsjs/runtime/runtime"; -import { InitConfigCache } from "@/lib/config"; import { useTranslation } from "react-i18next"; export function ImportExportSetting() { @@ -22,6 +21,20 @@ export function ImportExportSetting() { const dialogRef = useRef(null); const [usePath, setUsePath] = useState(""); + const handleImportButtonClick = () => { + GetLoadConfigPath().then((path) => { + if (path !== "") { + setUsePath(path); + dialogRef.current?.openDialog(); + } + }); + }; + + const handleAcceptImport = () => { + LogDebug("Attempting to read config from " + usePath); + ReadConfig(usePath).then(RestartApplication); + }; + return (
@@ -32,34 +45,19 @@ export function ImportExportSetting() {
- + { - LogDebug("Attempting to read config from " + usePath); - ReadConfig(usePath).then(() => { - InitConfigCache().then(() => { - RestartApplication(); - }); - }); - }} /> - + +
diff --git a/frontend/src/components/SettingItems/LocaleSetting.tsx b/frontend/src/components/SettingItems/LocaleSetting.tsx index 5d0210d..5814670 100644 --- a/frontend/src/components/SettingItems/LocaleSetting.tsx +++ b/frontend/src/components/SettingItems/LocaleSetting.tsx @@ -16,21 +16,18 @@ export function LocaleSetting() { const [language, setLanguage] = useState("en-US"); useEffect(() => { - Promise.all([GetConfigField("Language")]) - .then(([value]) => { - setLanguage(value); - - setIsLoading(false); // Mark loading as complete - }) - .catch((error) => { - console.error("Error fetching configuration:", error); - setIsLoading(false); // Handle loading error - }); + GetConfigField("Language").then((value) => { + setLanguage(value); + setIsLoading(false); + }); }, []); - useEffect(() => { - i18n.changeLanguage(language); - }, [language]); + const handleLanguageChange = (value: string) => { + SetConfigField("Language", value).then(() => { + setLanguage(value); + i18n.changeLanguage(value); + }); + }; return ( @@ -53,11 +50,7 @@ export function LocaleSetting() { nothingFoundMessage={t( "settings.setting.language.no_languages_found" )} - onChange={(value) => { - SetConfigField("Language", value).then(() => { - setLanguage(value); - }); - }} + onChange={(value) => handleLanguageChange(value)} /> diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx index 34f018d..146df05 100644 --- a/frontend/src/components/SettingItems/LogLevelSetting.tsx +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -10,71 +10,71 @@ import { useTranslation } from "react-i18next"; import { GetConfigField, SetConfigField } from "@/lib/config"; import { useStorage } from "@/contexts/storage-provider"; +type LogLevels = { + trace: boolean; + debug: boolean; + info: boolean; + warn: boolean; + error: boolean; + fatal: boolean; +}; + export function LogLevelSetting() { const { t } = useTranslation(); const { getValue, setValueIfUndefined } = useStorage(); - const [isLoading, setIsLoading] = useState(true); - - const [enableTrace, setEnableTrace] = useState(false); - const [enableDebug, setEnableDebug] = useState(false); - const [enableInfo, setEnableInfo] = useState(false); - const [enableWarn, setEnableWarn] = useState(false); - const [enableError, setEnableError] = useState(false); - const [enableFatal, setEnableFatal] = useState(false); + const [logLevels, setLogLevels] = useState({ + trace: false, + debug: false, + info: false, + warn: false, + error: false, + fatal: false, + }); useEffect(() => { - Promise.all([ - GetConfigField("EnableTrace"), - GetConfigField("EnableDebug"), - GetConfigField("EnableInfo"), - GetConfigField("EnableWarn"), - GetConfigField("EnableError"), - GetConfigField("EnableFatal"), - ]).then( - ([ - enableTrace, - enableDebug, - enableInfo, - enableWarn, - enableError, - enableFatal, - ]) => { - setEnableTrace(enableTrace === "true"); - setEnableDebug(enableDebug === "true"); - setEnableInfo(enableInfo === "true"); - setEnableWarn(enableWarn === "true"); - setEnableError(enableError === "true"); - setEnableFatal(enableFatal === "true"); + const fields = [ + "EnableTrace", + "EnableDebug", + "EnableInfo", + "EnableWarn", + "EnableError", + "EnableFatal", + ]; + Promise.all(fields.map(GetConfigField)).then((values) => { + const newLogLevels: LogLevels = { + trace: values[0] === "true", + debug: values[1] === "true", + info: values[2] === "true", + warn: values[3] === "true", + error: values[4] === "true", + fatal: values[5] === "true", + }; + setLogLevels(newLogLevels); + setValueIfUndefined( + "initialLogLevels", + Object.values(newLogLevels).join("") + ); - setValueIfUndefined( - "initialEnableLogLevel", - enableTrace + - enableDebug + - enableInfo + - enableWarn + - enableError + - enableFatal - ); - - setIsLoading(false); - } - ); + setIsLoading(false); + }); }, []); + const handleToggle = (level: keyof LogLevels) => { + SetConfigField( + `Enable${level.charAt(0).toUpperCase() + level.slice(1)}`, + String(!logLevels[level]) + ).then(() => { + setLogLevels({ ...logLevels, [level]: !logLevels[level] }); + }); + }; + return (
{t("settings.setting.log_levels.label")} @@ -85,86 +85,20 @@ export function LogLevelSetting() { value) + .map(([key]) => key)} > - { - SetConfigField("EnableTrace", String(!enableTrace)).then(() => { - setEnableTrace(!enableTrace); - }); - }} - > - Trace - - - { - SetConfigField("EnableDebug", String(!enableDebug)).then(() => { - setEnableDebug(!enableDebug); - }); - }} - > - Debug - - - { - SetConfigField("EnableInfo", String(!enableInfo)).then(() => { - setEnableInfo(!enableInfo); - }); - }} - > - Info - - - { - SetConfigField("EnableWarn", String(!enableWarn)).then(() => { - setEnableWarn(!enableWarn); - }); - }} - > - Warn - - - { - SetConfigField("EnableError", String(!enableError)).then(() => { - setEnableError(!enableError); - }); - }} - > - Error - - - { - SetConfigField("EnableFatal", String(!enableFatal)).then(() => { - setEnableFatal(!enableFatal); - }); - }} - > - Fatal - + {Object.entries(logLevels).map(([level, _]) => ( + handleToggle(level as keyof LogLevels)} + > + {level.charAt(0).toUpperCase() + level.slice(1)} + + ))} diff --git a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx index cd26760..4ccfa6f 100644 --- a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx +++ b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx @@ -13,7 +13,6 @@ import { useStorage } from "@/contexts/storage-provider"; export function MaxLogFilesSetting() { const { t } = useTranslation(); const { getValue, setValueIfUndefined } = useStorage(); - const [isLoading, setIsLoading] = useState(true); const [maxLogFiles, setMaxLogFiles] = useState(-1); @@ -25,6 +24,14 @@ export function MaxLogFilesSetting() { }); }, []); + const handleMaxLogFilesChange = (textValue: string) => { + const value = Math.max(1, Math.min(10000, parseInt(textValue))); + const targetValue = isNaN(parseInt(textValue)) ? 20 : value; + SetConfigField("MaxLogFiles", String(targetValue)).then(() => { + setMaxLogFiles(value); + }); + }; + return ( { - const value = Math.max( - 1, - Math.min(10000, parseInt(e.target.value)) - ); - const targetValue = isNaN(parseInt(e.target.value)) ? 20 : value; - SetConfigField("MaxLogFiles", String(targetValue)).then(() => { - setMaxLogFiles(value); - }); - }} + onChange={(e) => handleMaxLogFilesChange(e.target.value)} min={1} max={10000} /> diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx index c9b0565..dc3f390 100644 --- a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -25,6 +25,12 @@ export function UseSystemTitleBarSetting() { }); }, []); + const handleUseSystemTitleBarChange = (value: boolean) => { + SetConfigField("UseSystemTitleBar", String(value)).then(() => { + setUseSystemTitleBar(value); + }); + }; + return ( { - SetConfigField("UseSystemTitleBar", String(value)).then(() => { - setUseSystemTitleBar(value); - }); - }} + onCheckedChange={(value) => handleUseSystemTitleBarChange(value)} /> diff --git a/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx b/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx deleted file mode 100644 index a23276c..0000000 --- a/frontend/src/components/SettingItems/WindowEffectOpacitySetting.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { GetConfigField, SetConfigField } from "@/lib/config"; -import { - SettingsItem, - SettingContent, - SettingDescription, - SettingLabel, -} from "../ui/settings-group"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Slider } from "../ui/my-slider"; -import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; -import { useStorage } from "@/contexts/storage-provider"; -import React from "react"; - -export function WindowEffectOpacitySetting() { - const { t } = useTranslation(); - const { getValue, setValueIfUndefined } = useStorage(); - - const [isLoading, setIsLoading] = useState(true); - - const [useOpacity, setUseOpacity] = useState(-1); - const [useWindowEffect, setUseWindowEffect] = useState(""); - - useEffect(() => { - Promise.all([GetConfigField("Opacity"), GetConfigField("WindowEffect")]) - .then(([windowOpacityValue, windowEffectValue]) => { - setUseOpacity(parseInt(windowOpacityValue)); - setUseWindowEffect(windowEffectValue); - setValueIfUndefined("initialWindowEffect", windowEffectValue); - setIsLoading(false); // Mark loading as complete - }) - .catch((error) => { - console.error("Error fetching configuration:", error); - setIsLoading(false); // Handle loading error - }); - }, []); - - useEffect(() => { - if (!isLoading) { - document.documentElement.style.setProperty( - "--opacity", - String( - Number( - useWindowEffect === "1" || getValue("initialWindowEffect") === "1" - ? "100" - : useOpacity - ) / 100 - ) - ); - } - }, [useWindowEffect]); - - return ( - - -
- - {t("settings.setting.window_effect.label")} - - - {t("settings.setting.window_effect.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} - -
- - - { - SetConfigField("WindowEffect", "1").then(() => { - setUseWindowEffect("1"); - }); - }} - > - {t("settings.setting.window_effect.none")} - - - { - SetConfigField("WindowEffect", "0").then(() => { - setUseWindowEffect("0"); - }); - }} - > - {t("settings.setting.window_effect.auto")} - - - { - SetConfigField("WindowEffect", "2").then(() => { - setUseWindowEffect("2"); - }); - }} - > - {t("settings.setting.window_effect.mica")} - - - { - SetConfigField("WindowEffect", "3").then(() => { - setUseWindowEffect("3"); - }); - }} - > - {t("settings.setting.window_effect.acrylic")} - - - { - SetConfigField("WindowEffect", "4").then(() => { - setUseWindowEffect("4"); - }); - }} - > - {t("settings.setting.window_effect.tabbed")} - - - -
- - -
- - {t("settings.setting.window_opacity.label")} - - - {t("settings.setting.window_opacity.description")} - -
- -
-
50%
- { - setUseOpacity(value[0]); - document.documentElement.style.setProperty( - "--opacity", - String( - (useWindowEffect === "1" || - getValue("initialWindowEffect") === "1" - ? 100 - : useOpacity) / 100 - ) - ); - }} - onPointerUp={() => { - SetConfigField("Opacity", String(useOpacity)); - }} - defaultValue={[useOpacity]} - min={50} - max={100} - step={1} - className={"w-64 cursor-pointer"} - /> -
100%
-
({useOpacity}%)
-
-
-
-
- ); -} diff --git a/frontend/src/components/SettingItems/WindowEffectSetting.tsx b/frontend/src/components/SettingItems/WindowEffectSetting.tsx new file mode 100644 index 0000000..e0ba49b --- /dev/null +++ b/frontend/src/components/SettingItems/WindowEffectSetting.tsx @@ -0,0 +1,72 @@ +import { GetConfigField, SetConfigField } from "@/lib/config"; +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; +import { useStorage } from "@/contexts/storage-provider"; + +export function WindowEffectSetting() { + const { t } = useTranslation(); + const { getValue, setValueIfUndefined } = useStorage(); + const [isLoading, setIsLoading] = useState(true); + const [useWindowEffect, setUseWindowEffect] = useState(""); + + useEffect(() => { + GetConfigField("WindowEffect").then((windowEffectValue) => { + setUseWindowEffect(windowEffectValue); + setValueIfUndefined("initialWindowEffect", windowEffectValue); + + setIsLoading(false); + }); + }, []); + + const handleToggle = (value: string) => { + SetConfigField("WindowEffect", value).then(() => { + setUseWindowEffect(value); + }); + }; + + const windowEffectOptions = [ + { value: "1", label: t("settings.setting.window_effect.none"), aria: "No window effect" }, + { value: "0", label: t("settings.setting.window_effect.auto"), aria: "Auto window effect" }, + { value: "2", label: t("settings.setting.window_effect.mica"), aria: "Mica window effect" }, + { value: "3", label: t("settings.setting.window_effect.acrylic"), aria: "Acrylic window effect" }, + { value: "4", label: t("settings.setting.window_effect.tabbed"), aria: "Tabbed window effect" } + ]; + + return ( + +
+ {t("settings.setting.window_effect.label")} + + {`${t("settings.setting.window_effect.description")} (${t("settings.restart_the_app_for_changes_to_take_effect")})`} + +
+ + + {windowEffectOptions.map(({ value, label, aria }) => ( + handleToggle(value)} + className={value === getValue("initialWindowEffect") ? "font-bold text-shadow-xl shadow-foreground" : ""} + > + {label} + + ))} + + +
+ ); +} diff --git a/frontend/src/components/SettingItems/WindowOpacitySetting.tsx b/frontend/src/components/SettingItems/WindowOpacitySetting.tsx new file mode 100644 index 0000000..ee2389a --- /dev/null +++ b/frontend/src/components/SettingItems/WindowOpacitySetting.tsx @@ -0,0 +1,75 @@ +import { GetConfigField, SetConfigField } from "@/lib/config"; +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "../ui/settings-group"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Slider } from "../ui/my-slider"; +import { useStorage } from "@/contexts/storage-provider"; + +export function WindowOpacitySetting() { + const { t } = useTranslation(); + const { getValue } = useStorage(); + + const [isLoading, setIsLoading] = useState(true); + + const [useOpacity, setUseOpacity] = useState(-1); + + useEffect(() => { + GetConfigField("Opacity").then((value) => { + setUseOpacity(parseInt(value)); + + setIsLoading(false); + }); + }, []); + + const handleOpacityChange = (value: number) => { + document.documentElement.style.setProperty( + "--opacity", + String(value / 100) + ); + setUseOpacity(value); + }; + + const saveOpacityChange = () => { + SetConfigField("Opacity", useOpacity.toString()); + }; + + return ( + +
+ + {t("settings.setting.window_opacity.label")} + + + {t("settings.setting.window_opacity.description")} + +
+ +
+
50%
+ { + handleOpacityChange(value[0]); + }} + onPointerUp={saveOpacityChange} + defaultValue={[useOpacity]} + min={50} + max={100} + step={1} + className={"w-64 cursor-pointer"} + /> +
100%
+
({useOpacity}%)
+
+
+
+ ); +} diff --git a/frontend/src/components/SettingItems/WindowScaleSetting.tsx b/frontend/src/components/SettingItems/WindowScaleSetting.tsx index bbf8131..5abfe87 100644 --- a/frontend/src/components/SettingItems/WindowScaleSetting.tsx +++ b/frontend/src/components/SettingItems/WindowScaleSetting.tsx @@ -21,6 +21,12 @@ export function WindowScaleSetting() { }); }, []); + const handleScaleSave = () => { + SetConfigField("WindowScale", String(useScale)).then(() => { + document.documentElement.style.fontSize = useScale * (16 / 100) + "px"; + }); + }; + return (
@@ -33,15 +39,8 @@ export function WindowScaleSetting() {
50%
{ - setUseScale(value[0]); - }} - onPointerUp={() => { - document.documentElement.style.fontSize = - useScale * (16 / 100) + "px"; - - SetConfigField("WindowScale", String(useScale)); - }} + onValueChange={(value) => setUseScale(value[0])} + onPointerUp={handleScaleSave} defaultValue={[useScale]} min={50} max={150} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index f4781ec..c5624a1 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -4,12 +4,13 @@ import { LocaleSetting } from "./SettingItems/LocaleSetting"; import { ThemeSetting } from "./SettingItems/ThemeSetting"; import { SettingsGroup } from "./ui/settings-group"; import { UseSystemTitleBarSetting } from "./SettingItems/UseSystemTitleBarSetting"; -import { WindowEffectOpacitySetting } from "./SettingItems/WindowEffectOpacitySetting"; import { WindowScaleSetting } from "./SettingItems/WindowScaleSetting"; import { MaxLogFilesSetting } from "./SettingItems/MaxLogFilesSetting"; import { EnableLoggingSetting } from "./SettingItems/EnableLoggingSetting"; import { LogLevelSetting } from "./SettingItems/LogLevelSetting"; import { ImportExportSetting } from "./SettingItems/ImportExportSetting"; +import { WindowEffectSetting } from "./SettingItems/WindowEffectSetting"; +import { WindowOpacitySetting } from "./SettingItems/WindowOpacitySetting"; export default function Settings() { const { t } = useTranslation(); @@ -40,7 +41,8 @@ export default function Settings() { - + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 2492f5e..884df90 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,4 +1,6 @@ /** @type {import('tailwindcss').Config} */ +const plugin = require('tailwindcss/plugin') + module.exports = { darkMode: ["class"], content: [ @@ -81,7 +83,25 @@ module.exports = { "bottom-xl": "0px 8px 10px rgba(0,0,0,0.1)", "bottom-2xl": "0px 12px 16px rgba(0,0,0,0.1)", }, + textShadow: { + sm: "0 1px 2px var(--tw-shadow-color)", + DEFAULT: "0 2px 4px var(--tw-shadow-color)", + lg: "0 3px 16px var(--tw-shadow-color)", + xl: "0 4px 24px var(--tw-shadow-color)", + }, }, }, - plugins: [require("tailwindcss-animate")], + plugins: [ + require("tailwindcss-animate"), + plugin(function ({ matchUtilities, theme }) { + matchUtilities( + { + "text-shadow": (value) => ({ + textShadow: value, + }), + }, + { values: theme("textShadow") } + ); + }), + ], }; From 7870e23f13905403ce2c11b8adb596037b717cff Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 30 Jun 2024 18:16:38 +0300 Subject: [PATCH 076/221] refactor setting items --- .../SettingItems/EnableLoggingSetting.tsx | 1 - .../SettingItems/ImportExportSetting.tsx | 2 +- .../components/SettingItems/ThemeSetting.tsx | 82 +++++++++---------- .../SettingItems/UseSystemTitleBarSetting.tsx | 1 - .../SettingItems/WindowOpacitySetting.tsx | 2 - .../SettingItems/WindowScaleSetting.tsx | 2 +- 6 files changed, 43 insertions(+), 47 deletions(-) diff --git a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx index dfdb5cd..0963a91 100644 --- a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx +++ b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx @@ -13,7 +13,6 @@ import { useStorage } from "@/contexts/storage-provider"; export function EnableLoggingSetting() { const { t } = useTranslation(); const { getValue, setValueIfUndefined } = useStorage(); - const [isLoading, setIsLoading] = useState(true); const [enableLogging, setEnableLogging] = useState(false); diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index ed88075..2d57ba1 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -36,7 +36,7 @@ export function ImportExportSetting() { }; return ( - +
{t("settings.setting.import_export.label")} diff --git a/frontend/src/components/SettingItems/ThemeSetting.tsx b/frontend/src/components/SettingItems/ThemeSetting.tsx index 8d13f12..d741cb5 100644 --- a/frontend/src/components/SettingItems/ThemeSetting.tsx +++ b/frontend/src/components/SettingItems/ThemeSetting.tsx @@ -1,8 +1,8 @@ import { - SettingsItem, - SettingContent, - SettingDescription, - SettingLabel, + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, } from "../ui/settings-group"; import { useTranslation } from "react-i18next"; import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; @@ -10,42 +10,42 @@ import { Monitor, Moon, Sun } from "lucide-react"; import { useTheme } from "@/contexts/theme-provider"; export function ThemeSetting() { - const { t } = useTranslation(); - const { theme, setTheme } = useTheme(); + const { t } = useTranslation(); + const { theme, setTheme } = useTheme(); - return ( - -
- {t("settings.setting.theme.label")} - - {t("settings.setting.theme.description")} - -
- - - setTheme("system")} - > - - - setTheme("light")} - > - - - setTheme("dark")} - > - - - - -
- ); + return ( + +
+ {t("settings.setting.theme.label")} + + {t("settings.setting.theme.description")} + +
+ + + setTheme("system")} + > + + + setTheme("light")} + > + + + setTheme("dark")} + > + + + + +
+ ); } diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx index dc3f390..d4c9394 100644 --- a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -13,7 +13,6 @@ import { useStorage } from "@/contexts/storage-provider"; export function UseSystemTitleBarSetting() { const { t } = useTranslation(); const { getValue, setValueIfUndefined } = useStorage(); - const [isLoading, setIsLoading] = useState(true); const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); diff --git a/frontend/src/components/SettingItems/WindowOpacitySetting.tsx b/frontend/src/components/SettingItems/WindowOpacitySetting.tsx index ee2389a..b05a2b7 100644 --- a/frontend/src/components/SettingItems/WindowOpacitySetting.tsx +++ b/frontend/src/components/SettingItems/WindowOpacitySetting.tsx @@ -13,9 +13,7 @@ import { useStorage } from "@/contexts/storage-provider"; export function WindowOpacitySetting() { const { t } = useTranslation(); const { getValue } = useStorage(); - const [isLoading, setIsLoading] = useState(true); - const [useOpacity, setUseOpacity] = useState(-1); useEffect(() => { diff --git a/frontend/src/components/SettingItems/WindowScaleSetting.tsx b/frontend/src/components/SettingItems/WindowScaleSetting.tsx index 5abfe87..6cb11fb 100644 --- a/frontend/src/components/SettingItems/WindowScaleSetting.tsx +++ b/frontend/src/components/SettingItems/WindowScaleSetting.tsx @@ -28,7 +28,7 @@ export function WindowScaleSetting() { }; return ( - +
{t("settings.setting.window_scale.label")} From b28760496fdde141759c877698851092487ffd36 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 30 Jun 2024 18:48:27 +0300 Subject: [PATCH 077/221] generalize config swithces --- .../SettingItems/EnableLoggingSetting.tsx | 57 ++-------------- .../SettingItems/Presets/SwitchConfig.tsx | 65 +++++++++++++++++++ .../SettingItems/UseSystemTitleBarSetting.tsx | 58 ++--------------- 3 files changed, 79 insertions(+), 101 deletions(-) create mode 100644 frontend/src/components/SettingItems/Presets/SwitchConfig.tsx diff --git a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx index 0963a91..0642710 100644 --- a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx +++ b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx @@ -1,58 +1,15 @@ -import { - SettingsItem, - SettingContent, - SettingDescription, - SettingLabel, -} from "../ui/settings-group"; -import { Switch } from "../ui/switch"; -import { useEffect, useState } from "react"; +import { SwitchConfig } from "./Presets/SwitchConfig"; import { useTranslation } from "react-i18next"; -import { GetConfigField, SetConfigField } from "@/lib/config"; -import { useStorage } from "@/contexts/storage-provider"; export function EnableLoggingSetting() { const { t } = useTranslation(); - const { getValue, setValueIfUndefined } = useStorage(); - const [isLoading, setIsLoading] = useState(true); - const [enableLogging, setEnableLogging] = useState(false); - - useEffect(() => { - GetConfigField("EnableLogging").then((value) => { - setEnableLogging(value === "true"); - setValueIfUndefined("initialEnableLogging", value); - - setIsLoading(false); - }); - }, []); - - const handleEnableLoggingChange = (value: boolean) => { - SetConfigField("EnableLogging", String(value)).then(() => { - setEnableLogging(value); - }); - }; return ( - -
- {t("settings.setting.logging.label")} - - {t("settings.setting.logging.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} - -
- - handleEnableLoggingChange(!enableLogging)} - /> - -
+ ); } diff --git a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx new file mode 100644 index 0000000..e34c62d --- /dev/null +++ b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx @@ -0,0 +1,65 @@ +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "@/components/ui/settings-group"; +import { Switch } from "@/components/ui/switch"; +import { useEffect, useState } from "react"; +import { GetConfigField, SetConfigField } from "@/lib/config"; +import { useStorage } from "@/contexts/storage-provider"; +import { useTranslation } from "react-i18next"; + +interface SwitchConfigProps { + configValue: string; + label: string; + description?: string; + requiresRestart?: boolean; +} + +export function SwitchConfig({ + configValue, + label, + description, + requiresRestart, +}: SwitchConfigProps) { + const { t } = useTranslation(); + const { getValue, setValueIfUndefined } = useStorage(); + const [isLoading, setIsLoading] = useState(true); + const [switchValue, setSwitchValue] = useState(false); + + useEffect(() => { + GetConfigField(configValue).then((value) => { + setSwitchValue(value === "true"); + if (requiresRestart) setValueIfUndefined(`initial${configValue}`, value); + + setIsLoading(false); + }); + }, []); + + const handleSwitch = (value: boolean) => { + SetConfigField(configValue, String(value)).then(() => { + setSwitchValue(value); + }); + }; + + return ( + +
+ {label} + {description && {description+ " (" + t("settings.restart_the_app_for_changes_to_take_effect") + ")"}} +
+ + handleSwitch(!switchValue)} + /> + +
+ ); +} diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx index d4c9394..34f361f 100644 --- a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -1,59 +1,15 @@ -import { GetConfigField, SetConfigField } from "@/lib/config"; -import { - SettingsItem, - SettingContent, - SettingDescription, - SettingLabel, -} from "../ui/settings-group"; -import { Switch } from "@/components/ui/switch"; -import { useEffect, useState } from "react"; +import { SwitchConfig } from "./Presets/SwitchConfig"; import { useTranslation } from "react-i18next"; -import { useStorage } from "@/contexts/storage-provider"; export function UseSystemTitleBarSetting() { const { t } = useTranslation(); - const { getValue, setValueIfUndefined } = useStorage(); - const [isLoading, setIsLoading] = useState(true); - const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); - - useEffect(() => { - GetConfigField("UseSystemTitleBar").then((value) => { - setUseSystemTitleBar(value === "true"); - setValueIfUndefined("initialUseSystemTitleBar", value); - setIsLoading(false); - }); - }, []); - - const handleUseSystemTitleBarChange = (value: boolean) => { - SetConfigField("UseSystemTitleBar", String(value)).then(() => { - setUseSystemTitleBar(value); - }); - }; return ( - -
- - {t("settings.setting.use_system_title_bar.label")} - - - {t("settings.setting.use_system_title_bar.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} - -
- - handleUseSystemTitleBarChange(value)} - /> - -
+ ); } From 8ee123713340b37826b817d8f2694328bcf308b3 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 30 Jun 2024 18:50:56 +0300 Subject: [PATCH 078/221] fix requires restart text --- frontend/src/components/SettingItems/Presets/SwitchConfig.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx index e34c62d..69b16d9 100644 --- a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx +++ b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx @@ -52,7 +52,7 @@ export function SwitchConfig({ >
{label} - {description && {description+ " (" + t("settings.restart_the_app_for_changes_to_take_effect") + ")"}} + {description && {description + (requiresRestart ? " (" + t("settings.restart_the_app_for_changes_to_take_effect") + ")" : "")}}
Date: Sun, 30 Jun 2024 19:36:11 +0300 Subject: [PATCH 079/221] add save window status setting --- app.go | 32 ++++++++++--------- config.go | 3 ++ frontend/public/locales/en-US.json | 5 +++ frontend/public/locales/tr-TR.json | 5 +++ .../SettingItems/SaveWindowStatusSetting.tsx | 14 ++++++++ frontend/src/components/Settings.tsx | 2 ++ frontend/src/lib/config.ts | 6 ++-- 7 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 frontend/src/components/SettingItems/SaveWindowStatusSetting.tsx diff --git a/app.go b/app.go index 1e73428..b874aaa 100644 --- a/app.go +++ b/app.go @@ -70,23 +70,25 @@ func (a App) domReady(ctx context.Context) { // either by clicking the window close button or calling runtime.Quit. // Returning true will cause the application to continue, false will continue shutdown as normal. func (a *App) beforeClose(ctx context.Context) (prevent bool) { - if runtime.WindowIsMaximised(a.ctx) { - var windowState = 2 - config.WindowStartState = &windowState - runtime.LogInfo(a.ctx, "Setting window state to maximized") - } else { - var windowState = 0 - config.WindowStartState = &windowState - runtime.LogInfo(a.ctx, "Setting window state to normal") - } + if *config.SaveWindowStatus { + if runtime.WindowIsMaximised(a.ctx) { + var windowState = 2 + config.WindowStartState = &windowState + runtime.LogInfo(a.ctx, "Setting window state to maximized") + } else { + var windowState = 0 + config.WindowStartState = &windowState + runtime.LogInfo(a.ctx, "Setting window state to normal") + } - windowPositionX, windowPositionY := runtime.WindowGetPosition(a.ctx) - config.WindowStartPositionX, config.WindowStartPositionY = &windowPositionX, &windowPositionY - runtime.LogInfo(a.ctx, fmt.Sprintf("Setting window position to %d,%d", windowPositionX, windowPositionY)) + windowPositionX, windowPositionY := runtime.WindowGetPosition(a.ctx) + config.WindowStartPositionX, config.WindowStartPositionY = &windowPositionX, &windowPositionY + runtime.LogInfo(a.ctx, fmt.Sprintf("Setting window position to %d,%d", windowPositionX, windowPositionY)) - windowSizeX, windowSizeY := runtime.WindowGetSize(a.ctx) - config.WindowStartSizeX, config.WindowStartSizeY = &windowSizeX, &windowSizeY - runtime.LogInfo(a.ctx, fmt.Sprintf("Setting window size to %d,%d", windowSizeX, windowSizeY)) + windowSizeX, windowSizeY := runtime.WindowGetSize(a.ctx) + config.WindowStartSizeX, config.WindowStartSizeY = &windowSizeX, &windowSizeY + runtime.LogInfo(a.ctx, fmt.Sprintf("Setting window size to %d,%d", windowSizeX, windowSizeY)) + } runtime.LogInfo(a.ctx, "Saving config") err := WriteConfig(configPath) diff --git a/config.go b/config.go index 6bf525f..579c354 100644 --- a/config.go +++ b/config.go @@ -23,6 +23,7 @@ type Config struct { EnableFatal *bool `json:"enableFatal"` // true, false MaxLogFiles *int `json:"maxLogFiles"` // int Language *string `json:"language"` // en-US, tr-TR + SaveWindowStatus *bool `json:"saveWindowStatus"` // true, false WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen WindowStartPositionX *int `json:"windowStartPositionX"` // x WindowStartPositionY *int `json:"windowStartPositionY"` // y @@ -45,6 +46,7 @@ func GetDefaultConfig() Config { defaultEnableFatal := true defaultMaxLogFiles := 20 defaultLanguage := "en-US" + defaultSaveWindowStatus := true defaultWindowStartState := 0 defaultWindowStartPositionX := -1 defaultWindowStartPositionY := -1 @@ -66,6 +68,7 @@ func GetDefaultConfig() Config { EnableFatal: &defaultEnableFatal, MaxLogFiles: &defaultMaxLogFiles, Language: &defaultLanguage, + SaveWindowStatus: &defaultSaveWindowStatus, WindowStartState: &defaultWindowStartState, WindowStartPositionX: &defaultWindowStartPositionX, WindowStartPositionY: &defaultWindowStartPositionY, diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index 835b568..d8603b7 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -84,6 +84,11 @@ "import_export": { "label": "Import/Export Settings", "description": "Import or export settings your settings from/to a JSON file." + }, + + "save_window_status": { + "label": "Save Window Status", + "description": "Save window size, position, and state." } } } diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index 7422fb6..8fa7301 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -84,6 +84,11 @@ "import_export": { "label": "Ayarları İçe/Dışa Aktar", "description": "Ayarlarınızı bir JSON dosyasından içe veya dışa aktarın." + }, + + "save_window_status": { + "label": "Pencere Durumunu Kaydet", + "description": "Pencere boyutunu, konumunu ve durumunu kaydet." } } } diff --git a/frontend/src/components/SettingItems/SaveWindowStatusSetting.tsx b/frontend/src/components/SettingItems/SaveWindowStatusSetting.tsx new file mode 100644 index 0000000..2f8d24c --- /dev/null +++ b/frontend/src/components/SettingItems/SaveWindowStatusSetting.tsx @@ -0,0 +1,14 @@ +import { SwitchConfig } from "./Presets/SwitchConfig"; +import { useTranslation } from "react-i18next"; + +export function SaveWindowStatusSetting() { + const { t } = useTranslation(); + + return ( + + ); +} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index c5624a1..c8a4532 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -11,6 +11,7 @@ import { LogLevelSetting } from "./SettingItems/LogLevelSetting"; import { ImportExportSetting } from "./SettingItems/ImportExportSetting"; import { WindowEffectSetting } from "./SettingItems/WindowEffectSetting"; import { WindowOpacitySetting } from "./SettingItems/WindowOpacitySetting"; +import { SaveWindowStatusSetting } from "./SettingItems/SaveWindowStatusSetting"; export default function Settings() { const { t } = useTranslation(); @@ -45,6 +46,7 @@ export default function Settings() { + diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index 542c1a8..503e2d0 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -57,14 +57,16 @@ export async function InitConfigCache(): Promise { "EnableFatal", "MaxLogFiles", "Language", - "WindowStartState", + "SaveWindowStatus", "WindowScale", "Opacity", "WindowEffect", ]; // Array to store promises - const fetchPromises: Promise[] = keys.map((key) => GetConfigField(key)); + const fetchPromises: Promise[] = keys.map((key) => + GetConfigField(key) + ); // Wait for all promises to resolve const results = await Promise.all(fetchPromises); From 5845fa1f599e1888c8c67af95c8e1e786db6afb2 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 30 Jun 2024 19:39:11 +0300 Subject: [PATCH 080/221] update gitignore --- .gitignore | 1 + build/windows/installer/wails_tools.nsh | 249 ------------------------ 2 files changed, 1 insertion(+), 249 deletions(-) delete mode 100644 build/windows/installer/wails_tools.nsh diff --git a/.gitignore b/.gitignore index 0331b6d..80fc773 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ lerna-debug.log* *.local node_modules /build/bin +/build/windows/installer/wails_tools.nsh /frontend/dist /frontend/dist-ssr /frontend/package.json.md5 diff --git a/build/windows/installer/wails_tools.nsh b/build/windows/installer/wails_tools.nsh deleted file mode 100644 index f9c0f88..0000000 --- a/build/windows/installer/wails_tools.nsh +++ /dev/null @@ -1,249 +0,0 @@ -# DO NOT EDIT - Generated automatically by `wails build` - -!include "x64.nsh" -!include "WinVer.nsh" -!include "FileFunc.nsh" - -!ifndef INFO_PROJECTNAME - !define INFO_PROJECTNAME "{{.Name}}" -!endif -!ifndef INFO_COMPANYNAME - !define INFO_COMPANYNAME "{{.Info.CompanyName}}" -!endif -!ifndef INFO_PRODUCTNAME - !define INFO_PRODUCTNAME "{{.Info.ProductName}}" -!endif -!ifndef INFO_PRODUCTVERSION - !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}" -!endif -!ifndef INFO_COPYRIGHT - !define INFO_COPYRIGHT "{{.Info.Copyright}}" -!endif -!ifndef PRODUCT_EXECUTABLE - !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" -!endif -!ifndef UNINST_KEY_NAME - !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" -!endif -!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" - -!ifndef REQUEST_EXECUTION_LEVEL - !define REQUEST_EXECUTION_LEVEL "admin" -!endif - -RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" - -!ifdef ARG_WAILS_AMD64_BINARY - !define SUPPORTS_AMD64 -!endif - -!ifdef ARG_WAILS_ARM64_BINARY - !define SUPPORTS_ARM64 -!endif - -!ifdef SUPPORTS_AMD64 - !ifdef SUPPORTS_ARM64 - !define ARCH "amd64_arm64" - !else - !define ARCH "amd64" - !endif -!else - !ifdef SUPPORTS_ARM64 - !define ARCH "arm64" - !else - !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" - !endif -!endif - -!macro wails.checkArchitecture - !ifndef WAILS_WIN10_REQUIRED - !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." - !endif - - !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED - !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" - !endif - - ${If} ${AtLeastWin10} - !ifdef SUPPORTS_AMD64 - ${if} ${IsNativeAMD64} - Goto ok - ${EndIf} - !endif - - !ifdef SUPPORTS_ARM64 - ${if} ${IsNativeARM64} - Goto ok - ${EndIf} - !endif - - IfSilent silentArch notSilentArch - silentArch: - SetErrorLevel 65 - Abort - notSilentArch: - MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" - Quit - ${else} - IfSilent silentWin notSilentWin - silentWin: - SetErrorLevel 64 - Abort - notSilentWin: - MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" - Quit - ${EndIf} - - ok: -!macroend - -!macro wails.files - !ifdef SUPPORTS_AMD64 - ${if} ${IsNativeAMD64} - File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" - ${EndIf} - !endif - - !ifdef SUPPORTS_ARM64 - ${if} ${IsNativeARM64} - File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" - ${EndIf} - !endif -!macroend - -!macro wails.writeUninstaller - WriteUninstaller "$INSTDIR\uninstall.exe" - - SetRegView 64 - WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" - WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" - WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" - - ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 - IntFmt $0 "0x%08X" $0 - WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" -!macroend - -!macro wails.deleteUninstaller - Delete "$INSTDIR\uninstall.exe" - - SetRegView 64 - DeleteRegKey HKLM "${UNINST_KEY}" -!macroend - -!macro wails.setShellContext - ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" - SetShellVarContext all - ${else} - SetShellVarContext current - ${EndIf} -!macroend - -# Install webview2 by launching the bootstrapper -# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment -!macro wails.webview2runtime - !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT - !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" - !endif - - SetRegView 64 - # If the admin key exists and is not empty then webview2 is already installed - ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" - ${If} $0 != "" - Goto ok - ${EndIf} - - ${If} ${REQUEST_EXECUTION_LEVEL} == "user" - # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed - ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" - ${If} $0 != "" - Goto ok - ${EndIf} - ${EndIf} - - SetDetailsPrint both - DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" - SetDetailsPrint listonly - - InitPluginsDir - CreateDirectory "$pluginsdir\webview2bootstrapper" - SetOutPath "$pluginsdir\webview2bootstrapper" - File "tmp\MicrosoftEdgeWebview2Setup.exe" - ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' - - SetDetailsPrint both - ok: -!macroend - -# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b -!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" - - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" - - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` -!macroend - -!macro APP_UNASSOCIATE EXT FILECLASS - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" - - DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` -!macroend - -!macro wails.associateFiles - ; Create file associations - {{range .Info.FileAssociations}} - !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - - File "..\{{.IconName}}.ico" - {{end}} -!macroend - -!macro wails.unassociateFiles - ; Delete app associations - {{range .Info.FileAssociations}} - !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" - - Delete "$INSTDIR\{{.IconName}}.ico" - {{end}} -!macroend - -!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" -!macroend - -!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" -!macroend - -!macro wails.associateCustomProtocols - ; Create custom protocols associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - - {{end}} -!macroend - -!macro wails.unassociateCustomProtocols - ; Delete app custom protocol associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" - {{end}} -!macroend From 96aa72733993fa4ea309255f82e58e6934457298 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 30 Jun 2024 21:40:21 +0300 Subject: [PATCH 081/221] change default installation directory --- build/windows/installer/project.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/windows/installer/project.nsi b/build/windows/installer/project.nsi index 654ae2e..eb91df1 100644 --- a/build/windows/installer/project.nsi +++ b/build/windows/installer/project.nsi @@ -72,7 +72,7 @@ ManifestDPIAware true Name "${INFO_PRODUCTNAME}" OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. -InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +InstallDir "$PROGRAMFILES64\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). ShowInstDetails show # This will always show the installation details. Function .onInit From c695702147c8d4561a4077f6fd09818e38df47c7 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 30 Jun 2024 23:25:26 +0300 Subject: [PATCH 082/221] change app name --- .github/workflows/build.yml | 2 +- README.md | 4 ++-- app_paths.go | 6 +++--- frontend/index.html | 2 +- frontend/package.json | 2 +- go.mod | 2 +- main.go | 4 ++-- wails.json | 6 +++--- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c05a1bd..7c1fef3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: platform: [windows-latest] - build-name: ["desktop-manager"] + build-name: ["iconium"] go-version: [1.22] node-version: [20] runs-on: ${{ matrix.platform }} diff --git a/README.md b/README.md index 7474f6e..13c9223 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Desktop Manager +# Iconium Desktop icon and description manager, with a profile creation tool. @@ -30,4 +30,4 @@ Desktop icon and description manager, with a profile creation tool. ## License -Distributed under the MIT License. See [LICENSE](https://github.com/beyenilmez/desktop-manager/blob/main/LICENSE) for more information. +Distributed under the MIT License. See [LICENSE](https://github.com/beyenilmez/iconium/blob/main/LICENSE) for more information. diff --git a/app_paths.go b/app_paths.go index e683995..e636f67 100644 --- a/app_paths.go +++ b/app_paths.go @@ -24,7 +24,7 @@ func path_init() error { } runtime.LogDebug(appContext, "Found user config directory: "+appData) - appFolder := path.Join(appData, "desktop-manager") + appFolder := path.Join(appData, "iconium") packsFolder = path.Join(appFolder, "packs") logsFolder = path.Join(appFolder, "logs") savedConfigFolder = path.Join(appFolder, "savedconfigs") @@ -68,7 +68,7 @@ func path_init() error { } func get_logs_folder() (string, error) { - logsFolder = path.Join(os.Getenv("APPDATA"), "desktop-manager", "logs") + logsFolder = path.Join(os.Getenv("APPDATA"), "iconium", "logs") // Create folder if it doesn't exist if _, err := os.Stat(logsFolder); os.IsNotExist(err) { @@ -81,7 +81,7 @@ func get_logs_folder() (string, error) { } func get_config_path() string { - configPath = path.Join(os.Getenv("APPDATA"), "desktop-manager", "config.json") + configPath = path.Join(os.Getenv("APPDATA"), "iconium", "config.json") return configPath } diff --git a/frontend/index.html b/frontend/index.html index b8df9fb..9b1e671 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - Desktop Manager + Iconium
diff --git a/frontend/package.json b/frontend/package.json index 8ab3646..2440f69 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "desktop-manager", + "name": "iconium", "private": true, "version": "0.0.0", "type": "module", diff --git a/go.mod b/go.mod index c98f3e8..ab8a0f5 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module desktop-manager +module iconium go 1.21 diff --git a/main.go b/main.go index dbe7e76..7eb3c8b 100644 --- a/main.go +++ b/main.go @@ -75,7 +75,7 @@ func main() { // Create application with options err = wails.Run(&options.App{ - Title: "Desktop Manager", + Title: "Iconium", Width: 1280, Height: 800, MinWidth: 1024, @@ -111,7 +111,7 @@ func main() { DisableWindowIcon: false, BackdropType: windowEffect, // DisableFramelessWindowDecorations: false, - WebviewUserDataPath: path.Join(os.Getenv("APPDATA"), "desktop-manager"), + WebviewUserDataPath: path.Join(os.Getenv("APPDATA"), "iconium"), ZoomFactor: 1.0, DisablePinchZoom: true, }, diff --git a/wails.json b/wails.json index 950ce85..a06d1d7 100644 --- a/wails.json +++ b/wails.json @@ -1,7 +1,7 @@ { "$schema": "https://wails.io/schemas/config.v2.json", - "name": "desktop-manager", - "outputfilename": "desktop-manager", + "name": "iconium", + "outputfilename": "iconium", "frontend:install": "yarn", "frontend:build": "yarn build", "frontend:dev:watcher": "yarn dev", @@ -11,7 +11,7 @@ "email": "yenilmezbedirhan@gmail.com" }, "info": { - "productName": "Desktop Manager", + "productName": "Iconium", "productVersion": "0.1.0", "copyright": "Copyright © 2024 Bedirhan Yenilmez" } From 9a0f8b80627c20514db770275ba96e4a31210c04 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 30 Jun 2024 23:31:17 +0300 Subject: [PATCH 083/221] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 13c9223..796541e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Iconium -Desktop icon and description manager, with a profile creation tool. +Icon and description pack creation tool. ## Screenshots @@ -8,16 +8,16 @@ Desktop icon and description manager, with a profile creation tool. ## Features -- Create desktop profiles +- Create icon packs - Edit icon and description - Shortcut (.lnk) support ## Planned -- Save desktop layout to profile -- Share profiles as files +- Save desktop layout to a pack +- Share icon packs as files - Folder and .url support -- Sync desktop with profile +- Sync desktop with selected packs - .png and .jpg support for icon selection ## Built With From 5b29c97501ca3c8d88c03e7cebdfffc35f3a7858 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Mon, 1 Jul 2024 22:35:52 +0300 Subject: [PATCH 084/221] add self update --- app.go | 31 +++- build/windows/installer/project.nsi | 2 +- config.go | 6 + frontend/public/locales/en-US.json | 24 ++- frontend/public/locales/tr-TR.json | 24 ++- .../SettingItems/CheckForUpdatesSetting.tsx | 14 ++ .../SettingItems/ImportExportSetting.tsx | 4 +- .../components/SettingItems/UpdateSetting.tsx | 146 ++++++++++++++++ frontend/src/components/Settings.tsx | 11 ++ frontend/src/components/TitleBar.tsx | 2 +- frontend/src/index.css | 6 + frontend/src/lib/config.ts | 1 + frontend/src/main.tsx | 7 +- frontend/tailwind.config.js | 4 + frontend/wailsjs/go/main/App.d.ts | 11 +- frontend/wailsjs/go/main/App.js | 20 ++- frontend/wailsjs/go/models.ts | 27 +++ go.mod | 3 + go.sum | 17 ++ main.go | 42 +++++ update.go | 164 ++++++++++++++++++ 21 files changed, 549 insertions(+), 17 deletions(-) create mode 100644 frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx create mode 100644 frontend/src/components/SettingItems/UpdateSetting.tsx create mode 100644 frontend/wailsjs/go/models.ts create mode 100644 update.go diff --git a/app.go b/app.go index b874aaa..23749f9 100644 --- a/app.go +++ b/app.go @@ -62,8 +62,15 @@ func (a *App) startup(ctx context.Context) { } // domReady is called after front-end resources have been loaded -func (a App) domReady(ctx context.Context) { - // Add your action here +func (a *App) domReady(ctx context.Context) { + // Check updates + if *config.CheckForUpdates { + updateInfo := a.CheckForUpdate() + + if updateInfo.UpdateAvailable { + a.SendNotification("settings.setting.update.update_available", "v"+updateInfo.CurrentVersion+" ⭢ "+updateInfo.LatestVersion, "", "") + } + } } // beforeClose is called when the application is about to quit, @@ -127,6 +134,10 @@ func onFirstRun() { set_system_language() } +func (a *App) GetVersion() string { + return version +} + // Send notification func (a *App) SendNotification(title string, message string, path string, variant string) { runtime.LogInfo(a.ctx, "Sending notification") @@ -154,15 +165,25 @@ func (a *App) SendNotification(title string, message string, path string, varian } } -func (a *App) RestartApplication() error { +func (a *App) RestartApplication(admin bool, args []string) error { // Get the path to the current executable executable, err := os.Executable() if err != nil { return err } - // Create a command to execute the current executable with its arguments - cmd := exec.Command(executable, os.Args[1:]...) + // Determine the command to execute + var cmd *exec.Cmd + if admin { + // Execute with administrative privileges using runas command + cmd = exec.Command("runas", "/user:Administrator", executable) + } else { + // Execute without administrative privileges + cmd = exec.Command(executable) + } + + // Append additional arguments if provided + cmd.Args = append(cmd.Args, args...) // Pass along the environment variables cmd.Env = os.Environ() diff --git a/build/windows/installer/project.nsi b/build/windows/installer/project.nsi index eb91df1..d2a6269 100644 --- a/build/windows/installer/project.nsi +++ b/build/windows/installer/project.nsi @@ -72,7 +72,7 @@ ManifestDPIAware true Name "${INFO_PRODUCTNAME}" OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. -InstallDir "$PROGRAMFILES64\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +InstallDir "$Appdata\iconium\bin" # Default installing folder ($PROGRAMFILES is Program Files folder). ShowInstDetails show # This will always show the installation details. Function .onInit diff --git a/config.go b/config.go index 579c354..ca058fb 100644 --- a/config.go +++ b/config.go @@ -32,6 +32,8 @@ type Config struct { WindowScale *int `json:"windowScale"` // % Opacity *int `json:"opacity"` // % WindowEffect *int `json:"windowEffect"` // 0 = Auto, 1 = None, 2 = Mica, 3 = Acrylic, 4 = Tabbed + CheckForUpdates *bool `json:"checkForUpdates"` // true, false + LastUpdateCheck *int `json:"lastUpdateCheck"` // unix timestamp } func GetDefaultConfig() Config { @@ -55,6 +57,8 @@ func GetDefaultConfig() Config { defaultWindowScale := 100 defaultOpacity := 90 defaultWindowEffect := 0 + defaultCheckForUpdates := true + defaultLastUpdateCheck := 0 return Config{ Theme: &defaultTheme, @@ -77,6 +81,8 @@ func GetDefaultConfig() Config { WindowScale: &defaultWindowScale, Opacity: &defaultOpacity, WindowEffect: &defaultWindowEffect, + CheckForUpdates: &defaultCheckForUpdates, + LastUpdateCheck: &defaultLastUpdateCheck, } } diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index d8603b7..55417e6 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -5,6 +5,7 @@ "show_in_explorer": "Show in Explorer", "import": "Import", "export": "Export", + "error": "Error", "nav": { "my_packs": "My Packs", @@ -23,7 +24,8 @@ "general": "General", "application": "Application", "system": "System", - "advanced": "Advanced" + "advanced": "Advanced", + "update": "Update" }, "setting": { @@ -89,6 +91,26 @@ "save_window_status": { "label": "Save Window Status", "description": "Save window size, position, and state." + }, + + "check_for_updates": { + "label": "Check For Updates On Startup", + "description": "Check for updates on startup." + }, + + "update": { + "update_available": "Update available", + "no_updates_available": "No updates available", + "last_checked": "Last checked", + "check_for_updates": "Check for updates", + "update": "Update", + "updating": "Updating...", + "failed_to_check_for_updates": "Failed to check for updates. Please try again later.", + "failed_to_download_update": "Failed to download update. Please try again later.", + "failed_to_apply_update" : "Failed to apply update.", + "update_applied": "Update applied successfully.", + "restarting": "Restarting...", + "need_admin_privileges": "Administration privileges are needed to update the application." } } } diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index 8fa7301..8448899 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -5,6 +5,7 @@ "show_in_explorer": "Klasörü aç", "import": "İçe Aktar", "export": "Dışa Aktar", + "error": "Hata", "nav": { "my_packs": "Paketlerim", @@ -23,7 +24,8 @@ "general": "Genel", "application": "Uygulama", "system": "Sistem", - "advanced": "Gelişmiş" + "advanced": "Gelişmiş", + "update": "Güncelleme" }, "setting": { @@ -89,6 +91,26 @@ "save_window_status": { "label": "Pencere Durumunu Kaydet", "description": "Pencere boyutunu, konumunu ve durumunu kaydet." + }, + + "check_for_updates": { + "label": "Başlangıçta Güncellemeleri Kontrol Et", + "description": "Başlangıçta güncellemeleri kontrol et." + }, + + "update": { + "update_available": "Güncelleme mevcut", + "no_updates_available": "Güncelleme yok", + "last_checked": "Son kontrol", + "check_for_updates": "Güncellemeleri kontrol et", + "update": "Güncelle", + "updating": "Güncelleniyor...", + "failed_to_check_for_updates": "Güncellemeler kontrol edilemedi. Lütfen daha sonra tekrar deneyin.", + "failed_to_download_update": "Güncelleme indirilemedi. Lütfen daha sonra tekrar deneyin.", + "failed_to_apply_update" : "Güncelleme uygulanamadı.", + "update_applied": "Güncelleme uygulandı.", + "restarting": "Yeniden başlatılıyor...", + "need_admin_privileges": "Uygulamayı güncellemek için yönetici yetkileri gereklidir." } } } diff --git a/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx b/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx new file mode 100644 index 0000000..6cb11ce --- /dev/null +++ b/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx @@ -0,0 +1,14 @@ +import { SwitchConfig } from "./Presets/SwitchConfig"; +import { useTranslation } from "react-i18next"; + +export function CheckForUpdatesSetting() { + const { t } = useTranslation(); + + return ( + + ); +} diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index 2d57ba1..2e82c64 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -32,7 +32,9 @@ export function ImportExportSetting() { const handleAcceptImport = () => { LogDebug("Attempting to read config from " + usePath); - ReadConfig(usePath).then(RestartApplication); + ReadConfig(usePath).then(() => { + RestartApplication(false, []); + }); }; return ( diff --git a/frontend/src/components/SettingItems/UpdateSetting.tsx b/frontend/src/components/SettingItems/UpdateSetting.tsx new file mode 100644 index 0000000..8ed2dd4 --- /dev/null +++ b/frontend/src/components/SettingItems/UpdateSetting.tsx @@ -0,0 +1,146 @@ +import { useEffect, useState } from "react"; +import { CheckForUpdate, Update } from "wailsjs/go/main/App"; +import { main } from "wailsjs/go/models"; +import { Button } from "../ui/button"; +import { + SettingContent, + SettingDescription, + SettingLabel, + SettingsItem, +} from "../ui/settings-group"; +import { ArrowRight, RefreshCw, TriangleAlert } from "lucide-react"; +import { GetConfigField, NeedsAdminPrivileges } from "wailsjs/go/main/App"; +import { t } from "i18next"; + +export function UpdateSetting() { + const [updateInfo, setUpdateInfo] = useState( + {} as main.UpdateInfo + ); + + const [lastUpdateCheck, setLastUpdateCheck] = useState(0); + const [needsAdmin, setNeedsAdmin] = useState(false); + const [isChecking, setIsChecking] = useState(false); + const [isUpdating, setIsUpdating] = useState(false); + + const handleCheckForUpdate = () => { + setIsChecking(true); + CheckForUpdate() + .then((updateInfoJSON) => { + setUpdateInfo(updateInfoJSON); + }) + .finally(() => { + setTimeout(() => { + setIsChecking(false); + }, 200); + }); + }; + + const handleUpdate = () => { + setIsUpdating(true); + Update(updateInfo.downloadUrl).finally(() => { + setIsUpdating(false); + }); + }; + + useEffect(() => { + GetConfigField("LastUpdateCheck").then((value) => { + setLastUpdateCheck(parseInt(value)); + }); + }, [updateInfo]); + + useEffect(() => { + NeedsAdminPrivileges().then((value) => { + setNeedsAdmin(value); + }); + + handleCheckForUpdate(); + }, []); + + const formatDate = (timestamp: number) => { + const date = new Date(timestamp * 1000); + const day = String(date.getDate()).padStart(2, "0"); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const year = date.getFullYear(); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`; + }; + + return ( + + {needsAdmin && ( +
+ + {t("settings.setting.update.need_admin_privileges")} +
+ )} +
+ +
+ +
+ + {updateInfo.updateAvailable + ? t("settings.setting.update.update_available") + : t("settings.setting.update.no_updates_available")} + {lastUpdateCheck && lastUpdateCheck !== 0 && ( + + {" (" + + t("settings.setting.update.last_checked") + + ": " + + formatDate(lastUpdateCheck) + + ")"} + + )} + + + {updateInfo.updateAvailable && ( +
+ + v{updateInfo.currentVersion} + + + {updateInfo.latestVersion} +
+ )} + {!updateInfo.updateAvailable && ( + + v{updateInfo.currentVersion} + + )} + {!updateInfo.updateAvailable && } +
+
+
+
+
+ {updateInfo.updateAvailable && ( + + )} + +
+
+
+ {updateInfo.name} + + {updateInfo.releaseNotes} + +
+
+ ); +} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index c8a4532..b521bb9 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -12,6 +12,8 @@ import { ImportExportSetting } from "./SettingItems/ImportExportSetting"; import { WindowEffectSetting } from "./SettingItems/WindowEffectSetting"; import { WindowOpacitySetting } from "./SettingItems/WindowOpacitySetting"; import { SaveWindowStatusSetting } from "./SettingItems/SaveWindowStatusSetting"; +import { CheckForUpdatesSetting } from "./SettingItems/CheckForUpdatesSetting"; +import { UpdateSetting } from "./SettingItems/UpdateSetting"; export default function Settings() { const { t } = useTranslation(); @@ -31,6 +33,9 @@ export default function Settings() { {t("settings.categories.advanced")} + + {t("settings.categories.update")} + @@ -60,6 +65,12 @@ export default function Settings() { + + + + + + ); } diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index e5c74fa..14be4d8 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -50,7 +50,7 @@ export default function TitleBar() { ) : undefined, @@ -54,39 +61,53 @@ function App() { }); }; + window.goto = (path: string) => { + const pathArray = path.split("__"); + + setValue("tab", pathArray[0]); + + for (let i = 0; i < pathArray.length - 1; i++) { + setValue(pathArray[i], pathArray[i + 1]); + } + }; + + useEffect(() => { + setTab(getValue("tab") || "packs"); + }, [getValue("tab")]); + return ( - - - -
- - - -
- {t("nav.my_packs")} - {t("nav.edit")} - - {t("nav.settings")} - -
- -
+ +
+ + + +
+ setTab("packs")}> + {t("nav.my_packs")} + + setTab("edit")}> + {t("nav.edit")} + + setTab("settings")}> + {t("nav.settings")} + +
+ +
- - View your packs here. - - - Edit your packs here. - - - - -
-
- - - {" "} - + + View your packs here. + + + Edit your packs here. + + + + +
+
+ + ); } diff --git a/frontend/src/components/SettingItems/UpdateSetting.tsx b/frontend/src/components/SettingItems/UpdateSetting.tsx index 8ed2dd4..d9a06fb 100644 --- a/frontend/src/components/SettingItems/UpdateSetting.tsx +++ b/frontend/src/components/SettingItems/UpdateSetting.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { CheckForUpdate, Update } from "wailsjs/go/main/App"; +import { CheckForUpdate, Update, UpdateAsAdmin } from "wailsjs/go/main/App"; import { main } from "wailsjs/go/models"; import { Button } from "../ui/button"; import { @@ -8,11 +8,14 @@ import { SettingLabel, SettingsItem, } from "../ui/settings-group"; -import { ArrowRight, RefreshCw, TriangleAlert } from "lucide-react"; +import { ArrowRight, RefreshCw } from "lucide-react"; import { GetConfigField, NeedsAdminPrivileges } from "wailsjs/go/main/App"; import { t } from "i18next"; +import { useStorage } from "@/contexts/storage-provider"; export function UpdateSetting() { + const { getValue } = useStorage(); + const [updateInfo, setUpdateInfo] = useState( {} as main.UpdateInfo ); @@ -23,23 +26,32 @@ export function UpdateSetting() { const [isUpdating, setIsUpdating] = useState(false); const handleCheckForUpdate = () => { - setIsChecking(true); - CheckForUpdate() - .then((updateInfoJSON) => { - setUpdateInfo(updateInfoJSON); - }) - .finally(() => { - setTimeout(() => { - setIsChecking(false); - }, 200); - }); + if (!isUpdating) { + setIsChecking(true); + CheckForUpdate() + .then((updateInfoJSON) => { + setUpdateInfo(updateInfoJSON); + }) + .finally(() => { + setTimeout(() => { + setIsChecking(false); + }, 200); + }); + } }; const handleUpdate = () => { setIsUpdating(true); - Update(updateInfo.downloadUrl).finally(() => { - setIsUpdating(false); - }); + + if (needsAdmin) { + UpdateAsAdmin(updateInfo.downloadUrl).finally(() => { + setIsUpdating(false); + }); + } else { + Update(updateInfo.downloadUrl).finally(() => { + setIsUpdating(false); + }); + } }; useEffect(() => { @@ -56,6 +68,17 @@ export function UpdateSetting() { handleCheckForUpdate(); }, []); + useEffect(() => { + if (getValue("update") === undefined) return; + + while (isChecking) setTimeout(() => {}, 100); + + setIsUpdating(true); + Update(getValue("update")).finally(() => { + setIsUpdating(false); + }); + }, [getValue("update")]); + const formatDate = (timestamp: number) => { const date = new Date(timestamp * 1000); const day = String(date.getDate()).padStart(2, "0"); @@ -69,12 +92,6 @@ export function UpdateSetting() { return ( - {needsAdmin && ( -
- - {t("settings.setting.update.need_admin_privileges")} -
- )}
@@ -120,11 +137,7 @@ export function UpdateSetting() {
{updateInfo.updateAvailable && ( - @@ -75,6 +80,10 @@ function App() { setTab(getValue("tab") || "packs"); }, [getValue("tab")]); + useEffect(() => { + setValue("path1", tab); + }, [tab]); + return (
diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 4475dd4..5ccb1fc 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -20,28 +20,52 @@ import { useStorage } from "@/contexts/storage-provider"; export default function Settings() { const { t } = useTranslation(); const [tab, setTab] = useState("general"); - const { getValue } = useStorage(); + const { getValue, setValue } = useStorage(); useEffect(() => { setTab(getValue("settings") || "general"); }, [getValue("settings")]); + useEffect(() => { + setValue("path2", tab); + }, [tab]); + return ( - setTab("general")} className="px-12 py-2 w-full"> + setTab("general")} + className="px-12 py-2 w-full" + > {t("settings.categories.general")} - setTab("app")} className="px-12 py-2 w-full"> + setTab("app")} + className="px-12 py-2 w-full" + > {t("settings.categories.application")} - setTab("system")} className="px-12 py-2 w-full"> + setTab("system")} + className="px-12 py-2 w-full" + > {t("settings.categories.system")} - setTab("advanced")} className="px-12 py-2 w-full"> + setTab("advanced")} + className="px-12 py-2 w-full" + > {t("settings.categories.advanced")} - setTab("update")} className="px-12 py-2 w-full"> + setTab("update")} + className="px-12 py-2 w-full" + > {t("settings.categories.update")} diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 14be4d8..d526696 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -10,10 +10,12 @@ import { Button } from "@/components/ui/button"; import icon from "../assets/appicon.png"; import { useEffect, useState } from "react"; import { useRestart } from "@/contexts/restart-provider"; +import { useStorage } from "@/contexts/storage-provider"; export default function TitleBar() { const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); const { restartRequired } = useRestart(); + const { getValue } = useStorage(); useEffect(() => { GetConfigField("UseSystemTitleBar").then((value) => { @@ -50,7 +52,15 @@ export default function TitleBar() { - - ) : undefined, - variant: variant, - }); + action: path + ? { + label: path.startsWith("__") ? t("show") : t("show_in_explorer"), + onClick: () => handleToastGotoPath(path), + } + : undefined, + }; + switch (variant) { + case "message": + toast.message(t(title), props); + break; + case "success": + toast.success(t(title), props); + break; + case "info": + toast.info(t(title), props); + break; + case "warning": + toast.warning(t(title), props); + break; + case "error": + toast.error(t(title), props); + break; + default: + toast(t(title), props); + break; + } + }; + + const handleToastGotoPath = (path: string) => { + if (path.startsWith("__")) { + window.goto(path.substring(2)); + } else { + OpenFileInExplorer(path); + } }; window.goto = (path: string) => { @@ -115,7 +126,7 @@ function App() {
- +
); } diff --git a/frontend/src/components/ui/sonner.tsx b/frontend/src/components/ui/sonner.tsx new file mode 100644 index 0000000..1128edf --- /dev/null +++ b/frontend/src/components/ui/sonner.tsx @@ -0,0 +1,29 @@ +import { useTheme } from "next-themes" +import { Toaster as Sonner } from "sonner" + +type ToasterProps = React.ComponentProps + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme() + + return ( + + ) +} + +export { Toaster } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e1e8811..bee513c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2168,6 +2168,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +next-themes@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.3.0.tgz#b4d2a866137a67d42564b07f3a3e720e2ff3871a" + integrity sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w== + node-fetch@^2.6.12: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -2582,6 +2587,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +sonner@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.5.0.tgz#af359f817063318415326b33aab54c5d17c747b7" + integrity sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA== + source-map-js@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" diff --git a/update.go b/update.go index 2bd4e90..43633a5 100644 --- a/update.go +++ b/update.go @@ -54,6 +54,7 @@ func (app *App) CheckForUpdate() UpdateInfo { resp, err := http.Get(apiUrl) if err != nil { runtime.LogError(app.ctx, "Error sending request: "+err.Error()) + app.SendNotification("settings.setting.update.failed_to_check_for_updates", "", "", "error") return updateInfo } defer resp.Body.Close() @@ -61,7 +62,7 @@ func (app *App) CheckForUpdate() UpdateInfo { // Check if response was successful if resp.StatusCode != http.StatusOK { - app.SendNotification("error", "settings.setting.update.failed_to_check_for_updates", "", "destructive") + app.SendNotification("settings.setting.update.failed_to_check_for_updates", "", "", "error") return updateInfo } @@ -111,7 +112,7 @@ func (app *App) CheckForUpdate() UpdateInfo { runtime.LogDebug(app.ctx, fmt.Sprintf("Download URL: %s", downloadUrl)) // Check if a new version is available - if parsedVersion.Compare(parsedLatestVersion) < 0 && !prerelease { + if parsedVersion.Compare(parsedLatestVersion) <= 0 && !prerelease { runtime.LogInfo(app.ctx, fmt.Sprintf("A new version (%s) is available.", latestVersion)) updateInfo.UpdateAvailable = true updateInfo.LatestVersion = latestVersion @@ -132,7 +133,7 @@ func (app *App) Update(downloadUrl string) error { resp, err := http.Get(downloadUrl) if err != nil { runtime.LogError(app.ctx, "Error downloading update: "+err.Error()) - app.SendNotification("error", "settings.setting.update.failed_to_download_update", "", "destructive") + app.SendNotification("settings.setting.update.failed_to_download_update", "", "", "error") return err } defer resp.Body.Close() @@ -142,7 +143,7 @@ func (app *App) Update(downloadUrl string) error { // Check if the response was successful if resp.StatusCode != http.StatusOK { - app.SendNotification("error", "settings.setting.update.failed_to_download_update", "", "destructive") + app.SendNotification("settings.setting.update.failed_to_download_update", "", "", "error") return err } @@ -150,12 +151,12 @@ func (app *App) Update(downloadUrl string) error { err = selfupdate.Apply(resp.Body, selfupdate.Options{}) if err != nil { runtime.LogError(app.ctx, "Error applying update: "+err.Error()) - app.SendNotification("settings.setting.update.failed_to_apply_update", err.Error(), "", "destructive") + app.SendNotification("settings.setting.update.failed_to_apply_update", err.Error(), "", "error") return err } runtime.LogInfo(app.ctx, "Update applied successfully. Restarting.") - app.SendNotification("settings.setting.update.update_applied", "settings.setting.update.restarting", "", "") + app.SendNotification("settings.setting.update.update_applied", "settings.setting.update.restarting", "", "success") // Restart the application app.RestartApplication(false, []string{}) From 11023ceda030e7d14a0644750abb8d413900c836 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 2 Jul 2024 02:35:15 +0300 Subject: [PATCH 090/221] save tab on import config --- frontend/src/components/SettingItems/ImportExportSetting.tsx | 2 +- update.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index 2e82c64..8e34b9f 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -33,7 +33,7 @@ export function ImportExportSetting() { const handleAcceptImport = () => { LogDebug("Attempting to read config from " + usePath); ReadConfig(usePath).then(() => { - RestartApplication(false, []); + RestartApplication(false, ["--goto", "settings__advanced"]); }); }; diff --git a/update.go b/update.go index 43633a5..44911a8 100644 --- a/update.go +++ b/update.go @@ -112,7 +112,7 @@ func (app *App) CheckForUpdate() UpdateInfo { runtime.LogDebug(app.ctx, fmt.Sprintf("Download URL: %s", downloadUrl)) // Check if a new version is available - if parsedVersion.Compare(parsedLatestVersion) <= 0 && !prerelease { + if parsedVersion.Compare(parsedLatestVersion) < 0 && !prerelease { runtime.LogInfo(app.ctx, fmt.Sprintf("A new version (%s) is available.", latestVersion)) updateInfo.UpdateAvailable = true updateInfo.LatestVersion = latestVersion From 3691c4fd4a739019ce2bac57e3dac75da9e3122a Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 2 Jul 2024 17:13:45 +0300 Subject: [PATCH 091/221] restart at correct page after update --- update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.go b/update.go index 44911a8..e438dfc 100644 --- a/update.go +++ b/update.go @@ -159,7 +159,7 @@ func (app *App) Update(downloadUrl string) error { app.SendNotification("settings.setting.update.update_applied", "settings.setting.update.restarting", "", "success") // Restart the application - app.RestartApplication(false, []string{}) + app.RestartApplication(false, []string{"--goto", "settings__update"}) return nil } From f17d9d175fc7dbe2424cb63c972b45bf71ad1e5e Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 2 Jul 2024 22:59:14 +0300 Subject: [PATCH 092/221] update frontend config --- app.go | 2 +- config.go | 68 +++++++++--- frontend/src/App.tsx | 44 ++++---- .../SettingItems/CheckForUpdatesSetting.tsx | 2 +- .../SettingItems/EnableLoggingSetting.tsx | 4 +- .../components/SettingItems/LocaleSetting.tsx | 17 ++- .../SettingItems/LogLevelSetting.tsx | 105 ++++++++++-------- .../SettingItems/MaxLogFilesSetting.tsx | 31 +++--- .../SettingItems/Presets/SwitchConfig.tsx | 40 ++++--- .../SettingItems/SaveWindowStatusSetting.tsx | 2 +- .../SettingItems/UseSystemTitleBarSetting.tsx | 2 +- .../SettingItems/WindowEffectSetting.tsx | 79 ++++++++----- .../SettingItems/WindowOpacitySetting.tsx | 17 ++- .../SettingItems/WindowScaleSetting.tsx | 17 +-- frontend/src/components/TitleBar.tsx | 11 +- frontend/src/components/ui/settings-group.tsx | 38 +++++-- frontend/src/contexts/config-provider.tsx | 67 +++++++++++ frontend/src/contexts/theme-provider.tsx | 51 ++++----- frontend/src/lib/config.ts | 81 -------------- frontend/src/main.tsx | 17 +-- frontend/wailsjs/go/main/App.d.ts | 4 +- frontend/wailsjs/go/main/App.js | 4 + frontend/wailsjs/go/models.ts | 54 +++++++++ 23 files changed, 443 insertions(+), 314 deletions(-) create mode 100644 frontend/src/contexts/config-provider.tsx delete mode 100644 frontend/src/lib/config.ts diff --git a/app.go b/app.go index 1ce816d..6171cad 100644 --- a/app.go +++ b/app.go @@ -33,7 +33,7 @@ func (a *App) startup(ctx context.Context) { runtime.LogInfo(appContext, "Starting application") // Set window position - if *config.WindowStartPositionX >= 0 && *config.WindowStartPositionY >= 0 { + if *config.WindowStartPositionX != -100000 && *config.WindowStartPositionY != -100000 { runtime.LogInfo(appContext, "Setting window position") runtime.WindowSetPosition(appContext, *config.WindowStartPositionX, *config.WindowStartPositionY) } diff --git a/config.go b/config.go index ca058fb..2e229d8 100644 --- a/config.go +++ b/config.go @@ -50,10 +50,10 @@ func GetDefaultConfig() Config { defaultLanguage := "en-US" defaultSaveWindowStatus := true defaultWindowStartState := 0 - defaultWindowStartPositionX := -1 - defaultWindowStartPositionY := -1 - defaultWindowStartSizeX := -1 - defaultWindowStartSizeY := -1 + defaultWindowStartPositionX := -100000 + defaultWindowStartPositionY := -100000 + defaultWindowStartSizeX := -100000 + defaultWindowStartSizeY := -100000 defaultWindowScale := 100 defaultOpacity := 90 defaultWindowEffect := 0 @@ -124,6 +124,10 @@ func merge_defaults() { } } +func (app *App) GetConfig() Config { + return config +} + func (app *App) GetConfigField(fieldName string) string { runtime.LogDebug(app.ctx, fmt.Sprintf("Attempting to get config field %s", fieldName)) @@ -155,8 +159,8 @@ func (app *App) GetConfigField(fieldName string) string { return fmt.Sprintf("%v", fieldValue.Interface()) } -func (app *App) SetConfigField(fieldName string, value string) { - runtime.LogDebug(app.ctx, fmt.Sprintf("Attempting to set config field %s to %s", fieldName, value)) +func (app *App) SetConfigField(fieldName string, value interface{}) { + runtime.LogDebug(app.ctx, fmt.Sprintf("Attempting to set config field %s to %v", fieldName, value)) v := reflect.ValueOf(&config).Elem() t := v.Type() @@ -169,32 +173,62 @@ func (app *App) SetConfigField(fieldName string, value string) { fieldValue := v.FieldByName(fieldName) + if !fieldValue.IsValid() { + runtime.LogWarning(app.ctx, fmt.Sprintf("Invalid field: %s", fieldName)) + return + } + if fieldValue.Kind() == reflect.Ptr { - if fieldValue.IsNil() { - fieldValue.Set(reflect.New(fieldValue.Type().Elem())) - } + runtime.LogDebug(app.ctx, fmt.Sprintf("Dereferencing config field %s", fieldName)) fieldValue = fieldValue.Elem() } + runtime.LogDebug(app.ctx, fmt.Sprintf("Config field %s type: %v", fieldName, fieldValue.Kind())) + switch fieldValue.Kind() { case reflect.String: - fieldValue.SetString(value) + strVal, ok := value.(string) + if !ok { + runtime.LogWarning(app.ctx, fmt.Sprintf("Invalid value type for string field %s: %v", fieldName, value)) + return + } + fieldValue.SetString(strVal) + case reflect.Bool: - boolVal, err := strconv.ParseBool(value) - if err != nil { - runtime.LogError(app.ctx, fmt.Sprintf("Invalid value for boolean field %s: %s", fieldName, value)) + boolVal, ok := value.(bool) + if !ok { + runtime.LogWarning(app.ctx, fmt.Sprintf("Invalid value type for boolean field %s: %v", fieldName, value)) return } fieldValue.SetBool(boolVal) - case reflect.Int: - intVal, err := strconv.Atoi(value) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + intVal, err := strconv.Atoi(fmt.Sprintf("%v", value)) if err != nil { - runtime.LogError(app.ctx, fmt.Sprintf("Invalid value for integer field %s: %s", fieldName, value)) + runtime.LogWarning(app.ctx, fmt.Sprintf("Invalid value type for integer field %s: %v", fieldName, value)) return } fieldValue.SetInt(int64(intVal)) + + case reflect.Float32, reflect.Float64: + floatVal, ok := value.(float64) + if !ok { + runtime.LogWarning(app.ctx, fmt.Sprintf("Invalid value type for float field %s: %v", fieldName, value)) + return + } + fieldValue.SetFloat(floatVal) + + case reflect.Slice: + sliceVal, ok := value.([]string) + if !ok { + runtime.LogWarning(app.ctx, fmt.Sprintf("Invalid value type for slice field %s: %v", fieldName, value)) + return + } + slice := reflect.ValueOf(sliceVal) + fieldValue.Set(slice) + default: - runtime.LogError(app.ctx, fmt.Sprintf("Unsupported field type for field %s", fieldName)) + runtime.LogWarning(app.ctx, fmt.Sprintf("Unsupported field type for field %s of type %s", fieldName, fieldValue.Kind())) return } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 150c51b..75f8c6a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,41 +1,42 @@ -import { GetConfigField, InitConfigCache } from "@/lib/config"; import ModeToggle from "@/components/ModeToggle"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import TitleBar from "./components/TitleBar"; import Settings from "./components/Settings"; import { useTranslation } from "react-i18next"; -import { useEffect, useState } from "react"; +import { useEffect, useLayoutEffect, useState } from "react"; import { useStorage } from "./contexts/storage-provider"; import { Toaster } from "./components/ui/sonner"; import { toast } from "sonner"; import { OpenFileInExplorer } from "wailsjs/go/main/App"; import React from "react"; +import { useConfig } from "./contexts/config-provider"; +import { LogDebug } from "wailsjs/runtime/runtime"; function App() { + const { config, initialConfig } = useConfig(); const { t } = useTranslation(); const { setValue, getValue } = useStorage(); const [tab, setTab] = useState("packs"); - useEffect(() => { - InitConfigCache(); + useLayoutEffect(() => { + if ( + config && + initialConfig && + config.windowScale !== undefined && + config.opacity !== undefined && + initialConfig.windowEffect !== undefined + ) { + document.documentElement.style.fontSize = + config.windowScale * (16 / 100) + "px"; - Promise.all([ - GetConfigField("WindowScale"), - GetConfigField("Opacity"), - GetConfigField("WindowEffect"), - ]) - .then(([windowScaleValue, opacityValue, windowEffectValue]) => { - document.documentElement.style.fontSize = - Number(windowScaleValue) * (16 / 100) + "px"; - document.documentElement.style.setProperty( - "--opacity", - String(Number(windowEffectValue === "1" ? "100" : opacityValue) / 100) - ); - }) - .catch((error) => { - console.error("Error fetching configuration:", error); - }); - }, []); + document.documentElement.style.setProperty( + "--opacity", + ( + (initialConfig.windowEffect === 1 ? 100 : config.opacity) / 100 + ).toString() + ); + } + }, [config?.windowScale, config?.opacity, initialConfig?.windowEffect]); window.toast = ({ title, description, path, variant }: any) => { const props = { @@ -78,6 +79,7 @@ function App() { }; window.goto = (path: string) => { + LogDebug("window.goto: " + path); const pathArray = path.split("__"); setValue("tab", pathArray[0]); diff --git a/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx b/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx index 6cb11ce..d165510 100644 --- a/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx +++ b/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx @@ -6,7 +6,7 @@ export function CheckForUpdatesSetting() { return ( diff --git a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx index 0642710..1ffc73a 100644 --- a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx +++ b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx @@ -6,10 +6,10 @@ export function EnableLoggingSetting() { return ( ); } diff --git a/frontend/src/components/SettingItems/LocaleSetting.tsx b/frontend/src/components/SettingItems/LocaleSetting.tsx index 5814670..6913e92 100644 --- a/frontend/src/components/SettingItems/LocaleSetting.tsx +++ b/frontend/src/components/SettingItems/LocaleSetting.tsx @@ -1,4 +1,3 @@ -import { SetConfigField, GetConfigField } from "@/lib/config"; import { SettingsItem, SettingContent, @@ -9,24 +8,24 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Combobox } from "../ui/combobox"; import locales from "@/locales.json"; +import { useConfig } from "@/contexts/config-provider"; export function LocaleSetting() { + const { config, setConfigField } = useConfig(); const { t, i18n } = useTranslation(); const [isLoading, setIsLoading] = useState(true); const [language, setLanguage] = useState("en-US"); useEffect(() => { - GetConfigField("Language").then((value) => { - setLanguage(value); + if (config && config.language !== undefined && isLoading) { + setLanguage(config.language); setIsLoading(false); - }); - }, []); + } + }, [config?.language]); const handleLanguageChange = (value: string) => { - SetConfigField("Language", value).then(() => { - setLanguage(value); - i18n.changeLanguage(value); - }); + setConfigField("language", value); + i18n.changeLanguage(value); }; return ( diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx index 146df05..564ddb1 100644 --- a/frontend/src/components/SettingItems/LogLevelSetting.tsx +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -7,74 +7,81 @@ import { import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { GetConfigField, SetConfigField } from "@/lib/config"; -import { useStorage } from "@/contexts/storage-provider"; +import { useConfig } from "@/contexts/config-provider"; type LogLevels = { - trace: boolean; - debug: boolean; - info: boolean; - warn: boolean; - error: boolean; - fatal: boolean; + enableTrace: boolean; + enableDebug: boolean; + enableInfo: boolean; + enableWarn: boolean; + enableError: boolean; + enableFatal: boolean; }; export function LogLevelSetting() { + const { config, setConfigField } = useConfig(); const { t } = useTranslation(); - const { getValue, setValueIfUndefined } = useStorage(); const [isLoading, setIsLoading] = useState(true); const [logLevels, setLogLevels] = useState({ - trace: false, - debug: false, - info: false, - warn: false, - error: false, - fatal: false, + enableTrace: false, + enableDebug: false, + enableInfo: false, + enableWarn: false, + enableError: false, + enableFatal: false, }); useEffect(() => { - const fields = [ - "EnableTrace", - "EnableDebug", - "EnableInfo", - "EnableWarn", - "EnableError", - "EnableFatal", - ]; - Promise.all(fields.map(GetConfigField)).then((values) => { - const newLogLevels: LogLevels = { - trace: values[0] === "true", - debug: values[1] === "true", - info: values[2] === "true", - warn: values[3] === "true", - error: values[4] === "true", - fatal: values[5] === "true", - }; - setLogLevels(newLogLevels); - setValueIfUndefined( - "initialLogLevels", - Object.values(newLogLevels).join("") - ); + if ( + config && + config.enableTrace !== undefined && + config.enableDebug !== undefined && + config.enableInfo !== undefined && + config.enableWarn !== undefined && + config.enableError !== undefined && + config.enableFatal !== undefined && + isLoading + ) { + setLogLevels({ + enableTrace: config.enableTrace, + enableDebug: config.enableDebug, + enableInfo: config.enableInfo, + enableWarn: config.enableWarn, + enableError: config.enableError, + enableFatal: config.enableFatal, + }); setIsLoading(false); - }); - }, []); + } + }, [ + config?.enableTrace, + config?.enableDebug, + config?.enableInfo, + config?.enableWarn, + config?.enableError, + config?.enableFatal, + ]); const handleToggle = (level: keyof LogLevels) => { - SetConfigField( - `Enable${level.charAt(0).toUpperCase() + level.slice(1)}`, - String(!logLevels[level]) - ).then(() => { - setLogLevels({ ...logLevels, [level]: !logLevels[level] }); + setConfigField(level, !logLevels[level]); + + setLogLevels({ + ...logLevels, + [level]: !logLevels[level], }); }; return (
{t("settings.setting.log_levels.label")} @@ -93,10 +100,10 @@ export function LogLevelSetting() { handleToggle(level as keyof LogLevels)} > - {level.charAt(0).toUpperCase() + level.slice(1)} + {level.replace("enable", "")} ))} diff --git a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx index 4ccfa6f..309efb1 100644 --- a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx +++ b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx @@ -7,38 +7,33 @@ import { import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Input } from "../ui/input"; -import { GetConfigField, SetConfigField } from "@/lib/config"; -import { useStorage } from "@/contexts/storage-provider"; +import { useConfig } from "@/contexts/config-provider"; export function MaxLogFilesSetting() { + const { config, setConfigField } = useConfig(); const { t } = useTranslation(); - const { getValue, setValueIfUndefined } = useStorage(); const [isLoading, setIsLoading] = useState(true); - const [maxLogFiles, setMaxLogFiles] = useState(-1); + const [maxLogFiles, setMaxLogFiles] = useState(""); useEffect(() => { - GetConfigField("MaxLogFiles").then((value) => { - setMaxLogFiles(parseInt(value)); - setValueIfUndefined("initialMaxLogFiles", value); + if (config && config.maxLogFiles !== undefined && isLoading) { + setMaxLogFiles(config.maxLogFiles.toString()); + setIsLoading(false); - }); - }, []); + } + }, [config?.maxLogFiles]); const handleMaxLogFilesChange = (textValue: string) => { const value = Math.max(1, Math.min(10000, parseInt(textValue))); const targetValue = isNaN(parseInt(textValue)) ? 20 : value; - SetConfigField("MaxLogFiles", String(targetValue)).then(() => { - setMaxLogFiles(value); - }); + + setConfigField("maxLogFiles", targetValue); + if (textValue === "") setMaxLogFiles(""); + else setMaxLogFiles(value.toString()); }; return ( - +
{t("settings.setting.max_log_files.label")} diff --git a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx index 69b16d9..f7ccf61 100644 --- a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx +++ b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx @@ -6,53 +6,59 @@ import { } from "@/components/ui/settings-group"; import { Switch } from "@/components/ui/switch"; import { useEffect, useState } from "react"; -import { GetConfigField, SetConfigField } from "@/lib/config"; -import { useStorage } from "@/contexts/storage-provider"; import { useTranslation } from "react-i18next"; +import { useConfig } from "@/contexts/config-provider"; +import { main } from "wailsjs/go/models"; interface SwitchConfigProps { - configValue: string; + configKey: keyof main.Config; label: string; description?: string; requiresRestart?: boolean; } export function SwitchConfig({ - configValue, + configKey, label, description, requiresRestart, }: SwitchConfigProps) { + const { config, setConfigField } = useConfig(); const { t } = useTranslation(); - const { getValue, setValueIfUndefined } = useStorage(); const [isLoading, setIsLoading] = useState(true); const [switchValue, setSwitchValue] = useState(false); useEffect(() => { - GetConfigField(configValue).then((value) => { - setSwitchValue(value === "true"); - if (requiresRestart) setValueIfUndefined(`initial${configValue}`, value); + if (config && config[configKey] !== undefined && isLoading) { + const value = config[configKey] as boolean; + setSwitchValue(value); setIsLoading(false); - }); - }, []); + } + }, [config]); const handleSwitch = (value: boolean) => { - SetConfigField(configValue, String(value)).then(() => { - setSwitchValue(value); - }); + setConfigField(configKey, value); + setSwitchValue(value); }; return (
{label} - {description && {description + (requiresRestart ? " (" + t("settings.restart_the_app_for_changes_to_take_effect") + ")" : "")}} + {description && ( + + {description + + (requiresRestart + ? " (" + + t("settings.restart_the_app_for_changes_to_take_effect") + + ")" + : "")} + + )}
diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx index 34f361f..d427a2b 100644 --- a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -6,7 +6,7 @@ export function UseSystemTitleBarSetting() { return ( { - GetConfigField("WindowEffect").then((windowEffectValue) => { - setUseWindowEffect(windowEffectValue); - setValueIfUndefined("initialWindowEffect", windowEffectValue); - + if (config && config.windowEffect !== undefined && isLoading) { + setUseWindowEffect(config.windowEffect.toString()); + setIsLoading(false); - }); - }, []); + } + }, [config?.windowEffect]); - const handleToggle = (value: string) => { - SetConfigField("WindowEffect", value).then(() => { - setUseWindowEffect(value); - }); + const handleToggle = (number: number) => { + setConfigField("windowEffect", number); + setUseWindowEffect(number.toString()); }; const windowEffectOptions = [ - { value: "1", label: t("settings.setting.window_effect.none"), aria: "No window effect" }, - { value: "0", label: t("settings.setting.window_effect.auto"), aria: "Auto window effect" }, - { value: "2", label: t("settings.setting.window_effect.mica"), aria: "Mica window effect" }, - { value: "3", label: t("settings.setting.window_effect.acrylic"), aria: "Acrylic window effect" }, - { value: "4", label: t("settings.setting.window_effect.tabbed"), aria: "Tabbed window effect" } + { + value: "1", + number: 1, + label: t("settings.setting.window_effect.none"), + aria: "No window effect", + }, + { + value: "0", + number: 0, + label: t("settings.setting.window_effect.auto"), + aria: "Auto window effect", + }, + { + value: "2", + number: 2, + label: t("settings.setting.window_effect.mica"), + aria: "Mica window effect", + }, + { + value: "3", + number: 3, + label: t("settings.setting.window_effect.acrylic"), + aria: "Acrylic window effect", + }, + { + value: "4", + number: 4, + label: t("settings.setting.window_effect.tabbed"), + aria: "Tabbed window effect", + }, ]; return ( - +
{t("settings.setting.window_effect.label")} - {`${t("settings.setting.window_effect.description")} (${t("settings.restart_the_app_for_changes_to_take_effect")})`} + {`${t("settings.setting.window_effect.description")} (${t( + "settings.restart_the_app_for_changes_to_take_effect" + )})`}
- {windowEffectOptions.map(({ value, label, aria }) => ( + {windowEffectOptions.map(({ value, number, label, aria }) => ( handleToggle(value)} - className={value === getValue("initialWindowEffect") ? "font-bold text-shadow-xl shadow-foreground" : ""} + onClick={() => handleToggle(number)} + className={ + number === initialConfig?.windowEffect + ? "font-bold text-shadow-xl shadow-foreground" + : "" + } > {label} diff --git a/frontend/src/components/SettingItems/WindowOpacitySetting.tsx b/frontend/src/components/SettingItems/WindowOpacitySetting.tsx index b05a2b7..aeb4007 100644 --- a/frontend/src/components/SettingItems/WindowOpacitySetting.tsx +++ b/frontend/src/components/SettingItems/WindowOpacitySetting.tsx @@ -1,4 +1,3 @@ -import { GetConfigField, SetConfigField } from "@/lib/config"; import { SettingsItem, SettingContent, @@ -8,21 +7,21 @@ import { import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Slider } from "../ui/my-slider"; -import { useStorage } from "@/contexts/storage-provider"; +import { useConfig } from "@/contexts/config-provider"; export function WindowOpacitySetting() { + const { config, initialConfig, setConfigField } = useConfig(); const { t } = useTranslation(); - const { getValue } = useStorage(); const [isLoading, setIsLoading] = useState(true); const [useOpacity, setUseOpacity] = useState(-1); useEffect(() => { - GetConfigField("Opacity").then((value) => { - setUseOpacity(parseInt(value)); + if (config && config.opacity !== undefined && isLoading) { + setUseOpacity(config.opacity); setIsLoading(false); - }); - }, []); + } + }, [config?.opacity]); const handleOpacityChange = (value: number) => { document.documentElement.style.setProperty( @@ -33,14 +32,14 @@ export function WindowOpacitySetting() { }; const saveOpacityChange = () => { - SetConfigField("Opacity", useOpacity.toString()); + setConfigField("opacity", useOpacity); }; return (
diff --git a/frontend/src/components/SettingItems/WindowScaleSetting.tsx b/frontend/src/components/SettingItems/WindowScaleSetting.tsx index 6cb11fb..4b2ad73 100644 --- a/frontend/src/components/SettingItems/WindowScaleSetting.tsx +++ b/frontend/src/components/SettingItems/WindowScaleSetting.tsx @@ -1,4 +1,3 @@ -import { GetConfigField, SetConfigField } from "@/lib/config"; import { SettingsItem, SettingContent, @@ -8,23 +7,25 @@ import { import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Slider } from "../ui/my-slider"; +import { useConfig } from "@/contexts/config-provider"; export function WindowScaleSetting() { + const { config, setConfigField } = useConfig(); const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(true); const [useScale, setUseScale] = useState(-1); useEffect(() => { - GetConfigField("WindowScale").then((value) => { - setUseScale(parseInt(value)); + if (config && config.windowScale !== undefined && isLoading) { + setUseScale(config.windowScale); + setIsLoading(false); - }); - }, []); + } + }, [config?.windowScale]); const handleScaleSave = () => { - SetConfigField("WindowScale", String(useScale)).then(() => { - document.documentElement.style.fontSize = useScale * (16 / 100) + "px"; - }); + setConfigField("windowScale", useScale); + document.documentElement.style.fontSize = useScale * (16 / 100) + "px"; }; return ( diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index d526696..830439b 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -1,4 +1,3 @@ -import { GetConfigField } from "@/lib/config"; import { RestartApplication } from "wailsjs/go/main/App"; import { WindowMinimise, @@ -11,17 +10,19 @@ import icon from "../assets/appicon.png"; import { useEffect, useState } from "react"; import { useRestart } from "@/contexts/restart-provider"; import { useStorage } from "@/contexts/storage-provider"; +import { useConfig } from "@/contexts/config-provider"; export default function TitleBar() { + const { config } = useConfig(); const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); const { restartRequired } = useRestart(); const { getValue } = useStorage(); useEffect(() => { - GetConfigField("UseSystemTitleBar").then((value) => { - setUseSystemTitleBar(value === "true"); - }); - }, []); + if (config && config.useSystemTitleBar !== undefined) { + setUseSystemTitleBar(config.useSystemTitleBar); + } + }, [config?.useSystemTitleBar]); return ( !useSystemTitleBar && ( diff --git a/frontend/src/components/ui/settings-group.tsx b/frontend/src/components/ui/settings-group.tsx index bd5c446..329436d 100644 --- a/frontend/src/components/ui/settings-group.tsx +++ b/frontend/src/components/ui/settings-group.tsx @@ -1,6 +1,8 @@ import React, { ReactNode, useEffect } from "react"; import { Skeleton } from "@/components/ui/skeleton" import { useRestart } from "@/contexts/restart-provider"; +import { main } from "wailsjs/go/models"; +import { useConfig } from "@/contexts/config-provider"; interface SettingsComponentProps extends React.HTMLAttributes { children: ReactNode; @@ -15,23 +17,39 @@ interface SettingsItemProps extends React.HTMLAttributes { vertical?: boolean; disabled?: boolean; children: ReactNode; - initialValue?: any; - value?: any; - name?: string; + configKey?: keyof main.Config | (keyof main.Config)[]; } -export const SettingsItem: React.FC = ({ children, className, disabled, loading, vertical, initialValue, name, value, ...rest }) => { +export const SettingsItem: React.FC = ({ children, className, disabled, loading, vertical, configKey, ...rest }) => { const { addRestartRequired, removeRestartRequired } = useRestart(); + const {config, initialConfig} = useConfig(); useEffect(() => { - if(initialValue && value && name && !loading){ - if(value === initialValue){ - removeRestartRequired(name); - }else{ - addRestartRequired(name); + if (configKey && config && initialConfig && !loading) { + if (Array.isArray(configKey)) { + // Handle array of keys + configKey.forEach((k) => { + const configValue = config[k as keyof main.Config]; + const initialValue = initialConfig[k as keyof main.Config]; + if (configValue === initialValue) { + removeRestartRequired(k as keyof main.Config); + } else { + addRestartRequired(k as keyof main.Config); + } + }); + } else { + // Handle single key + const k = configKey as keyof main.Config; + const configValue = config[k]; + const initialValue = initialConfig[k]; + if (configValue === initialValue) { + removeRestartRequired(k); + } else { + addRestartRequired(k); + } } } - }, [initialValue, value]); + }, [config]); if (loading) { return diff --git a/frontend/src/contexts/config-provider.tsx b/frontend/src/contexts/config-provider.tsx new file mode 100644 index 0000000..2dcf8f4 --- /dev/null +++ b/frontend/src/contexts/config-provider.tsx @@ -0,0 +1,67 @@ +// src/ConfigContext.tsx +import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; +import { main } from 'wailsjs/go/models'; +import { GetConfig, SetConfigField as SetConfigField_backend } from 'wailsjs/go/main/App'; + +// Define the context type +interface ConfigContextType { + config: main.Config | null; + initialConfig: main.Config | null; + setConfig: React.Dispatch>; + setConfigField: (key: keyof main.Config, value: any) => void; +} + +// Create the context +const ConfigContext = createContext(undefined); + +// Create a provider component +export const ConfigProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [config, setConfig] = useState(null); + const [initialConfig, setInitialConfig] = useState(null); + + useEffect(() => { + // Fetch the initial configuration + GetConfig().then((initialConfig: main.Config) => { + setConfig(initialConfig); + setInitialConfig(initialConfig); + }).catch(error => { + console.error('Failed to fetch initial config:', error); + }); + }, []); + + const setConfigField = (key: keyof main.Config, value: any) => { + if (config) { + var strKey = key as string; + strKey = strKey.charAt(0).toUpperCase() + strKey.slice(1); + // Call the backend function to set the config field + SetConfigField_backend(strKey, value) + .then(() => { + // Update the config state with the new value + setConfig(prevConfig => { + if (prevConfig) { + return { ...prevConfig, [key]: value }; + } + return prevConfig; + }); + }) + .catch(error => { + console.error(`Failed to set config field ${key}:`, error); + }); + } + }; + + return ( + + {children} + + ); +}; + +// Custom hook to use the config context +export const useConfig = (): ConfigContextType => { + const context = useContext(ConfigContext); + if (!context) { + throw new Error('useConfig must be used within a ConfigProvider'); + } + return context; +}; diff --git a/frontend/src/contexts/theme-provider.tsx b/frontend/src/contexts/theme-provider.tsx index 1005126..3ef46a6 100644 --- a/frontend/src/contexts/theme-provider.tsx +++ b/frontend/src/contexts/theme-provider.tsx @@ -1,7 +1,7 @@ import { SetTheme } from "wailsjs/go/main/App"; -import { LogError, LogInfo, LogWarning } from "wailsjs/runtime/runtime"; +import { LogError, LogInfo } from "wailsjs/runtime/runtime"; import { createContext, useContext, useEffect, useState } from "react"; -import { GetConfigField, SetConfigField } from "@/lib/config"; +import { useConfig } from "./config-provider"; type Theme = "dark" | "light" | "system"; @@ -12,12 +12,12 @@ type ThemeProviderProps = { type ThemeProviderState = { theme: Theme; - setTheme: (theme: Theme) => void; + setTheme: (theme: Theme) => Promise; // Ensure setTheme is async }; const initialState: ThemeProviderState = { theme: "system", - setTheme: () => null, + setTheme: async () => {}, // Initial no-op async function }; const ThemeProviderContext = createContext(initialState); @@ -27,22 +27,14 @@ export function ThemeProvider({ defaultTheme = "system", ...props }: ThemeProviderProps) { + const { config, setConfigField } = useConfig(); const [theme, setThemeState] = useState(defaultTheme); useEffect(() => { - const fetchTheme = async () => { - try { - const storedTheme = await GetConfigField("Theme"); - if (storedTheme) { - setThemeState(storedTheme as Theme); - } - } catch (error) { - LogWarning("Failed to fetch theme"); - } - }; - - fetchTheme(); - }, []); + if (config) { + setThemeState(config.theme as Theme); + } + }, [config?.theme]); // Update theme state when config changes useEffect(() => { const root = window.document.documentElement; @@ -62,18 +54,20 @@ export function ThemeProvider({ root.classList.add(theme); }, [theme]); + const setTheme = async (newTheme: Theme) => { + try { + await setConfigField("theme", newTheme); + setThemeState(newTheme); + SetTheme(newTheme); + LogInfo(`Set theme to ${newTheme}`); + } catch (error) { + LogError("Failed to set theme"); + } + }; + const value = { theme, - setTheme: async (theme: Theme) => { - try { - await SetConfigField("Theme", theme); - setThemeState(theme); - SetTheme(theme); - LogInfo(`Setted theme to ${theme}`); - } catch (error) { - LogError("Failed to set theme"); - } - }, + setTheme, }; return ( @@ -86,8 +80,9 @@ export function ThemeProvider({ export const useTheme = () => { const context = useContext(ThemeProviderContext); - if (context === undefined) + if (context === undefined) { throw new Error("useTheme must be used within a ThemeProvider"); + } return context; }; diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts deleted file mode 100644 index 279f81f..0000000 --- a/frontend/src/lib/config.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - GetConfigField as GetConfigField_backend, - SetConfigField as SetConfigField_backend, -} from "wailsjs/go/main/App"; - -interface ConfigCache { - [key: string]: string; -} - -// Initialize an empty cache object -let configCache: ConfigCache = {}; - -// Function to retrieve config field, using cache if available -export async function GetConfigField(key: string): Promise { - // Check if value exists in cache - if (configCache[key] !== undefined) { - return configCache[key]; - } - - // Call GetConfigField_backend to fetch value from backend - const value = await GetConfigField_backend(key); - - // Cache the fetched value - configCache[key] = value; - - return value; -} - -// Function to set config field and update cache -export async function SetConfigField( - key: string, - value: string -): Promise { - // Call SetConfigField_backend to update backend - await SetConfigField_backend(key, value); - - // Update cache with new value - configCache[key] = value; -} - -// Function to initialize config cache -// Function to initialize config cache -export async function InitConfigCache(): Promise { - // Reset cache - configCache = {}; - - // Array of keys to fetch - const keys = [ - "Theme", - "UseSystemTitleBar", - "EnableLogging", - "EnableTrace", - "EnableDebug", - "EnableInfo", - "EnableWarn", - "EnableError", - "EnableFatal", - "MaxLogFiles", - "Language", - "SaveWindowStatus", - "WindowScale", - "Opacity", - "WindowEffect", - "CheckForUpdates", - ]; - - // Array to store promises - const fetchPromises: Promise[] = keys.map((key) => - GetConfigField(key) - ); - - // Wait for all promises to resolve - const results = await Promise.all(fetchPromises); - - // Update cache with fetched values - keys.forEach((key, index) => { - configCache[key] = results[index]; - }); - - // Optional: Log or perform additional actions after fetching all values -} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index b129748..2e19fcf 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,18 +5,21 @@ import initializeI18n from "./i18n"; import { RestartProvider } from "./contexts/restart-provider.tsx"; import { ThemeProvider } from "./contexts/theme-provider.tsx"; import { StorageProvider } from "./contexts/storage-provider.tsx"; +import { ConfigProvider } from "./contexts/config-provider.tsx"; const startApp = async () => { await initializeI18n(); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( // - - - - - - - + + + + + + + + + // ); }; diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 62f14cc..53f3966 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -4,6 +4,8 @@ import {main} from '../models'; export function CheckForUpdate():Promise; +export function GetConfig():Promise; + export function GetConfigField(arg1:string):Promise; export function GetLoadConfigPath():Promise; @@ -22,7 +24,7 @@ export function SaveConfigDialog():Promise; export function SendNotification(arg1:string,arg2:string,arg3:string,arg4:string):Promise; -export function SetConfigField(arg1:string,arg2:string):Promise; +export function SetConfigField(arg1:string,arg2:any):Promise; export function SetTheme(arg1:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 0f8adc9..1be33c5 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -6,6 +6,10 @@ export function CheckForUpdate() { return window['go']['main']['App']['CheckForUpdate'](); } +export function GetConfig() { + return window['go']['main']['App']['GetConfig'](); +} + export function GetConfigField(arg1) { return window['go']['main']['App']['GetConfigField'](arg1); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 9c40209..cb37aed 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1,5 +1,59 @@ export namespace main { + export class Config { + theme?: string; + useSystemTitleBar?: boolean; + enableLogging?: boolean; + enableTrace?: boolean; + enableDebug?: boolean; + enableInfo?: boolean; + enableWarn?: boolean; + enableError?: boolean; + enableFatal?: boolean; + maxLogFiles?: number; + language?: string; + saveWindowStatus?: boolean; + windowStartState?: number; + windowStartPositionX?: number; + windowStartPositionY?: number; + windowStartSizeX?: number; + windowStartSizeY?: number; + windowScale?: number; + opacity?: number; + windowEffect?: number; + checkForUpdates?: boolean; + lastUpdateCheck?: number; + + static createFrom(source: any = {}) { + return new Config(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.theme = source["theme"]; + this.useSystemTitleBar = source["useSystemTitleBar"]; + this.enableLogging = source["enableLogging"]; + this.enableTrace = source["enableTrace"]; + this.enableDebug = source["enableDebug"]; + this.enableInfo = source["enableInfo"]; + this.enableWarn = source["enableWarn"]; + this.enableError = source["enableError"]; + this.enableFatal = source["enableFatal"]; + this.maxLogFiles = source["maxLogFiles"]; + this.language = source["language"]; + this.saveWindowStatus = source["saveWindowStatus"]; + this.windowStartState = source["windowStartState"]; + this.windowStartPositionX = source["windowStartPositionX"]; + this.windowStartPositionY = source["windowStartPositionY"]; + this.windowStartSizeX = source["windowStartSizeX"]; + this.windowStartSizeY = source["windowStartSizeY"]; + this.windowScale = source["windowScale"]; + this.opacity = source["opacity"]; + this.windowEffect = source["windowEffect"]; + this.checkForUpdates = source["checkForUpdates"]; + this.lastUpdateCheck = source["lastUpdateCheck"]; + } + } export class UpdateInfo { updateAvailable: boolean; currentVersion: string; From 5216bc00743671c064d3323969d7b11a52f0f6c4 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 15:10:43 +0300 Subject: [PATCH 093/221] refactor config switch setting --- .../SettingItems/LogLevelSetting.tsx | 1 + .../SettingItems/MaxLogFilesSetting.tsx | 2 +- .../SettingItems/Presets/SwitchConfig.tsx | 37 +++---- .../SettingItems/UseSystemTitleBarSetting.tsx | 2 +- .../SettingItems/WindowEffectSetting.tsx | 2 +- frontend/src/components/TitleBar.tsx | 8 +- frontend/src/components/ui/settings-group.tsx | 101 +++++++++++++----- 7 files changed, 100 insertions(+), 53 deletions(-) diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx index 564ddb1..ff8b0c0 100644 --- a/frontend/src/components/SettingItems/LogLevelSetting.tsx +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -82,6 +82,7 @@ export function LogLevelSetting() { "enableError", "enableFatal", ]} + requiresRestart >
{t("settings.setting.log_levels.label")} diff --git a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx index 309efb1..317e791 100644 --- a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx +++ b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx @@ -33,7 +33,7 @@ export function MaxLogFilesSetting() { }; return ( - +
{t("settings.setting.max_log_files.label")} diff --git a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx index f7ccf61..806eff1 100644 --- a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx +++ b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx @@ -1,3 +1,5 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { SettingsItem, SettingContent, @@ -5,8 +7,6 @@ import { SettingLabel, } from "@/components/ui/settings-group"; import { Switch } from "@/components/ui/switch"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; import { useConfig } from "@/contexts/config-provider"; import { main } from "wailsjs/go/models"; @@ -20,43 +20,40 @@ interface SwitchConfigProps { export function SwitchConfig({ configKey, label, - description, - requiresRestart, + description = "", + requiresRestart = false, }: SwitchConfigProps) { const { config, setConfigField } = useConfig(); const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(true); - const [switchValue, setSwitchValue] = useState(false); + const [{ isLoading, switchValue }, setState] = useState({ + isLoading: true, + switchValue: false, + }); useEffect(() => { - if (config && config[configKey] !== undefined && isLoading) { - const value = config[configKey] as boolean; - setSwitchValue(value); - - setIsLoading(false); + if (isLoading && config?.[configKey] !== undefined) { + setState({ switchValue: config[configKey] as boolean, isLoading: false }); } - }, [config]); + }, [isLoading, config, configKey]); const handleSwitch = (value: boolean) => { setConfigField(configKey, value); - setSwitchValue(value); + setState((prevState) => ({ ...prevState, switchValue: value })); }; return (
{label} {description && ( - {description + - (requiresRestart - ? " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")" - : "")} + {description} + {requiresRestart && + ` (${t("settings.restart_the_app_for_changes_to_take_effect")})`} )}
diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx index d427a2b..aed98be 100644 --- a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -9,7 +9,7 @@ export function UseSystemTitleBarSetting() { configKey="useSystemTitleBar" label={t("settings.setting.use_system_title_bar.label")} description={t("settings.setting.use_system_title_bar.description")} - requiresRestart={true} + requiresRestart /> ); } diff --git a/frontend/src/components/SettingItems/WindowEffectSetting.tsx b/frontend/src/components/SettingItems/WindowEffectSetting.tsx index 7b3a9c8..8bd4035 100644 --- a/frontend/src/components/SettingItems/WindowEffectSetting.tsx +++ b/frontend/src/components/SettingItems/WindowEffectSetting.tsx @@ -62,7 +62,7 @@ export function WindowEffectSetting() { ]; return ( - +
{t("settings.setting.window_effect.label")} diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 830439b..034dcc8 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -13,16 +13,16 @@ import { useStorage } from "@/contexts/storage-provider"; import { useConfig } from "@/contexts/config-provider"; export default function TitleBar() { - const { config } = useConfig(); + const { initialConfig } = useConfig(); const [useSystemTitleBar, setUseSystemTitleBar] = useState(false); const { restartRequired } = useRestart(); const { getValue } = useStorage(); useEffect(() => { - if (config && config.useSystemTitleBar !== undefined) { - setUseSystemTitleBar(config.useSystemTitleBar); + if (initialConfig && initialConfig.useSystemTitleBar !== undefined) { + setUseSystemTitleBar(initialConfig.useSystemTitleBar); } - }, [config?.useSystemTitleBar]); + }, [initialConfig?.useSystemTitleBar]); return ( !useSystemTitleBar && ( diff --git a/frontend/src/components/ui/settings-group.tsx b/frontend/src/components/ui/settings-group.tsx index 329436d..884c644 100644 --- a/frontend/src/components/ui/settings-group.tsx +++ b/frontend/src/components/ui/settings-group.tsx @@ -1,5 +1,5 @@ import React, { ReactNode, useEffect } from "react"; -import { Skeleton } from "@/components/ui/skeleton" +import { Skeleton } from "@/components/ui/skeleton"; import { useRestart } from "@/contexts/restart-provider"; import { main } from "wailsjs/go/models"; import { useConfig } from "@/contexts/config-provider"; @@ -8,8 +8,14 @@ interface SettingsComponentProps extends React.HTMLAttributes { children: ReactNode; } -export const SettingsGroup: React.FC = ({ children, className, ...rest }) => ( -
{children}
+export const SettingsGroup: React.FC = ({ + children, + className, + ...rest +}) => ( +
+ {children} +
); interface SettingsItemProps extends React.HTMLAttributes { @@ -18,14 +24,24 @@ interface SettingsItemProps extends React.HTMLAttributes { disabled?: boolean; children: ReactNode; configKey?: keyof main.Config | (keyof main.Config)[]; + requiresRestart?: boolean; } -export const SettingsItem: React.FC = ({ children, className, disabled, loading, vertical, configKey, ...rest }) => { +export const SettingsItem: React.FC = ({ + children, + className, + disabled, + loading, + vertical, + configKey, + requiresRestart, + ...rest +}) => { const { addRestartRequired, removeRestartRequired } = useRestart(); - const {config, initialConfig} = useConfig(); + const { config, initialConfig } = useConfig(); useEffect(() => { - if (configKey && config && initialConfig && !loading) { + if (requiresRestart && configKey && config && initialConfig && !loading) { if (Array.isArray(configKey)) { // Handle array of keys configKey.forEach((k) => { @@ -52,35 +68,68 @@ export const SettingsItem: React.FC = ({ children, className, }, [config]); if (loading) { - return + return ; } else { - return
{children}
+ ${className}`} + {...rest} + > + {children} +
+ ); } -} +}; -export const SettingLabel: React.FC = ({ children, className, ...rest }) => ( -
{children}
+export const SettingLabel: React.FC = ({ + children, + className, + ...rest +}) => ( +
+ {children} +
); -export const SettingDescription: React.FC = ({ children, className, ...rest }) => ( -
{children}
+export const SettingDescription: React.FC = ({ + children, + className, + ...rest +}) => ( +
+ {children} +
); -export const SettingContent: React.FC = ({ children, className, ...rest }) => ( -
{children}
+export const SettingContent: React.FC = ({ + children, + className, + ...rest +}) => ( +
+ {children} +
); -interface SettingsGroupSkeletonProps extends React.HTMLAttributes { -} +interface SettingsGroupSkeletonProps + extends React.HTMLAttributes {} -const SettingsItemSkeleton: React.FC = ({ className, ...rest }) => ( -
-
- - -
- +const SettingsItemSkeleton: React.FC = ({ + className, + ...rest +}) => ( +
+
+ +
-); \ No newline at end of file + +
+); From 2c004a6c1ad1658a68943036eeb6f9cf3d65ee9c Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 15:15:51 +0300 Subject: [PATCH 094/221] refactor import/export settings --- .../SettingItems/CheckForUpdatesSetting.tsx | 2 +- .../SettingItems/EnableLoggingSetting.tsx | 2 +- .../SettingItems/ImportExportSetting.tsx | 27 +++++++++---------- .../SettingItems/SaveWindowStatusSetting.tsx | 2 +- .../SettingItems/UseSystemTitleBarSetting.tsx | 2 +- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx b/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx index d165510..596712f 100644 --- a/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx +++ b/frontend/src/components/SettingItems/CheckForUpdatesSetting.tsx @@ -1,5 +1,5 @@ -import { SwitchConfig } from "./Presets/SwitchConfig"; import { useTranslation } from "react-i18next"; +import { SwitchConfig } from "./Presets/SwitchConfig"; export function CheckForUpdatesSetting() { const { t } = useTranslation(); diff --git a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx index 1ffc73a..a2d8e73 100644 --- a/frontend/src/components/SettingItems/EnableLoggingSetting.tsx +++ b/frontend/src/components/SettingItems/EnableLoggingSetting.tsx @@ -1,5 +1,5 @@ -import { SwitchConfig } from "./Presets/SwitchConfig"; import { useTranslation } from "react-i18next"; +import { SwitchConfig } from "./Presets/SwitchConfig"; export function EnableLoggingSetting() { const { t } = useTranslation(); diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index 8e34b9f..d323ab6 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -1,3 +1,5 @@ +import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { GetLoadConfigPath, ReadConfig, @@ -12,29 +14,25 @@ import { } from "../ui/settings-group"; import { Button } from "../ui/button"; import { AreYouSureDialog, AreYouSureDialogRef } from "../ui/are-you-sure"; -import { useRef, useState } from "react"; import { LogDebug } from "wailsjs/runtime/runtime"; -import { useTranslation } from "react-i18next"; export function ImportExportSetting() { const { t } = useTranslation(); const dialogRef = useRef(null); const [usePath, setUsePath] = useState(""); - const handleImportButtonClick = () => { - GetLoadConfigPath().then((path) => { - if (path !== "") { - setUsePath(path); - dialogRef.current?.openDialog(); - } - }); + const handleImportButtonClick = async () => { + const path = await GetLoadConfigPath(); + if (path) { + setUsePath(path); + dialogRef.current?.openDialog(); + } }; - const handleAcceptImport = () => { - LogDebug("Attempting to read config from " + usePath); - ReadConfig(usePath).then(() => { - RestartApplication(false, ["--goto", "settings__advanced"]); - }); + const handleAcceptImport = async () => { + LogDebug(`Attempting to read config from ${usePath}`); + await ReadConfig(usePath); + RestartApplication(false, ["--goto", "settings__advanced"]); }; return ( @@ -58,7 +56,6 @@ export function ImportExportSetting() { cancelText={t("cancel")} acceptText={t("yes")} /> -
diff --git a/frontend/src/components/SettingItems/SaveWindowStatusSetting.tsx b/frontend/src/components/SettingItems/SaveWindowStatusSetting.tsx index b825a5f..6738d05 100644 --- a/frontend/src/components/SettingItems/SaveWindowStatusSetting.tsx +++ b/frontend/src/components/SettingItems/SaveWindowStatusSetting.tsx @@ -1,5 +1,5 @@ -import { SwitchConfig } from "./Presets/SwitchConfig"; import { useTranslation } from "react-i18next"; +import { SwitchConfig } from "./Presets/SwitchConfig"; export function SaveWindowStatusSetting() { const { t } = useTranslation(); diff --git a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx index aed98be..a502e5c 100644 --- a/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx +++ b/frontend/src/components/SettingItems/UseSystemTitleBarSetting.tsx @@ -1,5 +1,5 @@ -import { SwitchConfig } from "./Presets/SwitchConfig"; import { useTranslation } from "react-i18next"; +import { SwitchConfig } from "./Presets/SwitchConfig"; export function UseSystemTitleBarSetting() { const { t } = useTranslation(); From 608a8a697c35095613959906f6477610d46e9fff Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 17:30:41 +0300 Subject: [PATCH 095/221] refactor setting items --- .../components/SettingItems/LocaleSetting.tsx | 28 +++-- .../SettingItems/LogLevelSetting.tsx | 119 +++++++++--------- .../SettingItems/MaxLogFilesSetting.tsx | 44 ++++--- .../components/SettingItems/ThemeSetting.tsx | 57 +++++---- .../components/SettingItems/UpdateSetting.tsx | 87 +++++-------- .../SettingItems/WindowEffectSetting.tsx | 32 ++--- frontend/src/contexts/theme-provider.tsx | 2 +- 7 files changed, 185 insertions(+), 184 deletions(-) diff --git a/frontend/src/components/SettingItems/LocaleSetting.tsx b/frontend/src/components/SettingItems/LocaleSetting.tsx index 6913e92..296013f 100644 --- a/frontend/src/components/SettingItems/LocaleSetting.tsx +++ b/frontend/src/components/SettingItems/LocaleSetting.tsx @@ -1,11 +1,11 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { SettingsItem, SettingContent, SettingDescription, SettingLabel, } from "../ui/settings-group"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; import { Combobox } from "../ui/combobox"; import locales from "@/locales.json"; import { useConfig } from "@/contexts/config-provider"; @@ -13,19 +13,21 @@ import { useConfig } from "@/contexts/config-provider"; export function LocaleSetting() { const { config, setConfigField } = useConfig(); const { t, i18n } = useTranslation(); - const [isLoading, setIsLoading] = useState(true); - const [language, setLanguage] = useState("en-US"); + const [{ isLoading, language }, setState] = useState({ + isLoading: true, + language: "", + }); useEffect(() => { - if (config && config.language !== undefined && isLoading) { - setLanguage(config.language); - setIsLoading(false); + if (isLoading && config?.language !== undefined) { + setState({ language: config.language, isLoading: false }); } - }, [config?.language]); + }, [isLoading, config?.language]); const handleLanguageChange = (value: string) => { setConfigField("language", value); i18n.changeLanguage(value); + setState((prevState) => ({ ...prevState, language: value })); }; return ( @@ -39,17 +41,17 @@ export function LocaleSetting() { ({ - value: language.code, - label: language.name, + mandatory + elements={locales.locales.map((locale) => ({ + value: locale.code, + label: locale.name, }))} placeholder={t("settings.setting.language.select_language")} searchPlaceholder={t("settings.setting.language.search_language")} nothingFoundMessage={t( "settings.setting.language.no_languages_found" )} - onChange={(value) => handleLanguageChange(value)} + onChange={handleLanguageChange} />
diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx index ff8b0c0..916a82d 100644 --- a/frontend/src/components/SettingItems/LogLevelSetting.tsx +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -1,3 +1,5 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { SettingsItem, SettingContent, @@ -5,8 +7,6 @@ import { SettingLabel, } from "../ui/settings-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; import { useConfig } from "@/contexts/config-provider"; type LogLevels = { @@ -21,67 +21,71 @@ type LogLevels = { export function LogLevelSetting() { const { config, setConfigField } = useConfig(); const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(true); - const [logLevels, setLogLevels] = useState({ - enableTrace: false, - enableDebug: false, - enableInfo: false, - enableWarn: false, - enableError: false, - enableFatal: false, + const [state, setState] = useState<{ + isLoading: boolean; + logLevels: LogLevels; + }>({ + isLoading: true, + logLevels: { + enableTrace: false, + enableDebug: false, + enableInfo: false, + enableWarn: false, + enableError: false, + enableFatal: false, + }, }); useEffect(() => { - if ( - config && - config.enableTrace !== undefined && - config.enableDebug !== undefined && - config.enableInfo !== undefined && - config.enableWarn !== undefined && - config.enableError !== undefined && - config.enableFatal !== undefined && - isLoading - ) { - setLogLevels({ - enableTrace: config.enableTrace, - enableDebug: config.enableDebug, - enableInfo: config.enableInfo, - enableWarn: config.enableWarn, - enableError: config.enableError, - enableFatal: config.enableFatal, - }); - - setIsLoading(false); + if (state.isLoading && config) { + const { + enableTrace, + enableDebug, + enableInfo, + enableWarn, + enableError, + enableFatal, + } = config; + if ( + enableTrace !== undefined && + enableDebug !== undefined && + enableInfo !== undefined && + enableWarn !== undefined && + enableError !== undefined && + enableFatal !== undefined + ) { + setState({ + isLoading: false, + logLevels: { + enableTrace, + enableDebug, + enableInfo, + enableWarn, + enableError, + enableFatal, + }, + }); + } } - }, [ - config?.enableTrace, - config?.enableDebug, - config?.enableInfo, - config?.enableWarn, - config?.enableError, - config?.enableFatal, - ]); + }, [config, state.isLoading]); const handleToggle = (level: keyof LogLevels) => { - setConfigField(level, !logLevels[level]); - - setLogLevels({ - ...logLevels, - [level]: !logLevels[level], - }); + const newLogLevels = { + ...state.logLevels, + [level]: !state.logLevels[level], + }; + setConfigField(level, newLogLevels[level]); + setState({ ...state, logLevels: newLogLevels }); }; + const activeLogLevels = Object.entries(state.logLevels) + .filter(([, value]) => value) + .map(([key]) => key); + return (
@@ -91,13 +95,8 @@ export function LogLevelSetting() {
- value) - .map(([key]) => key)} - > - {Object.entries(logLevels).map(([level, _]) => ( + + {Object.keys(state.logLevels).map((level) => ( { - if (config && config.maxLogFiles !== undefined && isLoading) { - setMaxLogFiles(config.maxLogFiles.toString()); - - setIsLoading(false); + if (isLoading && config?.maxLogFiles !== undefined) { + setState({ + isLoading: false, + maxLogFiles: config.maxLogFiles.toString(), + }); } - }, [config?.maxLogFiles]); + }, [isLoading, config?.maxLogFiles]); const handleMaxLogFilesChange = (textValue: string) => { - const value = Math.max(1, Math.min(10000, parseInt(textValue))); - const targetValue = isNaN(parseInt(textValue)) ? 20 : value; - - setConfigField("maxLogFiles", targetValue); - if (textValue === "") setMaxLogFiles(""); - else setMaxLogFiles(value.toString()); + const parsedValue = parseInt(textValue); + const value = isNaN(parsedValue) + ? 20 + : Math.max(1, Math.min(10000, parsedValue)); + setConfigField("maxLogFiles", value); + setState((prevState) => ({ + ...prevState, + maxLogFiles: textValue === "" ? "" : value.toString(), + })); }; return ( @@ -37,10 +43,9 @@ export function MaxLogFilesSetting() {
{t("settings.setting.max_log_files.label")} - {t("settings.setting.max_log_files.description") + - " (" + - t("settings.restart_the_app_for_changes_to_take_effect") + - ")"} + {`${t("settings.setting.max_log_files.description")} (${t( + "settings.restart_the_app_for_changes_to_take_effect" + )})`}
@@ -51,6 +56,7 @@ export function MaxLogFilesSetting() { onChange={(e) => handleMaxLogFilesChange(e.target.value)} min={1} max={10000} + onKeyDown={(e) => e.key.match(/[-+]/) && e.preventDefault()} />
diff --git a/frontend/src/components/SettingItems/ThemeSetting.tsx b/frontend/src/components/SettingItems/ThemeSetting.tsx index d741cb5..5017ded 100644 --- a/frontend/src/components/SettingItems/ThemeSetting.tsx +++ b/frontend/src/components/SettingItems/ThemeSetting.tsx @@ -1,18 +1,40 @@ +import { useTranslation } from "react-i18next"; import { SettingsItem, SettingContent, SettingDescription, SettingLabel, } from "../ui/settings-group"; -import { useTranslation } from "react-i18next"; import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; import { Monitor, Moon, Sun } from "lucide-react"; -import { useTheme } from "@/contexts/theme-provider"; +import { Theme, useTheme } from "@/contexts/theme-provider"; export function ThemeSetting() { const { t } = useTranslation(); const { theme, setTheme } = useTheme(); + const themes = [ + { + value: "system", + label: "Use system theme", + icon: , + }, + { + value: "light", + label: "Use light theme", + icon: , + }, + { + value: "dark", + label: "Use dark theme", + icon: , + }, + ]; + + const handleThemeChange = (selectedTheme: string) => { + setTheme(selectedTheme as Theme); + }; + return (
@@ -23,27 +45,16 @@ export function ThemeSetting() {
- setTheme("system")} - > - - - setTheme("light")} - > - - - setTheme("dark")} - > - - + {themes.map((item) => ( + handleThemeChange(item.value)} + > + {item.icon} + + ))}
diff --git a/frontend/src/components/SettingItems/UpdateSetting.tsx b/frontend/src/components/SettingItems/UpdateSetting.tsx index d9a06fb..3f4ee5c 100644 --- a/frontend/src/components/SettingItems/UpdateSetting.tsx +++ b/frontend/src/components/SettingItems/UpdateSetting.tsx @@ -10,14 +10,15 @@ import { } from "../ui/settings-group"; import { ArrowRight, RefreshCw } from "lucide-react"; import { GetConfigField, NeedsAdminPrivileges } from "wailsjs/go/main/App"; -import { t } from "i18next"; +import { useTranslation } from "react-i18next"; import { useStorage } from "@/contexts/storage-provider"; export function UpdateSetting() { + const { t } = useTranslation(); const { getValue } = useStorage(); const [updateInfo, setUpdateInfo] = useState( - {} as main.UpdateInfo + main.UpdateInfo.createFrom({}) ); const [lastUpdateCheck, setLastUpdateCheck] = useState(0); @@ -25,76 +26,56 @@ export function UpdateSetting() { const [isChecking, setIsChecking] = useState(false); const [isUpdating, setIsUpdating] = useState(false); + useEffect(() => { + // Initial check for admin privileges and update availability + NeedsAdminPrivileges().then(setNeedsAdmin); + handleCheckForUpdate(); + }, []); + + useEffect(() => { + // Update last update check timestamp from config + GetConfigField("LastUpdateCheck").then((value) => + setLastUpdateCheck(parseInt(value)) + ); + }, [updateInfo]); + + useEffect(() => { + // Automatic update trigger from argument + const storedUpdate = getValue("update"); + if (storedUpdate && !isChecking) { + setIsUpdating(true); + Update(storedUpdate).finally(() => setIsUpdating(false)); + } + }, [getValue, isChecking]); + const handleCheckForUpdate = () => { + // Check for updates and update state accordingly if (!isUpdating) { setIsChecking(true); CheckForUpdate() - .then((updateInfoJSON) => { - setUpdateInfo(updateInfoJSON); - }) - .finally(() => { - setTimeout(() => { - setIsChecking(false); - }, 200); - }); + .then(setUpdateInfo) + .finally(() => setTimeout(() => setIsChecking(false), 200)); } }; const handleUpdate = () => { + // Perform update based on admin privileges setIsUpdating(true); - - if (needsAdmin) { - UpdateAsAdmin(updateInfo.downloadUrl).finally(() => { - setIsUpdating(false); - }); - } else { - Update(updateInfo.downloadUrl).finally(() => { - setIsUpdating(false); - }); - } + const updater = needsAdmin ? UpdateAsAdmin : Update; + updater(updateInfo.downloadUrl).finally(() => setIsUpdating(false)); }; - useEffect(() => { - GetConfigField("LastUpdateCheck").then((value) => { - setLastUpdateCheck(parseInt(value)); - }); - }, [updateInfo]); - - useEffect(() => { - NeedsAdminPrivileges().then((value) => { - setNeedsAdmin(value); - }); - - handleCheckForUpdate(); - }, []); - - useEffect(() => { - if (getValue("update") === undefined) return; - - while (isChecking) setTimeout(() => {}, 100); - - setIsUpdating(true); - Update(getValue("update")).finally(() => { - setIsUpdating(false); - }); - }, [getValue("update")]); - const formatDate = (timestamp: number) => { + // Format timestamp into readable date string const date = new Date(timestamp * 1000); - const day = String(date.getDate()).padStart(2, "0"); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const year = date.getFullYear(); - const hours = String(date.getHours()).padStart(2, "0"); - const minutes = String(date.getMinutes()).padStart(2, "0"); - const seconds = String(date.getSeconds()).padStart(2, "0"); - return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`; + return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`; }; return (
-
+
{ - if (config && config.windowEffect !== undefined && isLoading) { - setUseWindowEffect(config.windowEffect.toString()); + const [{ isLoading, useWindowEffect }, setLoadingAndEffect] = useState<{ + isLoading: boolean; + useWindowEffect: string; + }>({ isLoading: true, useWindowEffect: "" }); - setIsLoading(false); + useEffect(() => { + if (isLoading && config?.windowEffect !== undefined) { + setLoadingAndEffect({ + isLoading: false, + useWindowEffect: config.windowEffect.toString(), + }); } }, [config?.windowEffect]); const handleToggle = (number: number) => { setConfigField("windowEffect", number); - setUseWindowEffect(number.toString()); + setLoadingAndEffect((prev) => ({ + ...prev, + useWindowEffect: number.toString(), + })); }; const windowEffectOptions = [ { - value: "1", number: 1, label: t("settings.setting.window_effect.none"), aria: "No window effect", }, { - value: "0", number: 0, label: t("settings.setting.window_effect.auto"), aria: "Auto window effect", }, { - value: "2", number: 2, label: t("settings.setting.window_effect.mica"), aria: "Mica window effect", }, { - value: "3", number: 3, label: t("settings.setting.window_effect.acrylic"), aria: "Acrylic window effect", }, { - value: "4", number: 4, label: t("settings.setting.window_effect.tabbed"), aria: "Tabbed window effect", @@ -73,10 +75,10 @@ export function WindowEffectSetting() {
- {windowEffectOptions.map(({ value, number, label, aria }) => ( + {windowEffectOptions.map(({ number, label, aria }) => ( handleToggle(number)} className={ diff --git a/frontend/src/contexts/theme-provider.tsx b/frontend/src/contexts/theme-provider.tsx index 3ef46a6..5451ff6 100644 --- a/frontend/src/contexts/theme-provider.tsx +++ b/frontend/src/contexts/theme-provider.tsx @@ -3,7 +3,7 @@ import { LogError, LogInfo } from "wailsjs/runtime/runtime"; import { createContext, useContext, useEffect, useState } from "react"; import { useConfig } from "./config-provider"; -type Theme = "dark" | "light" | "system"; +export type Theme = "dark" | "light" | "system"; type ThemeProviderProps = { children: React.ReactNode; From 58a640ff05ffcc4a7b77025657668b1b230393fd Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 23:13:59 +0300 Subject: [PATCH 096/221] add generic config slider component --- .../SettingItems/Presets/SliderConfig.tsx | 95 +++++++++++++++++++ .../SettingItems/WindowOpacitySetting.tsx | 80 +++------------- .../SettingItems/WindowScaleSetting.tsx | 62 +++--------- 3 files changed, 122 insertions(+), 115 deletions(-) create mode 100644 frontend/src/components/SettingItems/Presets/SliderConfig.tsx diff --git a/frontend/src/components/SettingItems/Presets/SliderConfig.tsx b/frontend/src/components/SettingItems/Presets/SliderConfig.tsx new file mode 100644 index 0000000..a4ae62f --- /dev/null +++ b/frontend/src/components/SettingItems/Presets/SliderConfig.tsx @@ -0,0 +1,95 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + SettingsItem, + SettingContent, + SettingDescription, + SettingLabel, +} from "@/components/ui/settings-group"; +import { useConfig } from "@/contexts/config-provider"; +import { main } from "wailsjs/go/models"; +import { Slider } from "@/components/ui/my-slider"; + +interface SliderConfigProps { + configKey: keyof main.Config; + label: string; + description?: string; + requiresRestart?: boolean; + min?: number; + max?: number; + step?: number; + onChange?: (value: number) => void; + onSave?: (value: number) => void; +} + +export function SliderConfig({ + configKey, + label, + description = "", + requiresRestart = false, + min = 0, + max = 100, + step = 1, + onChange, + onSave, + ...rest +}: SliderConfigProps) { + const { config, setConfigField } = useConfig(); + const { t } = useTranslation(); + const [{ isLoading, value }, setState] = useState({ + isLoading: true, + value: 50, + }); + + useEffect(() => { + if (isLoading && config?.[configKey] !== undefined) { + setState({ value: config[configKey] as number, isLoading: false }); + } + }, [isLoading, config, configKey]); + + const handleSliderChange = (value: number) => { + setState((prevState) => ({ ...prevState, value })); + onChange?.(value); + }; + + const handleSliderSave = () => { + setConfigField(configKey, value); + onSave?.(value); + }; + + return ( + +
+ {label} + {description && ( + + {description} + {requiresRestart && + ` (${t("settings.restart_the_app_for_changes_to_take_effect")})`} + + )} +
+ +
+
{min}%
+ handleSliderChange(value[0] as number)} + onPointerUp={handleSliderSave} + defaultValue={[value]} + min={min} + max={max} + step={step} + className={"w-64 cursor-pointer"} + {...rest} + /> +
{max}%
+
({value}%)
+
+
+
+ ); +} diff --git a/frontend/src/components/SettingItems/WindowOpacitySetting.tsx b/frontend/src/components/SettingItems/WindowOpacitySetting.tsx index aeb4007..891cc46 100644 --- a/frontend/src/components/SettingItems/WindowOpacitySetting.tsx +++ b/frontend/src/components/SettingItems/WindowOpacitySetting.tsx @@ -1,72 +1,22 @@ -import { - SettingsItem, - SettingContent, - SettingDescription, - SettingLabel, -} from "../ui/settings-group"; -import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Slider } from "../ui/my-slider"; -import { useConfig } from "@/contexts/config-provider"; +import { SliderConfig } from "./Presets/SliderConfig"; export function WindowOpacitySetting() { - const { config, initialConfig, setConfigField } = useConfig(); const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(true); - const [useOpacity, setUseOpacity] = useState(-1); - - useEffect(() => { - if (config && config.opacity !== undefined && isLoading) { - setUseOpacity(config.opacity); - - setIsLoading(false); - } - }, [config?.opacity]); - - const handleOpacityChange = (value: number) => { - document.documentElement.style.setProperty( - "--opacity", - String(value / 100) - ); - setUseOpacity(value); - }; - - const saveOpacityChange = () => { - setConfigField("opacity", useOpacity); - }; - return ( - -
- - {t("settings.setting.window_opacity.label")} - - - {t("settings.setting.window_opacity.description")} - -
- -
-
50%
- { - handleOpacityChange(value[0]); - }} - onPointerUp={saveOpacityChange} - defaultValue={[useOpacity]} - min={50} - max={100} - step={1} - className={"w-64 cursor-pointer"} - /> -
100%
-
({useOpacity}%)
-
-
-
+ { + document.documentElement.style.setProperty( + "--opacity", + String(value / 100) + ); + }} + /> ); } diff --git a/frontend/src/components/SettingItems/WindowScaleSetting.tsx b/frontend/src/components/SettingItems/WindowScaleSetting.tsx index 4b2ad73..a5967e4 100644 --- a/frontend/src/components/SettingItems/WindowScaleSetting.tsx +++ b/frontend/src/components/SettingItems/WindowScaleSetting.tsx @@ -1,57 +1,19 @@ -import { - SettingsItem, - SettingContent, - SettingDescription, - SettingLabel, -} from "../ui/settings-group"; -import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Slider } from "../ui/my-slider"; -import { useConfig } from "@/contexts/config-provider"; +import { SliderConfig } from "./Presets/SliderConfig"; export function WindowScaleSetting() { - const { config, setConfigField } = useConfig(); const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(true); - const [useScale, setUseScale] = useState(-1); - - useEffect(() => { - if (config && config.windowScale !== undefined && isLoading) { - setUseScale(config.windowScale); - - setIsLoading(false); - } - }, [config?.windowScale]); - - const handleScaleSave = () => { - setConfigField("windowScale", useScale); - document.documentElement.style.fontSize = useScale * (16 / 100) + "px"; - }; - return ( - -
- {t("settings.setting.window_scale.label")} - - {t("settings.setting.window_scale.description")} - -
- -
-
50%
- setUseScale(value[0])} - onPointerUp={handleScaleSave} - defaultValue={[useScale]} - min={50} - max={150} - step={10} - className={"w-64 cursor-pointer"} - /> -
150%
-
({useScale}%)
-
-
-
+ { + document.documentElement.style.fontSize = value * (16 / 100) + "px"; + }} + /> ); } From b978a0a8ac12200d7724d8e660ffa27b77f5265b Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 23:20:00 +0300 Subject: [PATCH 097/221] refactor contexts --- frontend/src/contexts/config-provider.tsx | 48 ++++++++++++++-------- frontend/src/contexts/restart-provider.tsx | 1 - 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/frontend/src/contexts/config-provider.tsx b/frontend/src/contexts/config-provider.tsx index 2dcf8f4..b940867 100644 --- a/frontend/src/contexts/config-provider.tsx +++ b/frontend/src/contexts/config-provider.tsx @@ -1,9 +1,17 @@ -// src/ConfigContext.tsx -import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; -import { main } from 'wailsjs/go/models'; -import { GetConfig, SetConfigField as SetConfigField_backend } from 'wailsjs/go/main/App'; +import React, { + createContext, + useContext, + useEffect, + useState, + ReactNode, +} from "react"; +import { main } from "wailsjs/go/models"; +import { + GetConfig, + SetConfigField as SetConfigField_backend, +} from "wailsjs/go/main/App"; +import { LogError } from "wailsjs/runtime/runtime"; -// Define the context type interface ConfigContextType { config: main.Config | null; initialConfig: main.Config | null; @@ -15,18 +23,22 @@ interface ConfigContextType { const ConfigContext = createContext(undefined); // Create a provider component -export const ConfigProvider: React.FC<{ children: ReactNode }> = ({ children }) => { +export const ConfigProvider: React.FC<{ children: ReactNode }> = ({ + children, +}) => { const [config, setConfig] = useState(null); const [initialConfig, setInitialConfig] = useState(null); useEffect(() => { // Fetch the initial configuration - GetConfig().then((initialConfig: main.Config) => { - setConfig(initialConfig); - setInitialConfig(initialConfig); - }).catch(error => { - console.error('Failed to fetch initial config:', error); - }); + GetConfig() + .then((initialConfig: main.Config) => { + setConfig(initialConfig); + setInitialConfig(initialConfig); + }) + .catch((error) => { + LogError("Failed to fetch initial config: " + error); + }); }, []); const setConfigField = (key: keyof main.Config, value: any) => { @@ -37,21 +49,23 @@ export const ConfigProvider: React.FC<{ children: ReactNode }> = ({ children }) SetConfigField_backend(strKey, value) .then(() => { // Update the config state with the new value - setConfig(prevConfig => { + setConfig((prevConfig) => { if (prevConfig) { return { ...prevConfig, [key]: value }; } return prevConfig; }); }) - .catch(error => { - console.error(`Failed to set config field ${key}:`, error); + .catch((error) => { + LogError("Failed to set config field " + key + ": " + error); }); } }; return ( - + {children} ); @@ -61,7 +75,7 @@ export const ConfigProvider: React.FC<{ children: ReactNode }> = ({ children }) export const useConfig = (): ConfigContextType => { const context = useContext(ConfigContext); if (!context) { - throw new Error('useConfig must be used within a ConfigProvider'); + throw new Error("useConfig must be used within a ConfigProvider"); } return context; }; diff --git a/frontend/src/contexts/restart-provider.tsx b/frontend/src/contexts/restart-provider.tsx index 6b2af3b..e5f5967 100644 --- a/frontend/src/contexts/restart-provider.tsx +++ b/frontend/src/contexts/restart-provider.tsx @@ -1,6 +1,5 @@ import React, { createContext, useContext, useState, ReactNode } from "react"; -// Define types for context values interface RestartContextType { restartRequired: boolean; addRestartRequired: (item: string) => void; From 558fe4151c16093e1f4e719e5b6846772c7b6cce Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 23:22:35 +0300 Subject: [PATCH 098/221] remove relative imports --- .../src/components/SettingItems/ImportExportSetting.tsx | 9 ++++++--- frontend/src/components/SettingItems/LocaleSetting.tsx | 4 ++-- frontend/src/components/SettingItems/LogLevelSetting.tsx | 2 +- .../src/components/SettingItems/MaxLogFilesSetting.tsx | 4 ++-- frontend/src/components/SettingItems/ThemeSetting.tsx | 4 ++-- frontend/src/components/SettingItems/UpdateSetting.tsx | 4 ++-- .../src/components/SettingItems/WindowEffectSetting.tsx | 4 ++-- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index d323ab6..3573315 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -11,9 +11,12 @@ import { SettingContent, SettingDescription, SettingLabel, -} from "../ui/settings-group"; -import { Button } from "../ui/button"; -import { AreYouSureDialog, AreYouSureDialogRef } from "../ui/are-you-sure"; +} from "@/components/ui/settings-group"; +import { Button } from "@/components/ui/button"; +import { + AreYouSureDialog, + AreYouSureDialogRef, +} from "@/components/ui/are-you-sure"; import { LogDebug } from "wailsjs/runtime/runtime"; export function ImportExportSetting() { diff --git a/frontend/src/components/SettingItems/LocaleSetting.tsx b/frontend/src/components/SettingItems/LocaleSetting.tsx index 296013f..00bd23a 100644 --- a/frontend/src/components/SettingItems/LocaleSetting.tsx +++ b/frontend/src/components/SettingItems/LocaleSetting.tsx @@ -5,8 +5,8 @@ import { SettingContent, SettingDescription, SettingLabel, -} from "../ui/settings-group"; -import { Combobox } from "../ui/combobox"; +} from "@/components/ui/settings-group"; +import { Combobox } from "@/components/ui/combobox"; import locales from "@/locales.json"; import { useConfig } from "@/contexts/config-provider"; diff --git a/frontend/src/components/SettingItems/LogLevelSetting.tsx b/frontend/src/components/SettingItems/LogLevelSetting.tsx index 916a82d..fc2055e 100644 --- a/frontend/src/components/SettingItems/LogLevelSetting.tsx +++ b/frontend/src/components/SettingItems/LogLevelSetting.tsx @@ -5,7 +5,7 @@ import { SettingContent, SettingDescription, SettingLabel, -} from "../ui/settings-group"; +} from "@/components/ui/settings-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useConfig } from "@/contexts/config-provider"; diff --git a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx index 9f7484a..27067af 100644 --- a/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx +++ b/frontend/src/components/SettingItems/MaxLogFilesSetting.tsx @@ -5,8 +5,8 @@ import { SettingContent, SettingDescription, SettingLabel, -} from "../ui/settings-group"; -import { Input } from "../ui/input"; +} from "@/components/ui/settings-group"; +import { Input } from "@/components/ui/input"; import { useConfig } from "@/contexts/config-provider"; export function MaxLogFilesSetting() { diff --git a/frontend/src/components/SettingItems/ThemeSetting.tsx b/frontend/src/components/SettingItems/ThemeSetting.tsx index 5017ded..7d4076f 100644 --- a/frontend/src/components/SettingItems/ThemeSetting.tsx +++ b/frontend/src/components/SettingItems/ThemeSetting.tsx @@ -4,8 +4,8 @@ import { SettingContent, SettingDescription, SettingLabel, -} from "../ui/settings-group"; -import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; +} from "@/components/ui/settings-group"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { Monitor, Moon, Sun } from "lucide-react"; import { Theme, useTheme } from "@/contexts/theme-provider"; diff --git a/frontend/src/components/SettingItems/UpdateSetting.tsx b/frontend/src/components/SettingItems/UpdateSetting.tsx index 3f4ee5c..9c0f4e4 100644 --- a/frontend/src/components/SettingItems/UpdateSetting.tsx +++ b/frontend/src/components/SettingItems/UpdateSetting.tsx @@ -1,13 +1,13 @@ import { useEffect, useState } from "react"; import { CheckForUpdate, Update, UpdateAsAdmin } from "wailsjs/go/main/App"; import { main } from "wailsjs/go/models"; -import { Button } from "../ui/button"; +import { Button } from "@/components/ui/button"; import { SettingContent, SettingDescription, SettingLabel, SettingsItem, -} from "../ui/settings-group"; +} from "@/components/ui/settings-group"; import { ArrowRight, RefreshCw } from "lucide-react"; import { GetConfigField, NeedsAdminPrivileges } from "wailsjs/go/main/App"; import { useTranslation } from "react-i18next"; diff --git a/frontend/src/components/SettingItems/WindowEffectSetting.tsx b/frontend/src/components/SettingItems/WindowEffectSetting.tsx index 21665ee..8ed6c73 100644 --- a/frontend/src/components/SettingItems/WindowEffectSetting.tsx +++ b/frontend/src/components/SettingItems/WindowEffectSetting.tsx @@ -3,10 +3,10 @@ import { SettingContent, SettingDescription, SettingLabel, -} from "../ui/settings-group"; +} from "@/components/ui/settings-group"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useConfig } from "@/contexts/config-provider"; export function WindowEffectSetting() { From c424a992865df1adad7f72cbbe6238d61eabbf68 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 23:25:59 +0300 Subject: [PATCH 099/221] combine providers into a single component --- frontend/src/contexts/providers.tsx | 23 +++++++++++++++++++++++ frontend/src/main.tsx | 17 ++++------------- 2 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 frontend/src/contexts/providers.tsx diff --git a/frontend/src/contexts/providers.tsx b/frontend/src/contexts/providers.tsx new file mode 100644 index 0000000..9b59477 --- /dev/null +++ b/frontend/src/contexts/providers.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from "react"; +import { RestartProvider } from "./restart-provider.tsx"; +import { ThemeProvider } from "./theme-provider.tsx"; +import { StorageProvider } from "./storage-provider.tsx"; +import { ConfigProvider } from "./config-provider.tsx"; + +interface ProvidersProps { + children: ReactNode; +} + +const Providers = ({ children }: ProvidersProps) => { + return ( + + + + {children} + + + + ); +}; + +export default Providers; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2e19fcf..dd75ebe 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,24 +2,15 @@ import ReactDOM from "react-dom/client"; import App from "./App.tsx"; import "./index.css"; import initializeI18n from "./i18n"; -import { RestartProvider } from "./contexts/restart-provider.tsx"; -import { ThemeProvider } from "./contexts/theme-provider.tsx"; -import { StorageProvider } from "./contexts/storage-provider.tsx"; -import { ConfigProvider } from "./contexts/config-provider.tsx"; +import Providers from "./contexts/providers.tsx"; const startApp = async () => { await initializeI18n(); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( // - - - - - - - - - + + + // ); }; From 68d59119bdf44aeffa770d9bb4d83e17bb26dc8b Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 23:39:37 +0300 Subject: [PATCH 100/221] remove redundancy --- frontend/src/App.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 75f8c6a..4d4cacc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -15,7 +15,7 @@ import { LogDebug } from "wailsjs/runtime/runtime"; function App() { const { config, initialConfig } = useConfig(); const { t } = useTranslation(); - const { setValue, getValue } = useStorage(); + const { setValue } = useStorage(); const [tab, setTab] = useState("packs"); useLayoutEffect(() => { @@ -82,17 +82,13 @@ function App() { LogDebug("window.goto: " + path); const pathArray = path.split("__"); - setValue("tab", pathArray[0]); + setTab(pathArray[0]); for (let i = 0; i < pathArray.length - 1; i++) { setValue(pathArray[i], pathArray[i + 1]); } }; - useEffect(() => { - setTab(getValue("tab") || "packs"); - }, [getValue("tab")]); - useEffect(() => { setValue("path1", tab); }, [tab]); From 3cfbbbefefa17c626fa7dff24b28b1c79ceb09a5 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 23:48:31 +0300 Subject: [PATCH 101/221] return generic value from getConfigField --- config.go | 4 ++-- frontend/src/components/SettingItems/UpdateSetting.tsx | 6 +++--- frontend/src/i18n.ts | 2 +- frontend/wailsjs/go/main/App.d.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/config.go b/config.go index 2e229d8..3beb533 100644 --- a/config.go +++ b/config.go @@ -128,7 +128,7 @@ func (app *App) GetConfig() Config { return config } -func (app *App) GetConfigField(fieldName string) string { +func (app *App) GetConfigField(fieldName string) interface{} { runtime.LogDebug(app.ctx, fmt.Sprintf("Attempting to get config field %s", fieldName)) // Get the reflection Type and Value of the Config struct @@ -156,7 +156,7 @@ func (app *App) GetConfigField(fieldName string) string { } runtime.LogDebug(app.ctx, fmt.Sprintf("Config field %s has value: %v", fieldName, fieldValue.Interface())) - return fmt.Sprintf("%v", fieldValue.Interface()) + return fieldValue.Interface() } func (app *App) SetConfigField(fieldName string, value interface{}) { diff --git a/frontend/src/components/SettingItems/UpdateSetting.tsx b/frontend/src/components/SettingItems/UpdateSetting.tsx index 9c0f4e4..0c3cbc9 100644 --- a/frontend/src/components/SettingItems/UpdateSetting.tsx +++ b/frontend/src/components/SettingItems/UpdateSetting.tsx @@ -35,18 +35,18 @@ export function UpdateSetting() { useEffect(() => { // Update last update check timestamp from config GetConfigField("LastUpdateCheck").then((value) => - setLastUpdateCheck(parseInt(value)) + setLastUpdateCheck(value as number) ); }, [updateInfo]); useEffect(() => { // Automatic update trigger from argument const storedUpdate = getValue("update"); - if (storedUpdate && !isChecking) { + if (storedUpdate && !isChecking && !isUpdating) { setIsUpdating(true); Update(storedUpdate).finally(() => setIsUpdating(false)); } - }, [getValue, isChecking]); + }, [getValue, isChecking, isUpdating]); const handleCheckForUpdate = () => { // Check for updates and update state accordingly diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 646a05f..22db037 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -6,7 +6,7 @@ import HttpApi from "i18next-http-backend"; import locales from "@/locales.json"; const initializeI18n = async () => { - const language = (await GetConfigField("Language")) || "en-US"; + const language = ((await GetConfigField("Language")) as string) || "en-US"; const supportedLngs = locales.locales.map((language) => language.code); diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 53f3966..af895c6 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -6,7 +6,7 @@ export function CheckForUpdate():Promise; export function GetConfig():Promise; -export function GetConfigField(arg1:string):Promise; +export function GetConfigField(arg1:string):Promise; export function GetLoadConfigPath():Promise; From 1a7ce0b0360719b87dcf39c6c111025ee5f5cc04 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 23:52:53 +0300 Subject: [PATCH 102/221] remove theme.go --- frontend/src/contexts/theme-provider.tsx | 18 +++++++++++++++--- theme.go | 19 ------------------- 2 files changed, 15 insertions(+), 22 deletions(-) delete mode 100644 theme.go diff --git a/frontend/src/contexts/theme-provider.tsx b/frontend/src/contexts/theme-provider.tsx index 5451ff6..1ca83cf 100644 --- a/frontend/src/contexts/theme-provider.tsx +++ b/frontend/src/contexts/theme-provider.tsx @@ -1,5 +1,10 @@ -import { SetTheme } from "wailsjs/go/main/App"; -import { LogError, LogInfo } from "wailsjs/runtime/runtime"; +import { + LogError, + LogInfo, + WindowSetDarkTheme, + WindowSetLightTheme, + WindowSetSystemDefaultTheme, +} from "wailsjs/runtime/runtime"; import { createContext, useContext, useEffect, useState } from "react"; import { useConfig } from "./config-provider"; @@ -58,7 +63,14 @@ export function ThemeProvider({ try { await setConfigField("theme", newTheme); setThemeState(newTheme); - SetTheme(newTheme); + if (newTheme === "system") { + WindowSetSystemDefaultTheme(); + } else if (newTheme === "light") { + WindowSetLightTheme(); + } else { + WindowSetDarkTheme(); + } + LogInfo(`Set theme to ${newTheme}`); } catch (error) { LogError("Failed to set theme"); diff --git a/theme.go b/theme.go deleted file mode 100644 index d5422e7..0000000 --- a/theme.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/wailsapp/wails/v2/pkg/runtime" -) - -func (app *App) SetTheme(theme string) { - runtime.LogDebug(app.ctx, fmt.Sprintf("Setting windows theme to %s", theme)) - switch theme { - case "light": - runtime.WindowSetLightTheme(app.ctx) - case "dark": - runtime.WindowSetDarkTheme(app.ctx) - case "system": - runtime.WindowSetSystemDefaultTheme(app.ctx) - } -} From 635bef954e313ab0ab9f959b8214e4458ec2710f Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 3 Jul 2024 23:56:11 +0300 Subject: [PATCH 103/221] add new path --- app_paths.go | 6 ++++++ frontend/wailsjs/go/main/App.d.ts | 2 -- frontend/wailsjs/go/main/App.js | 4 ---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app_paths.go b/app_paths.go index e636f67..7d77af3 100644 --- a/app_paths.go +++ b/app_paths.go @@ -11,6 +11,7 @@ import ( var packsFolder string var logsFolder string var savedConfigFolder string +var activeIconFolder string var configPath string var appIconPath string @@ -28,6 +29,7 @@ func path_init() error { packsFolder = path.Join(appFolder, "packs") logsFolder = path.Join(appFolder, "logs") savedConfigFolder = path.Join(appFolder, "savedconfigs") + activeIconFolder = path.Join(appFolder, "icons") configPath = path.Join(appFolder, "config.json") appIconPath = path.Join(appFolder, "appicon.png") @@ -50,6 +52,10 @@ func path_init() error { if err != nil { return err } + err = create_folder(activeIconFolder) + if err != nil { + return err + } runtime.LogTrace(appContext, "Creating folders complete") diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index af895c6..e2a8274 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -26,8 +26,6 @@ export function SendNotification(arg1:string,arg2:string,arg3:string,arg4:string export function SetConfigField(arg1:string,arg2:any):Promise; -export function SetTheme(arg1:string):Promise; - export function Update(arg1:string):Promise; export function UpdateAsAdmin(arg1:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 1be33c5..317fd6b 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -50,10 +50,6 @@ export function SetConfigField(arg1, arg2) { return window['go']['main']['App']['SetConfigField'](arg1, arg2); } -export function SetTheme(arg1) { - return window['go']['main']['App']['SetTheme'](arg1); -} - export function Update(arg1) { return window['go']['main']['App']['Update'](arg1); } From f0b3eb3580a1eec01d1a163d65c410bb68aa92dc Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 4 Jul 2024 00:26:40 +0300 Subject: [PATCH 104/221] move wailsjs folder to resolve ts error --- .github/workflows/build.yml | 3 - frontend/src/App.tsx | 4 +- .../SettingItems/ImportExportSetting.tsx | 4 +- .../SettingItems/Presets/SliderConfig.tsx | 2 +- .../SettingItems/Presets/SwitchConfig.tsx | 2 +- .../components/SettingItems/UpdateSetting.tsx | 8 +- frontend/src/components/TitleBar.tsx | 4 +- frontend/src/components/ui/settings-group.tsx | 2 +- frontend/src/contexts/config-provider.tsx | 6 +- frontend/src/contexts/theme-provider.tsx | 2 +- frontend/src/i18n.ts | 2 +- frontend/src/wailsjs/go/main/App.d.ts | 31 +++ frontend/src/wailsjs/go/main/App.js | 59 +++++ frontend/src/wailsjs/go/models.ts | 81 ++++++ frontend/src/wailsjs/runtime/package.json | 24 ++ frontend/src/wailsjs/runtime/runtime.d.ts | 249 ++++++++++++++++++ frontend/src/wailsjs/runtime/runtime.js | 238 +++++++++++++++++ frontend/vite.config.ts | 21 +- wails.json | 1 + 19 files changed, 711 insertions(+), 32 deletions(-) create mode 100644 frontend/src/wailsjs/go/main/App.d.ts create mode 100644 frontend/src/wailsjs/go/main/App.js create mode 100644 frontend/src/wailsjs/go/models.ts create mode 100644 frontend/src/wailsjs/runtime/package.json create mode 100644 frontend/src/wailsjs/runtime/runtime.d.ts create mode 100644 frontend/src/wailsjs/runtime/runtime.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c1fef3..889f81a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,9 +41,6 @@ jobs: run: go install github.com/wailsapp/wails/v2/cmd/wails@latest - name: Build Wails app - run: wails build - - - name: Build NSIS installer run: wails build -nsis - name: Sign Windows binaries diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4d4cacc..04af5fc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,10 +7,10 @@ import { useEffect, useLayoutEffect, useState } from "react"; import { useStorage } from "./contexts/storage-provider"; import { Toaster } from "./components/ui/sonner"; import { toast } from "sonner"; -import { OpenFileInExplorer } from "wailsjs/go/main/App"; +import { OpenFileInExplorer } from "@/wailsjs/go/main/App"; import React from "react"; import { useConfig } from "./contexts/config-provider"; -import { LogDebug } from "wailsjs/runtime/runtime"; +import { LogDebug } from "@/wailsjs/runtime/runtime"; function App() { const { config, initialConfig } = useConfig(); diff --git a/frontend/src/components/SettingItems/ImportExportSetting.tsx b/frontend/src/components/SettingItems/ImportExportSetting.tsx index 3573315..7e4aa8d 100644 --- a/frontend/src/components/SettingItems/ImportExportSetting.tsx +++ b/frontend/src/components/SettingItems/ImportExportSetting.tsx @@ -5,7 +5,7 @@ import { ReadConfig, RestartApplication, SaveConfigDialog, -} from "wailsjs/go/main/App"; +} from "@/wailsjs/go/main/App"; import { SettingsItem, SettingContent, @@ -17,7 +17,7 @@ import { AreYouSureDialog, AreYouSureDialogRef, } from "@/components/ui/are-you-sure"; -import { LogDebug } from "wailsjs/runtime/runtime"; +import { LogDebug } from "@/wailsjs/runtime/runtime"; export function ImportExportSetting() { const { t } = useTranslation(); diff --git a/frontend/src/components/SettingItems/Presets/SliderConfig.tsx b/frontend/src/components/SettingItems/Presets/SliderConfig.tsx index a4ae62f..6cc29fc 100644 --- a/frontend/src/components/SettingItems/Presets/SliderConfig.tsx +++ b/frontend/src/components/SettingItems/Presets/SliderConfig.tsx @@ -7,7 +7,7 @@ import { SettingLabel, } from "@/components/ui/settings-group"; import { useConfig } from "@/contexts/config-provider"; -import { main } from "wailsjs/go/models"; +import { main } from "@/wailsjs/go/models"; import { Slider } from "@/components/ui/my-slider"; interface SliderConfigProps { diff --git a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx index 806eff1..c497fab 100644 --- a/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx +++ b/frontend/src/components/SettingItems/Presets/SwitchConfig.tsx @@ -8,7 +8,7 @@ import { } from "@/components/ui/settings-group"; import { Switch } from "@/components/ui/switch"; import { useConfig } from "@/contexts/config-provider"; -import { main } from "wailsjs/go/models"; +import { main } from "@/wailsjs/go/models"; interface SwitchConfigProps { configKey: keyof main.Config; diff --git a/frontend/src/components/SettingItems/UpdateSetting.tsx b/frontend/src/components/SettingItems/UpdateSetting.tsx index 0c3cbc9..d9af4cc 100644 --- a/frontend/src/components/SettingItems/UpdateSetting.tsx +++ b/frontend/src/components/SettingItems/UpdateSetting.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import { CheckForUpdate, Update, UpdateAsAdmin } from "wailsjs/go/main/App"; -import { main } from "wailsjs/go/models"; +import { CheckForUpdate, Update, UpdateAsAdmin } from "@/wailsjs/go/main/App"; +import { main } from "@/wailsjs/go/models"; import { Button } from "@/components/ui/button"; import { SettingContent, @@ -9,7 +9,7 @@ import { SettingsItem, } from "@/components/ui/settings-group"; import { ArrowRight, RefreshCw } from "lucide-react"; -import { GetConfigField, NeedsAdminPrivileges } from "wailsjs/go/main/App"; +import { GetConfigField, NeedsAdminPrivileges } from "@/wailsjs/go/main/App"; import { useTranslation } from "react-i18next"; import { useStorage } from "@/contexts/storage-provider"; @@ -46,7 +46,7 @@ export function UpdateSetting() { setIsUpdating(true); Update(storedUpdate).finally(() => setIsUpdating(false)); } - }, [getValue, isChecking, isUpdating]); + }, [getValue]); const handleCheckForUpdate = () => { // Check for updates and update state accordingly diff --git a/frontend/src/components/TitleBar.tsx b/frontend/src/components/TitleBar.tsx index 034dcc8..ec3b52b 100644 --- a/frontend/src/components/TitleBar.tsx +++ b/frontend/src/components/TitleBar.tsx @@ -1,9 +1,9 @@ -import { RestartApplication } from "wailsjs/go/main/App"; +import { RestartApplication } from "@/wailsjs/go/main/App"; import { WindowMinimise, WindowToggleMaximise, Quit, -} from "wailsjs/runtime/runtime"; +} from "@/wailsjs/runtime/runtime"; import { Minus, Copy, X, RotateCcw } from "lucide-react"; import { Button } from "@/components/ui/button"; import icon from "../assets/appicon.png"; diff --git a/frontend/src/components/ui/settings-group.tsx b/frontend/src/components/ui/settings-group.tsx index 884c644..28f2599 100644 --- a/frontend/src/components/ui/settings-group.tsx +++ b/frontend/src/components/ui/settings-group.tsx @@ -1,7 +1,7 @@ import React, { ReactNode, useEffect } from "react"; import { Skeleton } from "@/components/ui/skeleton"; import { useRestart } from "@/contexts/restart-provider"; -import { main } from "wailsjs/go/models"; +import { main } from "@/wailsjs/go/models"; import { useConfig } from "@/contexts/config-provider"; interface SettingsComponentProps extends React.HTMLAttributes { diff --git a/frontend/src/contexts/config-provider.tsx b/frontend/src/contexts/config-provider.tsx index b940867..0e23fdd 100644 --- a/frontend/src/contexts/config-provider.tsx +++ b/frontend/src/contexts/config-provider.tsx @@ -5,12 +5,12 @@ import React, { useState, ReactNode, } from "react"; -import { main } from "wailsjs/go/models"; +import { main } from "@/wailsjs/go/models"; import { GetConfig, SetConfigField as SetConfigField_backend, -} from "wailsjs/go/main/App"; -import { LogError } from "wailsjs/runtime/runtime"; +} from "@/wailsjs/go/main/App"; +import { LogError } from "@/wailsjs/runtime/runtime"; interface ConfigContextType { config: main.Config | null; diff --git a/frontend/src/contexts/theme-provider.tsx b/frontend/src/contexts/theme-provider.tsx index 1ca83cf..b8e77cb 100644 --- a/frontend/src/contexts/theme-provider.tsx +++ b/frontend/src/contexts/theme-provider.tsx @@ -4,7 +4,7 @@ import { WindowSetDarkTheme, WindowSetLightTheme, WindowSetSystemDefaultTheme, -} from "wailsjs/runtime/runtime"; +} from "@/wailsjs/runtime/runtime"; import { createContext, useContext, useEffect, useState } from "react"; import { useConfig } from "./config-provider"; diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 22db037..4c76eb8 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -1,4 +1,4 @@ -import { GetConfigField } from "wailsjs/go/main/App"; +import { GetConfigField } from "@/wailsjs/go/main/App"; import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts new file mode 100644 index 0000000..e2a8274 --- /dev/null +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -0,0 +1,31 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +import {main} from '../models'; + +export function CheckForUpdate():Promise; + +export function GetConfig():Promise; + +export function GetConfigField(arg1:string):Promise; + +export function GetLoadConfigPath():Promise; + +export function GetVersion():Promise; + +export function NeedsAdminPrivileges():Promise; + +export function OpenFileInExplorer(arg1:string):Promise; + +export function ReadConfig(arg1:string):Promise; + +export function RestartApplication(arg1:boolean,arg2:Array):Promise; + +export function SaveConfigDialog():Promise; + +export function SendNotification(arg1:string,arg2:string,arg3:string,arg4:string):Promise; + +export function SetConfigField(arg1:string,arg2:any):Promise; + +export function Update(arg1:string):Promise; + +export function UpdateAsAdmin(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js new file mode 100644 index 0000000..317fd6b --- /dev/null +++ b/frontend/src/wailsjs/go/main/App.js @@ -0,0 +1,59 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function CheckForUpdate() { + return window['go']['main']['App']['CheckForUpdate'](); +} + +export function GetConfig() { + return window['go']['main']['App']['GetConfig'](); +} + +export function GetConfigField(arg1) { + return window['go']['main']['App']['GetConfigField'](arg1); +} + +export function GetLoadConfigPath() { + return window['go']['main']['App']['GetLoadConfigPath'](); +} + +export function GetVersion() { + return window['go']['main']['App']['GetVersion'](); +} + +export function NeedsAdminPrivileges() { + return window['go']['main']['App']['NeedsAdminPrivileges'](); +} + +export function OpenFileInExplorer(arg1) { + return window['go']['main']['App']['OpenFileInExplorer'](arg1); +} + +export function ReadConfig(arg1) { + return window['go']['main']['App']['ReadConfig'](arg1); +} + +export function RestartApplication(arg1, arg2) { + return window['go']['main']['App']['RestartApplication'](arg1, arg2); +} + +export function SaveConfigDialog() { + return window['go']['main']['App']['SaveConfigDialog'](); +} + +export function SendNotification(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['SendNotification'](arg1, arg2, arg3, arg4); +} + +export function SetConfigField(arg1, arg2) { + return window['go']['main']['App']['SetConfigField'](arg1, arg2); +} + +export function Update(arg1) { + return window['go']['main']['App']['Update'](arg1); +} + +export function UpdateAsAdmin(arg1) { + return window['go']['main']['App']['UpdateAsAdmin'](arg1); +} diff --git a/frontend/src/wailsjs/go/models.ts b/frontend/src/wailsjs/go/models.ts new file mode 100644 index 0000000..cb37aed --- /dev/null +++ b/frontend/src/wailsjs/go/models.ts @@ -0,0 +1,81 @@ +export namespace main { + + export class Config { + theme?: string; + useSystemTitleBar?: boolean; + enableLogging?: boolean; + enableTrace?: boolean; + enableDebug?: boolean; + enableInfo?: boolean; + enableWarn?: boolean; + enableError?: boolean; + enableFatal?: boolean; + maxLogFiles?: number; + language?: string; + saveWindowStatus?: boolean; + windowStartState?: number; + windowStartPositionX?: number; + windowStartPositionY?: number; + windowStartSizeX?: number; + windowStartSizeY?: number; + windowScale?: number; + opacity?: number; + windowEffect?: number; + checkForUpdates?: boolean; + lastUpdateCheck?: number; + + static createFrom(source: any = {}) { + return new Config(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.theme = source["theme"]; + this.useSystemTitleBar = source["useSystemTitleBar"]; + this.enableLogging = source["enableLogging"]; + this.enableTrace = source["enableTrace"]; + this.enableDebug = source["enableDebug"]; + this.enableInfo = source["enableInfo"]; + this.enableWarn = source["enableWarn"]; + this.enableError = source["enableError"]; + this.enableFatal = source["enableFatal"]; + this.maxLogFiles = source["maxLogFiles"]; + this.language = source["language"]; + this.saveWindowStatus = source["saveWindowStatus"]; + this.windowStartState = source["windowStartState"]; + this.windowStartPositionX = source["windowStartPositionX"]; + this.windowStartPositionY = source["windowStartPositionY"]; + this.windowStartSizeX = source["windowStartSizeX"]; + this.windowStartSizeY = source["windowStartSizeY"]; + this.windowScale = source["windowScale"]; + this.opacity = source["opacity"]; + this.windowEffect = source["windowEffect"]; + this.checkForUpdates = source["checkForUpdates"]; + this.lastUpdateCheck = source["lastUpdateCheck"]; + } + } + export class UpdateInfo { + updateAvailable: boolean; + currentVersion: string; + latestVersion: string; + name: string; + releaseNotes: string; + downloadUrl: string; + + static createFrom(source: any = {}) { + return new UpdateInfo(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.updateAvailable = source["updateAvailable"]; + this.currentVersion = source["currentVersion"]; + this.latestVersion = source["latestVersion"]; + this.name = source["name"]; + this.releaseNotes = source["releaseNotes"]; + this.downloadUrl = source["downloadUrl"]; + } + } + +} + diff --git a/frontend/src/wailsjs/runtime/package.json b/frontend/src/wailsjs/runtime/package.json new file mode 100644 index 0000000..1e7c8a5 --- /dev/null +++ b/frontend/src/wailsjs/runtime/package.json @@ -0,0 +1,24 @@ +{ + "name": "@wailsapp/runtime", + "version": "2.0.0", + "description": "Wails Javascript runtime library", + "main": "runtime.js", + "types": "runtime.d.ts", + "scripts": { + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wailsapp/wails.git" + }, + "keywords": [ + "Wails", + "Javascript", + "Go" + ], + "author": "Lea Anthony ", + "license": "MIT", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "homepage": "https://github.com/wailsapp/wails#readme" +} diff --git a/frontend/src/wailsjs/runtime/runtime.d.ts b/frontend/src/wailsjs/runtime/runtime.d.ts new file mode 100644 index 0000000..94778df --- /dev/null +++ b/frontend/src/wailsjs/runtime/runtime.d.ts @@ -0,0 +1,249 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export interface Position { + x: number; + y: number; +} + +export interface Size { + w: number; + h: number; +} + +export interface Screen { + isCurrent: boolean; + isPrimary: boolean; + width : number + height : number +} + +// Environment information such as platform, buildtype, ... +export interface EnvironmentInfo { + buildType: string; + platform: string; + arch: string; +} + +// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) +// emits the given event. Optional data may be passed with the event. +// This will trigger any event listeners. +export function EventsEmit(eventName: string, ...data: any): void; + +// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. +export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) +// sets up a listener for the given event name, but will only trigger a given number times. +export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; + +// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) +// sets up a listener for the given event name, but will only trigger once. +export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) +// unregisters the listener for the given event name. +export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; + +// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) +// unregisters all listeners. +export function EventsOffAll(): void; + +// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) +// logs the given message as a raw message +export function LogPrint(message: string): void; + +// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) +// logs the given message at the `trace` log level. +export function LogTrace(message: string): void; + +// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) +// logs the given message at the `debug` log level. +export function LogDebug(message: string): void; + +// [LogError](https://wails.io/docs/reference/runtime/log#logerror) +// logs the given message at the `error` log level. +export function LogError(message: string): void; + +// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) +// logs the given message at the `fatal` log level. +// The application will quit after calling this method. +export function LogFatal(message: string): void; + +// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) +// logs the given message at the `info` log level. +export function LogInfo(message: string): void; + +// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) +// logs the given message at the `warning` log level. +export function LogWarning(message: string): void; + +// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) +// Forces a reload by the main application as well as connected browsers. +export function WindowReload(): void; + +// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) +// Reloads the application frontend. +export function WindowReloadApp(): void; + +// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) +// Sets the window AlwaysOnTop or not on top. +export function WindowSetAlwaysOnTop(b: boolean): void; + +// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) +// *Windows only* +// Sets window theme to system default (dark/light). +export function WindowSetSystemDefaultTheme(): void; + +// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) +// *Windows only* +// Sets window to light theme. +export function WindowSetLightTheme(): void; + +// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) +// *Windows only* +// Sets window to dark theme. +export function WindowSetDarkTheme(): void; + +// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) +// Centers the window on the monitor the window is currently on. +export function WindowCenter(): void; + +// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) +// Sets the text in the window title bar. +export function WindowSetTitle(title: string): void; + +// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) +// Makes the window full screen. +export function WindowFullscreen(): void; + +// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) +// Restores the previous window dimensions and position prior to full screen. +export function WindowUnfullscreen(): void; + +// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) +// Returns the state of the window, i.e. whether the window is in full screen mode or not. +export function WindowIsFullscreen(): Promise; + +// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) +// Sets the width and height of the window. +export function WindowSetSize(width: number, height: number): Promise; + +// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) +// Gets the width and height of the window. +export function WindowGetSize(): Promise; + +// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) +// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMaxSize(width: number, height: number): void; + +// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) +// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMinSize(width: number, height: number): void; + +// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) +// Sets the window position relative to the monitor the window is currently on. +export function WindowSetPosition(x: number, y: number): void; + +// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) +// Gets the window position relative to the monitor the window is currently on. +export function WindowGetPosition(): Promise; + +// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) +// Hides the window. +export function WindowHide(): void; + +// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) +// Shows the window, if it is currently hidden. +export function WindowShow(): void; + +// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) +// Maximises the window to fill the screen. +export function WindowMaximise(): void; + +// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) +// Toggles between Maximised and UnMaximised. +export function WindowToggleMaximise(): void; + +// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) +// Restores the window to the dimensions and position prior to maximising. +export function WindowUnmaximise(): void; + +// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) +// Returns the state of the window, i.e. whether the window is maximised or not. +export function WindowIsMaximised(): Promise; + +// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) +// Minimises the window. +export function WindowMinimise(): void; + +// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) +// Restores the window to the dimensions and position prior to minimising. +export function WindowUnminimise(): void; + +// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) +// Returns the state of the window, i.e. whether the window is minimised or not. +export function WindowIsMinimised(): Promise; + +// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) +// Returns the state of the window, i.e. whether the window is normal or not. +export function WindowIsNormal(): Promise; + +// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) +// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. +export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; + +// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) +// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. +export function ScreenGetAll(): Promise; + +// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) +// Opens the given URL in the system browser. +export function BrowserOpenURL(url: string): void; + +// [Environment](https://wails.io/docs/reference/runtime/intro#environment) +// Returns information about the environment +export function Environment(): Promise; + +// [Quit](https://wails.io/docs/reference/runtime/intro#quit) +// Quits the application. +export function Quit(): void; + +// [Hide](https://wails.io/docs/reference/runtime/intro#hide) +// Hides the application. +export function Hide(): void; + +// [Show](https://wails.io/docs/reference/runtime/intro#show) +// Shows the application. +export function Show(): void; + +// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) +// Returns the current text stored on clipboard +export function ClipboardGetText(): Promise; + +// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) +// Sets a text on the clipboard +export function ClipboardSetText(text: string): Promise; + +// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) +// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. +export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void + +// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) +// OnFileDropOff removes the drag and drop listeners and handlers. +export function OnFileDropOff() :void + +// Check if the file path resolver is available +export function CanResolveFilePaths(): boolean; + +// Resolves file paths for an array of files +export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/frontend/src/wailsjs/runtime/runtime.js b/frontend/src/wailsjs/runtime/runtime.js new file mode 100644 index 0000000..623397b --- /dev/null +++ b/frontend/src/wailsjs/runtime/runtime.js @@ -0,0 +1,238 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export function LogPrint(message) { + window.runtime.LogPrint(message); +} + +export function LogTrace(message) { + window.runtime.LogTrace(message); +} + +export function LogDebug(message) { + window.runtime.LogDebug(message); +} + +export function LogInfo(message) { + window.runtime.LogInfo(message); +} + +export function LogWarning(message) { + window.runtime.LogWarning(message); +} + +export function LogError(message) { + window.runtime.LogError(message); +} + +export function LogFatal(message) { + window.runtime.LogFatal(message); +} + +export function EventsOnMultiple(eventName, callback, maxCallbacks) { + return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); +} + +export function EventsOn(eventName, callback) { + return EventsOnMultiple(eventName, callback, -1); +} + +export function EventsOff(eventName, ...additionalEventNames) { + return window.runtime.EventsOff(eventName, ...additionalEventNames); +} + +export function EventsOnce(eventName, callback) { + return EventsOnMultiple(eventName, callback, 1); +} + +export function EventsEmit(eventName) { + let args = [eventName].slice.call(arguments); + return window.runtime.EventsEmit.apply(null, args); +} + +export function WindowReload() { + window.runtime.WindowReload(); +} + +export function WindowReloadApp() { + window.runtime.WindowReloadApp(); +} + +export function WindowSetAlwaysOnTop(b) { + window.runtime.WindowSetAlwaysOnTop(b); +} + +export function WindowSetSystemDefaultTheme() { + window.runtime.WindowSetSystemDefaultTheme(); +} + +export function WindowSetLightTheme() { + window.runtime.WindowSetLightTheme(); +} + +export function WindowSetDarkTheme() { + window.runtime.WindowSetDarkTheme(); +} + +export function WindowCenter() { + window.runtime.WindowCenter(); +} + +export function WindowSetTitle(title) { + window.runtime.WindowSetTitle(title); +} + +export function WindowFullscreen() { + window.runtime.WindowFullscreen(); +} + +export function WindowUnfullscreen() { + window.runtime.WindowUnfullscreen(); +} + +export function WindowIsFullscreen() { + return window.runtime.WindowIsFullscreen(); +} + +export function WindowGetSize() { + return window.runtime.WindowGetSize(); +} + +export function WindowSetSize(width, height) { + window.runtime.WindowSetSize(width, height); +} + +export function WindowSetMaxSize(width, height) { + window.runtime.WindowSetMaxSize(width, height); +} + +export function WindowSetMinSize(width, height) { + window.runtime.WindowSetMinSize(width, height); +} + +export function WindowSetPosition(x, y) { + window.runtime.WindowSetPosition(x, y); +} + +export function WindowGetPosition() { + return window.runtime.WindowGetPosition(); +} + +export function WindowHide() { + window.runtime.WindowHide(); +} + +export function WindowShow() { + window.runtime.WindowShow(); +} + +export function WindowMaximise() { + window.runtime.WindowMaximise(); +} + +export function WindowToggleMaximise() { + window.runtime.WindowToggleMaximise(); +} + +export function WindowUnmaximise() { + window.runtime.WindowUnmaximise(); +} + +export function WindowIsMaximised() { + return window.runtime.WindowIsMaximised(); +} + +export function WindowMinimise() { + window.runtime.WindowMinimise(); +} + +export function WindowUnminimise() { + window.runtime.WindowUnminimise(); +} + +export function WindowSetBackgroundColour(R, G, B, A) { + window.runtime.WindowSetBackgroundColour(R, G, B, A); +} + +export function ScreenGetAll() { + return window.runtime.ScreenGetAll(); +} + +export function WindowIsMinimised() { + return window.runtime.WindowIsMinimised(); +} + +export function WindowIsNormal() { + return window.runtime.WindowIsNormal(); +} + +export function BrowserOpenURL(url) { + window.runtime.BrowserOpenURL(url); +} + +export function Environment() { + return window.runtime.Environment(); +} + +export function Quit() { + window.runtime.Quit(); +} + +export function Hide() { + window.runtime.Hide(); +} + +export function Show() { + window.runtime.Show(); +} + +export function ClipboardGetText() { + return window.runtime.ClipboardGetText(); +} + +export function ClipboardSetText(text) { + return window.runtime.ClipboardSetText(text); +} + +/** + * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * + * @export + * @callback OnFileDropCallback + * @param {number} x - x coordinate of the drop + * @param {number} y - y coordinate of the drop + * @param {string[]} paths - A list of file paths. + */ + +/** + * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. + * + * @export + * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) + */ +export function OnFileDrop(callback, useDropTarget) { + return window.runtime.OnFileDrop(callback, useDropTarget); +} + +/** + * OnFileDropOff removes the drag and drop listeners and handlers. + */ +export function OnFileDropOff() { + return window.runtime.OnFileDropOff(); +} + +export function CanResolveFilePaths() { + return window.runtime.CanResolveFilePaths(); +} + +export function ResolveFilePaths(files) { + return window.runtime.ResolveFilePaths(files); +} \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 0942c02..0464f87 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,12 +1,12 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import path from 'path' -import { fileURLToPath } from 'url' -import { dirname } from 'path' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import path from "path"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; // if in ESM context -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); // https://vitejs.dev/config/ export default defineConfig({ @@ -14,7 +14,6 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(__dirname, "./src"), - "wailsjs": path.resolve(__dirname, "./wailsjs") - } - } -}) + }, + }, +}); diff --git a/wails.json b/wails.json index a06d1d7..7e4d6db 100644 --- a/wails.json +++ b/wails.json @@ -2,6 +2,7 @@ "$schema": "https://wails.io/schemas/config.v2.json", "name": "iconium", "outputfilename": "iconium", + "@/wailsjsdir": "./frontend/src", "frontend:install": "yarn", "frontend:build": "yarn build", "frontend:dev:watcher": "yarn dev", From e17c304e1e282e3096e7b1ed2f0b9d147dd227c4 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 4 Jul 2024 00:41:00 +0300 Subject: [PATCH 105/221] add notify argument --- app.go | 6 ++++++ frontend/public/locales/en-US.json | 5 +++-- frontend/public/locales/tr-TR.json | 5 +++-- update.go | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app.go b/app.go index 6171cad..666ee6a 100644 --- a/app.go +++ b/app.go @@ -81,6 +81,12 @@ func (a *App) domReady(ctx context.Context) { runtime.WindowExecJS(a.ctx, fmt.Sprintf(`window.goto("%s");`, args[i+1])) i++ } + case "--notify": + if i+4 < len(args) { + runtime.LogInfo(a.ctx, "Notify: "+args[i+1]+" "+args[i+2]+" "+args[i+3]+" "+args[i+4]) + a.SendNotification(args[i+1], args[i+2], args[i+3], args[i+4]) + i += 4 + } } } } diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index 28ddfa1..196b598 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -108,10 +108,11 @@ "updating": "Updating...", "failed_to_check_for_updates": "Failed to check for updates. Please try again later.", "failed_to_download_update": "Failed to download update. Please try again later.", - "failed_to_apply_update" : "Failed to apply update.", + "failed_to_apply_update": "Failed to apply update.", "update_applied": "Update applied successfully.", "restarting": "Restarting...", - "need_admin_privileges": "Administration privileges are needed to update the application." + "need_admin_privileges": "Administration privileges are needed to update the application.", + "update_successful": "Update was successfully applied." } } } diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index 23d8524..7ef00ef 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -108,10 +108,11 @@ "updating": "Güncelleniyor...", "failed_to_check_for_updates": "Güncellemeler kontrol edilemedi. Lütfen daha sonra tekrar deneyin.", "failed_to_download_update": "Güncelleme indirilemedi. Lütfen daha sonra tekrar deneyin.", - "failed_to_apply_update" : "Güncelleme uygulanamadı.", + "failed_to_apply_update": "Güncelleme uygulanamadı.", "update_applied": "Güncelleme uygulandı.", "restarting": "Yeniden başlatılıyor...", - "need_admin_privileges": "Uygulamayı güncellemek için yönetici yetkileri gereklidir." + "need_admin_privileges": "Uygulamayı güncellemek için yönetici yetkileri gereklidir.", + "update_successful": "Güncelleme başarıyla uygulandı." } } } diff --git a/update.go b/update.go index e438dfc..e9f858e 100644 --- a/update.go +++ b/update.go @@ -159,7 +159,7 @@ func (app *App) Update(downloadUrl string) error { app.SendNotification("settings.setting.update.update_applied", "settings.setting.update.restarting", "", "success") // Restart the application - app.RestartApplication(false, []string{"--goto", "settings__update"}) + app.RestartApplication(false, []string{"--goto", "settings__update", "--notify", "settings.setting.update.update_successful", "", "", "success"}) return nil } From 1aedb6683ce17be600d0e1863e544ce11b8a42fb Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 4 Jul 2024 22:24:30 +0300 Subject: [PATCH 106/221] move some jobs after dom is loaded --- app.go | 42 ++++++++++++++++++++++++++++++++++++++++++ main.go | 41 +---------------------------------------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/app.go b/app.go index 666ee6a..28596ff 100644 --- a/app.go +++ b/app.go @@ -2,9 +2,11 @@ package main import ( "context" + "encoding/json" "fmt" "os" "os/exec" + "path/filepath" "strings" "github.com/gen2brain/beeep" @@ -64,6 +66,23 @@ func (a *App) startup(ctx context.Context) { // domReady is called after front-end resources have been loaded func (a *App) domReady(ctx context.Context) { + // Show window + runtime.WindowShow(appContext) + + // Get version from wails.json + var wailsDeccodedJSON map[string]interface{} + err := json.Unmarshal(wailsJSON, &wailsDeccodedJSON) + if err != nil { + runtime.LogError(appContext, "Failed to decode wails.json: "+err.Error()) + } + version = wailsDeccodedJSON["info"].(map[string]interface{})["productVersion"].(string) + + // Check if admin privileges are needed + NeedsAdminPrivileges = checkAdminPrivileges() + + // Get launch args + args = os.Args[1:] + // Check updates if *config.CheckForUpdates { updateInfo := a.CheckForUpdate() @@ -249,3 +268,26 @@ func (a *App) RestartApplication(admin bool, args []string) error { os.Exit(0) return nil } + +func checkAdminPrivileges() bool { + executable, err := os.Executable() + if err != nil { + return false + } + + directory := filepath.Dir(executable) + + // Try to create a temporary file in the directory + tempFile, err := os.CreateTemp(directory, "test") + if err != nil { + return os.IsPermission(err) + } + tempFile.Close() + os.Remove(tempFile.Name()) + + return false +} + +func (app *App) NeedsAdminPrivileges() bool { + return NeedsAdminPrivileges +} diff --git a/main.go b/main.go index c48c24e..01350f0 100644 --- a/main.go +++ b/main.go @@ -2,11 +2,9 @@ package main import ( "embed" - "encoding/json" "log" "os" "path" - "path/filepath" "time" "github.com/wailsapp/wails/v2" @@ -82,20 +80,6 @@ func main() { windowEffect = windows.Tabbed } - // Get version from wails.json - var wailsDeccodedJSON map[string]interface{} - err = json.Unmarshal(wailsJSON, &wailsDeccodedJSON) - if err != nil { - log.Println(err) - } - version = wailsDeccodedJSON["info"].(map[string]interface{})["productVersion"].(string) - - // Check if admin privileges are needed - NeedsAdminPrivileges = checkAdminPrivileges() - - // Get launch args - args = os.Args[1:] - // Create application with options err = wails.Run(&options.App{ Title: "Iconium", @@ -105,7 +89,7 @@ func main() { MinHeight: 768, DisableResize: false, Frameless: !*config.UseSystemTitleBar, - StartHidden: false, + StartHidden: true, HideWindowOnClose: false, BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255}, AssetServer: &assetserver.Options{ @@ -144,26 +128,3 @@ func main() { log.Fatal(err) } } - -func checkAdminPrivileges() bool { - executable, err := os.Executable() - if err != nil { - return false - } - - directory := filepath.Dir(executable) - - // Try to create a temporary file in the directory - tempFile, err := os.CreateTemp(directory, "test") - if err != nil { - return os.IsPermission(err) - } - tempFile.Close() - os.Remove(tempFile.Name()) - - return false -} - -func (app *App) NeedsAdminPrivileges() bool { - return NeedsAdminPrivileges -} From d5260387a904fdd29a855a521c408b8008ac756c Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 9 Jul 2024 13:47:50 +0300 Subject: [PATCH 107/221] implement packs --- app_paths.go | 26 +- dialog.go | 62 +++ frontend/package.json | 6 +- frontend/src/App.tsx | 5 +- frontend/src/components/Packs.tsx | 512 ++++++++++++++++++++++++ frontend/src/components/SelectImage.tsx | 55 +++ frontend/src/components/ui/form.tsx | 176 ++++++++ frontend/src/components/ui/label.tsx | 24 ++ frontend/src/wailsjs/go/main/App.d.ts | 26 ++ frontend/src/wailsjs/go/main/App.js | 52 +++ frontend/src/wailsjs/go/models.ts | 92 +++++ frontend/wailsjs/go/main/App.d.ts | 31 -- frontend/wailsjs/go/main/App.js | 59 --- frontend/wailsjs/go/models.ts | 81 ---- frontend/wailsjs/runtime/package.json | 24 -- frontend/wailsjs/runtime/runtime.d.ts | 249 ------------ frontend/wailsjs/runtime/runtime.js | 238 ----------- frontend/yarn.lock | 22 + go.mod | 8 +- go.sum | 7 + iconpack.go | 363 +++++++++++++++++ utils.go | 107 +++++ wails.json | 2 +- 23 files changed, 1528 insertions(+), 699 deletions(-) create mode 100644 frontend/src/components/Packs.tsx create mode 100644 frontend/src/components/SelectImage.tsx create mode 100644 frontend/src/components/ui/form.tsx create mode 100644 frontend/src/components/ui/label.tsx delete mode 100644 frontend/wailsjs/go/main/App.d.ts delete mode 100644 frontend/wailsjs/go/main/App.js delete mode 100644 frontend/wailsjs/go/models.ts delete mode 100644 frontend/wailsjs/runtime/package.json delete mode 100644 frontend/wailsjs/runtime/runtime.d.ts delete mode 100644 frontend/wailsjs/runtime/runtime.js create mode 100644 iconpack.go create mode 100644 utils.go diff --git a/app_paths.go b/app_paths.go index 7d77af3..bee7259 100644 --- a/app_paths.go +++ b/app_paths.go @@ -3,7 +3,9 @@ package main import ( "errors" "os" + "os/user" "path" + "path/filepath" "github.com/wailsapp/wails/v2/pkg/runtime" ) @@ -92,18 +94,18 @@ func get_config_path() string { return configPath } -// Create folder if it doesn't exist, return error -func create_folder(folder string) error { - if _, err := os.Stat(folder); os.IsNotExist(err) { - err = os.MkdirAll(folder, 0o755) - if err != nil { - return err - } - } else { - runtime.LogDebug(appContext, "Folder already exists: "+folder) - return nil +// Returns the desktop paths +func get_desktop_paths() []string { + userDir, err := user.Current() + + if err != nil { + return []string{} } - runtime.LogDebug(appContext, "Created folder: "+folder) - return nil + homedir := userDir.HomeDir + desktop := filepath.Join(homedir, "Desktop") + + public := "C:\\Users\\Public\\Desktop" + + return []string{desktop, public} } diff --git a/dialog.go b/dialog.go index 28247d3..b5e7dd3 100644 --- a/dialog.go +++ b/dialog.go @@ -64,6 +64,68 @@ func (a *App) GetLoadConfigPath() string { return path } +func (a *App) GetBase64Png() string { + path, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "Select image", + CanCreateDirectories: true, + Filters: []runtime.FileFilter{ + { + DisplayName: "PNG", + Pattern: "*.png", + }, + }, + }) + + if err != nil { + runtime.LogWarning(a.ctx, err.Error()) + return "" + } + + if path == "" { + return "" + } + + base64Png := GenerateBase64PngFromPath(path) + + runtime.LogInfo(a.ctx, "Image selected: "+path) + + return base64Png +} + +func (a *App) GetIconFolder() string { + path, err := runtime.OpenDirectoryDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "Select folder", + CanCreateDirectories: true, + }) + + if err != nil { + runtime.LogWarning(a.ctx, err.Error()) + return "" + } + + return path +} + +func (a *App) GetIconFile() string { + path, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "Select file", + CanCreateDirectories: true, + Filters: []runtime.FileFilter{ + { + DisplayName: "Shortcut", + Pattern: "*.lnk", + }, + }, + }) + + if err != nil { + runtime.LogWarning(a.ctx, err.Error()) + return "" + } + + return path +} + func (a *App) OpenFileInExplorer(path string) { runtime.LogInfo(a.ctx, "Opening file in explorer: "+path) diff --git a/frontend/package.json b/frontend/package.json index 8ff60f5..f25f475 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,9 @@ "preview": "vite preview" }, "dependencies": { + "@hookform/resolvers": "^3.9.0", "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-slider": "^1.2.0", "@radix-ui/react-slot": "^1.1.0", @@ -29,10 +31,12 @@ "next-themes": "^0.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hook-form": "^7.52.1", "react-i18next": "^14.1.2", "sonner": "^1.5.0", "tailwind-merge": "^2.3.0", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.8" }, "devDependencies": { "@types/react": "^18.3.3", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 04af5fc..9d53323 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,6 +11,7 @@ import { OpenFileInExplorer } from "@/wailsjs/go/main/App"; import React from "react"; import { useConfig } from "./contexts/config-provider"; import { LogDebug } from "@/wailsjs/runtime/runtime"; +import Packs from "./components/Packs"; function App() { const { config, initialConfig } = useConfig(); @@ -98,7 +99,7 @@ function App() {
- +
setTab("packs")}> {t("nav.my_packs")} @@ -114,7 +115,7 @@ function App() { - View your packs here. + Edit your packs here. diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx new file mode 100644 index 0000000..2a16944 --- /dev/null +++ b/frontend/src/components/Packs.tsx @@ -0,0 +1,512 @@ +"use client"; + +import { useState, useEffect, useRef, useCallback } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { + CircleHelp, + Edit, + FolderSearch, + Monitor, + Trash, + Upload, +} from "lucide-react"; +import { + AreYouSureDialog, + AreYouSureDialogRef, +} from "@/components/ui/are-you-sure"; +import { + Dialog, + DialogClose, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import SelectImage from "./SelectImage"; +import { + AddFilesToIconPackFromDesktop, + AddFilesToIconPackFromFolder, + AddFileToIconPackFromPath, + AddIconPack, + DeleteIconPack, + GetIconFile, + GetIconFolder, + GetIconPackInfo, + SetIconPackInfo, + Test, +} from "@/wailsjs/go/main/App"; +import { main } from "@/wailsjs/go/models"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { + SettingContent, + SettingLabel, + SettingsItem, +} from "./ui/settings-group"; + +export default function Packs() { + const [pack, setPack] = useState(""); + const [packInfos, setPackInfos] = useState(); + + const dialogCloseRef = useRef(null); + + const loadPackInfo = async () => { + const packInfos = await GetIconPackInfo(); + setPackInfos(packInfos); + }; + + useEffect(() => { + loadPackInfo(); + }, []); + + return ( + + + + {packInfos?.map((pack) => ( + + ))} + + + + + + + + + Create Icon Pack + + + + + + + {pack && ( + found_pack.metadata.id === pack)! + } + setPack={setPack} + loadPackInfo={loadPackInfo} + /> + )} + + ); +} + +interface PackTriggerProps { + iconPack: main.IconPack; + setPack: (pack: string) => void; + loadPackInfo: () => void; +} + +function PackTrigger({ + iconPack, + setPack, + loadPackInfo, + ...props +}: PackTriggerProps) { + const handleEnable = () => { + iconPack.settings.enabled = !iconPack.settings.enabled; + SetIconPackInfo(iconPack).then(loadPackInfo); + }; + + return ( + setPack(iconPack.metadata.id)} + className="flex justify-between p-4 w-full" + {...props} + > +
+ {iconPack.metadata.icon ? ( + pack-icon + ) : ( + + )} +
+
+ {iconPack.metadata.name} +
+
{iconPack.metadata.version}
+
+
+ +
+ ); +} + +interface PackContentProps { + iconPack: main.IconPack; + setPack: (pack: string) => void; + loadPackInfo: () => void; +} + +function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { + const [editMode, setEditMode] = useState(false); + const [iconPackInfo, setIconPackInfo] = useState(iconPack); + const dialogRef = useRef(null); + + // Reset the icon pack when the pack changes + useEffect(() => { + handleCancel(); + setIconPackInfo(iconPack); + }, [iconPack]); + + const handleChange = ( + field: keyof main.IconPack["metadata"], + value: string + ) => { + setIconPackInfo( + (prev) => + ({ + ...prev, + metadata: { + ...prev.metadata, + [field]: value, + }, + } as main.IconPack) + ); + }; + + const handleEdit = () => { + setIconPackInfo(iconPack); + setEditMode(true); + }; + + const handleSave = () => { + SetIconPackInfo(iconPackInfo).then(loadPackInfo); + setEditMode(false); + }; + + const handleCancel = () => { + setIconPackInfo(iconPack); + setEditMode(false); + }; + + const handleDelete = () => { + DeleteIconPack(iconPack.metadata.id).then(() => { + setPack(""); + loadPackInfo(); + }); + }; + + const handleSettingChange = ( + field: keyof main.IconPack["settings"], + value: boolean + ) => { + const newIconPackInfo = { + ...iconPackInfo, + settings: { + ...iconPackInfo.settings, + [field]: value, + }, + } as main.IconPack; + + setIconPackInfo(newIconPackInfo); + SetIconPackInfo(newIconPackInfo).then(loadPackInfo); + }; + + const fields: (keyof main.IconPack["metadata"])[] = [ + "name", + "version", + "author", + ]; + + const openDialog = useCallback(() => { + if (dialogRef.current) { + dialogRef.current.openDialog(); + } + }, []); + + return ( + +
+
+ Pack Information +
+
+
+ handleChange("icon", icon)} + sizeClass="w-12 h-12" + editSizeClass="w-7 h-7" + editable={editMode} + /> +
+ {fields.map((field) => ( +
+
+ {field.charAt(0).toUpperCase() + field.slice(1)} +
+ {editMode ? ( + handleChange(field, e.target.value)} + /> + ) : ( +
{iconPack.metadata[field]}
+ )} +
+ ))} +
+
+
+ {!editMode ? ( + <> + + + + + ) : ( + <> + + + + )} +
+
+
+ +
+
+ Pack Actions +
+
+ +
+ +
+ + + +
+
+ +
+
+ Pack Settings +
+ + Enabled + + + handleSettingChange("enabled", enabled) + } + /> + + +
+
+ ); +} + +interface CreatePackFormProps { + loadPackInfo: () => void; + dialogCloseRef: React.RefObject; +} + +function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { + const formSchema = z.object({ + icon: z.string(), + name: z + .string() + .min(1, { message: "Name is required" }) + .max(32, { message: "Name can not exceed 32 characters" }) + // Check for windows file name compatibility + .regex(/^(?!\.)/, { message: "Name can not start with a period" }) + .regex(/^(?!.*\.$)/, { + message: "Name can not end with a period", + }), + version: z.string().regex(/^v[0-9]{1,4}\.[0-9]{1,4}\.[0-9]{1,4}$/, { + message: "Version must be in the format: v1.0.0", + }), + author: z + .string() + .max(32, { message: "Author can not exceed 32 characters" }), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + icon: "", + name: "", + version: "v1.0.0", + author: "", + }, + }); + + function onSubmit(data: z.infer) { + AddIconPack(data.name, data.version, data.author, data.icon).then(() => { + loadPackInfo(); + dialogCloseRef.current?.click(); + }); + } + + return ( +
+ + ( + + Icon + form.setValue("icon", icon)} + /> + + + )} + /> + ( + + Name + + + + + + )} + /> + ( + + Version + + + + + + )} + /> + ( + + Author + + + + + + )} + /> + + + + ); +} diff --git a/frontend/src/components/SelectImage.tsx b/frontend/src/components/SelectImage.tsx new file mode 100644 index 0000000..eb048f4 --- /dev/null +++ b/frontend/src/components/SelectImage.tsx @@ -0,0 +1,55 @@ +import { GetBase64Png } from "@/wailsjs/go/main/App"; +import { CircleHelp, Upload } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +interface SelectImageProps { + icon: string; + onIconChange: (icon: string) => void; + editable?: boolean; + className?: string; + sizeClass: string; + editSizeClass: string; +} + +const SelectImage: React.FC = ({ + icon, + onIconChange, + editable = true, + className, + sizeClass, + editSizeClass, + ...rest +}) => { + const handleIconEdit = () => { + GetBase64Png().then((base64) => { + onIconChange(base64); + }); + }; + + const selectedIcon = icon ? ( + pack-icon + ) : ( + + ); + + return ( + + ); +}; + +export default SelectImage; diff --git a/frontend/src/components/ui/form.tsx b/frontend/src/components/ui/form.tsx new file mode 100644 index 0000000..4603f8b --- /dev/null +++ b/frontend/src/components/ui/form.tsx @@ -0,0 +1,176 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +
- + e.stopPropagation()} />
); } @@ -420,7 +430,7 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { Enabled handleSettingChange("enabled", enabled) } @@ -434,12 +444,16 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) {
{iconPackInfo.files?.map((file) => file.hasIcon ? ( - ) : null )} diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index 7423a0b..d2720c9 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -14,8 +14,6 @@ export function CheckForUpdate():Promise; export function DeleteIconPack(arg1:string):Promise; -export function GetBase64Image(arg1:string,arg2:string):Promise; - export function GetBase64Png():Promise; export function GetConfig():Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 1d22950..332bef4 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -26,10 +26,6 @@ export function DeleteIconPack(arg1) { return window['go']['main']['App']['DeleteIconPack'](arg1); } -export function GetBase64Image(arg1, arg2) { - return window['go']['main']['App']['GetBase64Image'](arg1, arg2); -} - export function GetBase64Png() { return window['go']['main']['App']['GetBase64Png'](); } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ae951cf..56bccda 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -224,120 +224,115 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" -"@esbuild/aix-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" - integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== - -"@esbuild/android-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" - integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== - -"@esbuild/android-arm@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" - integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== - -"@esbuild/android-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" - integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== - -"@esbuild/darwin-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" - integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== - -"@esbuild/darwin-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" - integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== - -"@esbuild/freebsd-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" - integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== - -"@esbuild/freebsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" - integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== - -"@esbuild/linux-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" - integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== - -"@esbuild/linux-arm@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" - integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== - -"@esbuild/linux-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" - integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== - -"@esbuild/linux-loong64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" - integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== - -"@esbuild/linux-mips64el@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" - integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== - -"@esbuild/linux-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" - integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== - -"@esbuild/linux-riscv64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" - integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== - -"@esbuild/linux-s390x@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" - integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== - -"@esbuild/linux-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" - integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== - -"@esbuild/netbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" - integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== - -"@esbuild/openbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" - integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== - -"@esbuild/sunos-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" - integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== - -"@esbuild/win32-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" - integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== - -"@esbuild/win32-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" - integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== - -"@esbuild/win32-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" - integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" @@ -961,86 +956,6 @@ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== -"@rollup/rollup-android-arm-eabi@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz#f0da481244b7d9ea15296b35f7fe39cd81157396" - integrity sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA== - -"@rollup/rollup-android-arm64@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz#82ab3c575f4235fb647abea5e08eec6cf325964e" - integrity sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg== - -"@rollup/rollup-darwin-arm64@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz#6a530452e68a9152809ce58de1f89597632a085b" - integrity sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ== - -"@rollup/rollup-darwin-x64@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz#47727479f5ca292cf434d7e75af2725b724ecbc7" - integrity sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA== - -"@rollup/rollup-linux-arm-gnueabihf@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz#46193c498aa7902a8db89ac00128060320e84fef" - integrity sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g== - -"@rollup/rollup-linux-arm-musleabihf@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz#22d831fe239643c1d05c98906420325cee439d85" - integrity sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ== - -"@rollup/rollup-linux-arm64-gnu@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz#19abd33695ec9d588b4a858d122631433084e4a3" - integrity sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ== - -"@rollup/rollup-linux-arm64-musl@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz#d60af8c0b9be424424ff96a0ba19fce65d26f6ab" - integrity sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ== - -"@rollup/rollup-linux-powerpc64le-gnu@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz#b1194e5ed6d138fdde0842d126fccde74a90f457" - integrity sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ== - -"@rollup/rollup-linux-riscv64-gnu@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz#f5a635c017b9bff8b856b0221fbd5c0e3373b7ec" - integrity sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg== - -"@rollup/rollup-linux-s390x-gnu@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz#f1043d9f4026bf6995863cb3f8dd4732606e4baa" - integrity sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg== - -"@rollup/rollup-linux-x64-gnu@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz#1e781730be445119f06c9df5f185e193bc82c610" - integrity sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g== - -"@rollup/rollup-linux-x64-musl@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz#08f12e1965d6f27d6898ff932592121cca6abc4b" - integrity sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ== - -"@rollup/rollup-win32-arm64-msvc@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz#4a5dcbbe7af7d41cac92b09798e7c1831da1f599" - integrity sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g== - -"@rollup/rollup-win32-ia32-msvc@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz#075b0713de627843a73b4cf0e087c56b53e9d780" - integrity sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg== - -"@rollup/rollup-win32-x64-msvc@4.18.1": - version "4.18.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz#0cb240c147c0dfd0e3eaff4cc060a772d39e155c" - integrity sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw== - "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -1074,11 +989,6 @@ dependencies: "@babel/types" "^7.20.7" -"@types/estree@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== - "@types/prop-types@*": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" @@ -1552,34 +1462,33 @@ es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -esbuild@^0.21.3: - version "0.21.5" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" - integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== +esbuild@^0.18.10: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== optionalDependencies: - "@esbuild/aix-ppc64" "0.21.5" - "@esbuild/android-arm" "0.21.5" - "@esbuild/android-arm64" "0.21.5" - "@esbuild/android-x64" "0.21.5" - "@esbuild/darwin-arm64" "0.21.5" - "@esbuild/darwin-x64" "0.21.5" - "@esbuild/freebsd-arm64" "0.21.5" - "@esbuild/freebsd-x64" "0.21.5" - "@esbuild/linux-arm" "0.21.5" - "@esbuild/linux-arm64" "0.21.5" - "@esbuild/linux-ia32" "0.21.5" - "@esbuild/linux-loong64" "0.21.5" - "@esbuild/linux-mips64el" "0.21.5" - "@esbuild/linux-ppc64" "0.21.5" - "@esbuild/linux-riscv64" "0.21.5" - "@esbuild/linux-s390x" "0.21.5" - "@esbuild/linux-x64" "0.21.5" - "@esbuild/netbsd-x64" "0.21.5" - "@esbuild/openbsd-x64" "0.21.5" - "@esbuild/sunos-x64" "0.21.5" - "@esbuild/win32-arm64" "0.21.5" - "@esbuild/win32-ia32" "0.21.5" - "@esbuild/win32-x64" "0.21.5" + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" escalade@^3.1.2: version "3.1.2" @@ -1778,7 +1687,7 @@ fraction.js@^4.3.7: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== -fsevents@~2.3.2, fsevents@~2.3.3: +fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -2369,7 +2278,7 @@ postcss@^8.4.23, postcss@^8.4.38: picocolors "^1.0.0" source-map-js "^1.2.0" -postcss@^8.4.39: +postcss@^8.4.27: version "8.4.39" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== @@ -2520,29 +2429,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rollup@^4.13.0: - version "4.18.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.18.1.tgz#18a606df5e76ca53b8a69f2d8eab256d69dda851" - integrity sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A== - dependencies: - "@types/estree" "1.0.5" +rollup@^3.27.1: + version "3.29.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.18.1" - "@rollup/rollup-android-arm64" "4.18.1" - "@rollup/rollup-darwin-arm64" "4.18.1" - "@rollup/rollup-darwin-x64" "4.18.1" - "@rollup/rollup-linux-arm-gnueabihf" "4.18.1" - "@rollup/rollup-linux-arm-musleabihf" "4.18.1" - "@rollup/rollup-linux-arm64-gnu" "4.18.1" - "@rollup/rollup-linux-arm64-musl" "4.18.1" - "@rollup/rollup-linux-powerpc64le-gnu" "4.18.1" - "@rollup/rollup-linux-riscv64-gnu" "4.18.1" - "@rollup/rollup-linux-s390x-gnu" "4.18.1" - "@rollup/rollup-linux-x64-gnu" "4.18.1" - "@rollup/rollup-linux-x64-musl" "4.18.1" - "@rollup/rollup-win32-arm64-msvc" "4.18.1" - "@rollup/rollup-win32-ia32-msvc" "4.18.1" - "@rollup/rollup-win32-x64-msvc" "4.18.1" fsevents "~2.3.2" run-parallel@^1.1.9: @@ -2846,16 +2737,16 @@ util@^0.10.3: dependencies: inherits "2.0.3" -vite@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.3.tgz#5265b1f0a825b3b6564c2d07524777c83e3c04c2" - integrity sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A== +vite@^4.0.0: + version "4.5.3" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.3.tgz#d88a4529ea58bae97294c7e2e6f0eab39a50fb1a" + integrity sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg== dependencies: - esbuild "^0.21.3" - postcss "^8.4.39" - rollup "^4.13.0" + esbuild "^0.18.10" + postcss "^8.4.27" + rollup "^3.27.1" optionalDependencies: - fsevents "~2.3.3" + fsevents "~2.3.2" void-elements@3.1.0: version "3.1.0" diff --git a/iconpack.go b/iconpack.go index 42f3425..ffcb04f 100644 --- a/iconpack.go +++ b/iconpack.go @@ -240,20 +240,16 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { fileInfo.Destination = ConvertToGeneralPath(fileInfo.Destination) if strings.ToLower(filepath.Ext(link.StringData.IconLocation)) == ".ico" { - base64Image := GenerateBase64PngFromPath(link.StringData.IconLocation) - - if base64Image == "" { - return FileInfo{}, errors.New("failed to generate base64 image") - } - - fileInfo.HasIcon = true - // Save to file - iconPath := filepath.Join(packsFolder, packId, "icons", fileInfo.Id) - err := os.WriteFile(iconPath, []byte(base64Image), 0644) + iconPath := filepath.Join(packsFolder, packId, "icons", fileInfo.Id+".png") + err = ConvertToPng(link.StringData.IconLocation, iconPath) if err != nil { - return FileInfo{}, err + runtime.LogError(appContext, err.Error()) + } else { + fileInfo.HasIcon = true } } + + return fileInfo, nil } file, err := os.Open(path) diff --git a/image.go b/image.go new file mode 100644 index 0000000..bc5d8ce --- /dev/null +++ b/image.go @@ -0,0 +1,99 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" +) + +var allowedImageExtensions = []string{".ico"} + +func ConvertToPng(path string, destination string) error { + extension := filepath.Ext(path) + + if !contains(allowedImageExtensions, extension) { + return fmt.Errorf("invalid image extension: %s", extension) + } + + tempIconFolder := filepath.Join(tempFolder, "icons") + err := create_folder(tempIconFolder) + if err != nil { + return err + } + defer os.RemoveAll(tempIconFolder) + tempIconPath := filepath.Join(tempIconFolder, "icon.ico") + + // Build magick command arguments + args := []string{path, "-alpha", "on", "-background", "none", tempIconPath} + + // Execute magick command silently + cmd := exec.Command(imageMagickPath, args...) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + err = cmd.Run() + if err != nil { + return fmt.Errorf("error converting image: %w\n%s", err, stderr.String()) + } + + // Delete the icons other than the largest one + files, err := os.ReadDir(tempIconFolder) + if err != nil { + return err + } + + largestWidth := -1 + var largestFile string + for _, file := range files { + width, err := GetImageWidth(filepath.Join(tempIconFolder, file.Name())) + if err != nil { + return err + } + + if width > largestWidth { + largestWidth = width + largestFile = file.Name() + } + } + + // Move the largest icon to the destination + err = os.Rename(filepath.Join(tempIconFolder, largestFile), destination) + if err != nil { + return err + } + + return nil +} + +func GetImageWidth(path string) (int, error) { + extension := filepath.Ext(path) + + if !contains(allowedImageExtensions, extension) { + return -1, fmt.Errorf("invalid image extension: %s", extension) + } + + // Build magick command arguments + args := []string{"identify", "-ping", "-format", "%w", path} + + // Execute magick command silently + cmd := exec.Command(imageMagickPath, args...) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + output, err := cmd.Output() + if err != nil { + return -1, fmt.Errorf("error getting image width: %w\n%s", err, stderr.String()) + } + + width, err := strconv.Atoi(strings.TrimSpace(string(output))) + + if err != nil { + return -1, err + } + + return width, nil +} diff --git a/main.go b/main.go index 01350f0..eb0d091 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,13 @@ package main import ( "embed" + "fmt" "log" + "net/http" "os" "path" + "path/filepath" + "strings" "time" "github.com/wailsapp/wails/v2" @@ -27,6 +31,27 @@ var version string var NeedsAdminPrivileges bool var args []string +type FileLoader struct { + http.Handler +} + +func NewFileLoader() *FileLoader { + return &FileLoader{} +} + +func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) { + var err error + requestedFilename := strings.TrimPrefix(filepath.Join("C:\\Users\\bedoy\\AppData\\Roaming\\iconium", req.URL.Path), "/") + println("Requesting file:", requestedFilename) + fileData, err := os.ReadFile(requestedFilename) + if err != nil { + res.WriteHeader(http.StatusBadRequest) + res.Write([]byte(fmt.Sprintf("Could not load file %s", requestedFilename))) + } + + res.Write(fileData) +} + func main() { // Create an instance of the app structure app := NewApp() @@ -93,7 +118,8 @@ func main() { HideWindowOnClose: false, BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255}, AssetServer: &assetserver.Options{ - Assets: assets, + Assets: assets, + Handler: NewFileLoader(), }, Menu: nil, Logger: fileLogger, diff --git a/utils.go b/utils.go index 4a13371..d9022ca 100644 --- a/utils.go +++ b/utils.go @@ -134,11 +134,3 @@ func ConvertToFullPath(path string) string { return path } } - -func (app *App) GetBase64Image(packId string, fileId string) string { - bytes, err := os.ReadFile(filepath.Join(packsFolder, packId, "icons", fileId)) - if err != nil { - runtime.LogError(appContext, "Error reading file: "+err.Error()) - } - return string(bytes) -} From d444c9ee57052c2c7ce9c8f3218e117fb32299a3 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 16 Jul 2024 19:28:56 +0300 Subject: [PATCH 111/221] implement png-ico conversion --- app_paths.go | 6 ++ frontend/src/components/Packs.tsx | 13 +++- frontend/src/wailsjs/go/main/App.d.ts | 2 + frontend/src/wailsjs/go/main/App.js | 4 ++ iconpack.go | 45 +++++++++++++ image.go | 91 +++++++++++++++++++++++++-- 6 files changed, 154 insertions(+), 7 deletions(-) diff --git a/app_paths.go b/app_paths.go index 06481f2..c7ea79e 100644 --- a/app_paths.go +++ b/app_paths.go @@ -15,6 +15,7 @@ var logsFolder string var savedConfigFolder string var activeIconFolder string var tempFolder string +var maskFolder string var configPath string var appIconPath string @@ -38,6 +39,7 @@ func path_init() error { savedConfigFolder = path.Join(appFolder, "savedconfigs") activeIconFolder = path.Join(appFolder, "icons") tempFolder = path.Join(appFolder, "temp") + maskFolder = path.Join(appFolder, "masks") configPath = path.Join(appFolder, "config.json") appIconPath = path.Join(appFolder, "appicon.png") @@ -68,6 +70,10 @@ func path_init() error { if err != nil { return err } + err = create_folder(maskFolder) + if err != nil { + return err + } runtime.LogTrace(appContext, "Creating folders complete") diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 699c241..63088bf 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -34,6 +34,7 @@ import { AddFilesToIconPackFromFolder, AddFileToIconPackFromPath, AddIconPack, + ApplyIconPack, DeleteIconPack, GetIconFile, GetIconFolder, @@ -190,7 +191,11 @@ function PackTrigger({
{iconPack.metadata.version}
- e.stopPropagation()} /> + e.stopPropagation()} + /> ); } @@ -355,7 +360,11 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { Pack Actions
-
diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index d2720c9..b3dfaff 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -10,6 +10,8 @@ export function AddFilesToIconPackFromFolder(arg1:string,arg2:string,arg3:boolea export function AddIconPack(arg1:string,arg2:string,arg3:string,arg4:string):Promise; +export function ApplyIconPack(arg1:string):Promise; + export function CheckForUpdate():Promise; export function DeleteIconPack(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 332bef4..4716359 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -18,6 +18,10 @@ export function AddIconPack(arg1, arg2, arg3, arg4) { return window['go']['main']['App']['AddIconPack'](arg1, arg2, arg3, arg4); } +export function ApplyIconPack(arg1) { + return window['go']['main']['App']['ApplyIconPack'](arg1); +} + export function CheckForUpdate() { return window['go']['main']['App']['CheckForUpdate'](); } diff --git a/iconpack.go b/iconpack.go index ffcb04f..31e6657 100644 --- a/iconpack.go +++ b/iconpack.go @@ -359,6 +359,51 @@ func (a *App) AddFilesToIconPackFromDesktop(id string) { a.AddFilesToIconPackFromFolder(id, public, true) } +func (a *App) ApplyIconPack(id string) { + pack, err := ReadIconPack(id) + + if err != nil { + runtime.LogError(appContext, err.Error()) + return + } + + err = pack.Apply() + + if err != nil { + runtime.LogError(appContext, err.Error()) + } +} + +func (pack *IconPack) Apply() error { + for _, file := range pack.Files { + + if file.HasIcon { + iconPath := filepath.Join(packsFolder, pack.Metadata.Id, "icons", file.Id+".png") + if _, err := os.Stat(iconPath); err != nil { + runtime.LogError(appContext, err.Error()) + continue + } + + // Copy to icons folder + targetFolder := filepath.Join(activeIconFolder, pack.Metadata.Id) + err := create_folder(targetFolder) + if err != nil { + runtime.LogError(appContext, err.Error()) + continue + } + targetPath := filepath.Join(targetFolder, file.Id+".ico") + err = ConvertToIco(iconPath, targetPath) + + if err != nil { + runtime.LogError(appContext, err.Error()) + continue + } + } + } + + return nil +} + func (a *App) Test() { a.AddFilesToIconPackFromFolder("02edc52e-152a-4224-b63f-1282cb9cfc53", "C:\\Users\\bedoy\\Desktop", true) } diff --git a/image.go b/image.go index bc5d8ce..b8d2904 100644 --- a/image.go +++ b/image.go @@ -3,29 +3,110 @@ package main import ( "bytes" "fmt" + "math" "os" "os/exec" "path/filepath" "strconv" "strings" + + "github.com/google/uuid" ) -var allowedImageExtensions = []string{".ico"} +var allowedImageExtensionsPng = []string{".ico"} + +var allowedImageExtensionsIco = []string{".png", ".jpg", ".jpeg"} + +func GetMaskPath(radius int) (string, error) { + if radius <= 0 { + radius = 1 + } else if radius > 100 { + radius = 100 + } + + // Create a rounded rectangle mask + maskPath := filepath.Join(maskFolder, fmt.Sprintf("mask_%d.png", radius)) + + // Check if the mask already exists + if _, err := os.Stat(maskPath); err == nil { + return maskPath, nil + } + + // Round the corner radius to the closest integer + roundedRadius := int(math.Round(float64(radius) * 1.28)) + + maskArgs := []string{ + "-size", "256x256", + "xc:none", + "-draw", fmt.Sprintf("roundrectangle 0,0,255,255,%d,%d", roundedRadius, roundedRadius), + maskPath, + } + + // Execute ImageMagick command to create mask + maskCmd := exec.Command(imageMagickPath, maskArgs...) + var maskStderr bytes.Buffer + maskCmd.Stderr = &maskStderr + + err := maskCmd.Run() + if err != nil { + return "", fmt.Errorf("error creating mask: %w\n%s", err, maskStderr.String()) + } + + return maskPath, nil +} + +// ConvertToIco converts an image to an ICO file with specified corner radius +func ConvertToIco(path string, destination string) error { + extension := filepath.Ext(path) + + // Check if the input file has a valid image extension + if !contains([]string{".png", ".jpg", ".jpeg", ".bmp", ".gif"}, extension) { + return fmt.Errorf("invalid image extension: %s", extension) + } + + maskPath, err := GetMaskPath(0) + + if err != nil { + return err + } + + // Build ImageMagick command arguments to apply the mask and convert to ICO + args := []string{ + path, + maskPath, + "-compose", "DstIn", + "-composite", + "-define", "icon:auto-resize=16,24,32,48,64,72,96,128,256", + destination, + } + + // Execute ImageMagick command silently + cmd := exec.Command(imageMagickPath, args...) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + err = cmd.Run() + if err != nil { + return fmt.Errorf("error converting image: %w\n%s", err, stderr.String()) + } + + return nil +} func ConvertToPng(path string, destination string) error { extension := filepath.Ext(path) - if !contains(allowedImageExtensions, extension) { + if !contains(allowedImageExtensionsPng, extension) { return fmt.Errorf("invalid image extension: %s", extension) } - tempIconFolder := filepath.Join(tempFolder, "icons") + tempIconFolder := filepath.Join(tempFolder, "iconium-"+uuid.NewString()) err := create_folder(tempIconFolder) if err != nil { return err } defer os.RemoveAll(tempIconFolder) - tempIconPath := filepath.Join(tempIconFolder, "icon.ico") + tempIconPath := filepath.Join(tempIconFolder, "icon.png") // Build magick command arguments args := []string{path, "-alpha", "on", "-background", "none", tempIconPath} @@ -72,7 +153,7 @@ func ConvertToPng(path string, destination string) error { func GetImageWidth(path string) (int, error) { extension := filepath.Ext(path) - if !contains(allowedImageExtensions, extension) { + if !contains(allowedImageExtensionsIco, extension) { return -1, fmt.Errorf("invalid image extension: %s", extension) } From 1d6015d4699e6e5d77d335853dc5dc6254f7abb0 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 15:13:02 +0300 Subject: [PATCH 112/221] add opacity and corner radius settings --- frontend/src/components/Image.tsx | 18 ++++++-- frontend/src/components/Packs.tsx | 77 +++++++++++++++++++++++++++++-- frontend/src/wailsjs/go/models.ts | 4 ++ iconpack.go | 10 +++- image.go | 30 +++++++----- main.go | 3 ++ 6 files changed, 121 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/Image.tsx b/frontend/src/components/Image.tsx index 2131e9a..33c2efe 100644 --- a/frontend/src/components/Image.tsx +++ b/frontend/src/components/Image.tsx @@ -5,20 +5,30 @@ import { Skeleton } from "./ui/skeleton"; interface ImageProps { src: string; // Assuming `icon` is a URL or path to the image className?: string; + cornerRadius?: number; + opacity?: number; } -const Image: React.FC = ({ src, className, ...rest }) => { +const Image: React.FC = ({ + src, + className, + cornerRadius = 0, + opacity = 100, + ...rest +}) => { const [loading, setLoading] = useState(true); return ( <> - + setLoading(false)} + style={{ + borderRadius: `${cornerRadius}%`, + opacity: `${opacity / 100}`, + }} {...rest} /> diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 63088bf..a8baecc 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -54,10 +54,12 @@ import { } from "@/components/ui/form"; import { SettingContent, + SettingDescription, SettingLabel, SettingsItem, } from "./ui/settings-group"; import Image from "./Image"; +import { Slider } from "./ui/my-slider"; export default function Packs() { const [pack, setPack] = useState(""); @@ -76,6 +78,7 @@ export default function Packs() { }) ); const [packInfos, setPackInfos] = useState(); + const [selectedPackKeyCount, setSelectedPackKeyCount] = useState(0); const dialogCloseRef = useRef(null); @@ -98,6 +101,10 @@ export default function Packs() { reloadSelectedPack(); }, [pack]); + useEffect(() => { + setSelectedPackKeyCount(selectedPackKeyCount + 1); + }, [selectedPack]); + return ( @@ -132,6 +139,7 @@ export default function Packs() { {pack && ( (null); + const [loading, setLoading] = useState(true); + const [cornerRadius, setCornerRadius] = useState( + iconPack.settings.cornerRadius + ); + const [opacity, setOpacity] = useState(iconPack.settings.opacity); + // Reset the icon pack when the pack changes useEffect(() => { - handleCancel(); + setLoading(true); setIconPackInfo(iconPack); + setLoading(false); }, [iconPack]); const handleChange = ( @@ -257,7 +272,7 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { const handleSettingChange = ( field: keyof main.IconPack["settings"], - value: boolean + value: boolean | number ) => { const newIconPackInfo = { ...iconPackInfo, @@ -435,7 +450,7 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) {
Pack Settings
- + Enabled + + +
+ Corner Radius + + Change the corner radius of the icons in this pack. + +
+ +
+
0%
+ setCornerRadius(value[0] as number)} + onPointerUp={() => + handleSettingChange("cornerRadius", cornerRadius) + } + defaultValue={[iconPackInfo.settings.cornerRadius]} + min={0} + max={50} + step={1} + className={"w-56 cursor-pointer"} + /> +
50%
+
+ ({cornerRadius}%) +
+
+
+
+ + +
+ Opacity + + Change the opacity of the icons in this pack. + +
+ +
+
10%
+ setOpacity(value[0] as number)} + onPointerUp={() => handleSettingChange("opacity", opacity)} + defaultValue={[iconPackInfo.settings.opacity]} + min={10} + max={100} + step={1} + className={"w-56 cursor-pointer"} + /> +
100%
+
({opacity}%)
+
+
+
@@ -463,6 +532,8 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { ".png" } className="w-10 h-10" + cornerRadius={cornerRadius} + opacity={opacity} /> ) : null )} diff --git a/frontend/src/wailsjs/go/models.ts b/frontend/src/wailsjs/go/models.ts index ab00eec..3ac639c 100644 --- a/frontend/src/wailsjs/go/models.ts +++ b/frontend/src/wailsjs/go/models.ts @@ -80,6 +80,8 @@ export namespace main { } export class IconPackSettings { enabled: boolean; + cornerRadius: number; + opacity: number; static createFrom(source: any = {}) { return new IconPackSettings(source); @@ -88,6 +90,8 @@ export namespace main { constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); this.enabled = source["enabled"]; + this.cornerRadius = source["cornerRadius"]; + this.opacity = source["opacity"]; } } export class Metadata { diff --git a/iconpack.go b/iconpack.go index 31e6657..79cf30a 100644 --- a/iconpack.go +++ b/iconpack.go @@ -40,7 +40,9 @@ type IconPack struct { } type IconPackSettings struct { - Enabled bool `json:"enabled"` + Enabled bool `json:"enabled"` + CornerRadius int `json:"cornerRadius"` + Opacity int `json:"opacity"` } var allowedFileExtensions = []string{".lnk"} @@ -56,6 +58,10 @@ func CreateIconPack(name string, version string, author string, icon string) (Ic iconPack.Metadata.Icon = icon iconPack.Files = []FileInfo{} + iconPack.Settings.Enabled = false + iconPack.Settings.CornerRadius = 0 + iconPack.Settings.Opacity = 100 + err := WriteIconPack(iconPack) if err != nil { @@ -392,7 +398,7 @@ func (pack *IconPack) Apply() error { continue } targetPath := filepath.Join(targetFolder, file.Id+".ico") - err = ConvertToIco(iconPath, targetPath) + err = ConvertToIco(iconPath, targetPath, pack.Settings) if err != nil { runtime.LogError(appContext, err.Error()) diff --git a/image.go b/image.go index b8d2904..2a1c0a5 100644 --- a/image.go +++ b/image.go @@ -17,15 +17,20 @@ var allowedImageExtensionsPng = []string{".ico"} var allowedImageExtensionsIco = []string{".png", ".jpg", ".jpeg"} -func GetMaskPath(radius int) (string, error) { +func GetMaskPath(radius, opacity int) (string, error) { if radius <= 0 { radius = 1 } else if radius > 100 { radius = 100 } + if opacity < 0 { + opacity = 0 + } else if opacity > 100 { + opacity = 100 + } // Create a rounded rectangle mask - maskPath := filepath.Join(maskFolder, fmt.Sprintf("mask_%d.png", radius)) + maskPath := filepath.Join(maskFolder, fmt.Sprintf("mask_r%d_o%d.png", radius, opacity)) // Check if the mask already exists if _, err := os.Stat(maskPath); err == nil { @@ -33,21 +38,22 @@ func GetMaskPath(radius int) (string, error) { } // Round the corner radius to the closest integer - roundedRadius := int(math.Round(float64(radius) * 1.28)) + roundedRadius := int(math.Round(float64(radius) * 2.56)) + opacityPercent := fmt.Sprintf("%.2f", float64(opacity)/100.0) maskArgs := []string{ "-size", "256x256", "xc:none", - "-draw", fmt.Sprintf("roundrectangle 0,0,255,255,%d,%d", roundedRadius, roundedRadius), + "-draw", fmt.Sprintf("fill rgba(0,0,0,%s) roundrectangle 0,0,255,255,%d,%d", opacityPercent, roundedRadius, roundedRadius), maskPath, } // Execute ImageMagick command to create mask - maskCmd := exec.Command(imageMagickPath, maskArgs...) + cmd = exec.Command(imageMagickPath, maskArgs...) var maskStderr bytes.Buffer - maskCmd.Stderr = &maskStderr + cmd.Stderr = &maskStderr - err := maskCmd.Run() + err := cmd.Run() if err != nil { return "", fmt.Errorf("error creating mask: %w\n%s", err, maskStderr.String()) } @@ -56,7 +62,7 @@ func GetMaskPath(radius int) (string, error) { } // ConvertToIco converts an image to an ICO file with specified corner radius -func ConvertToIco(path string, destination string) error { +func ConvertToIco(path string, destination string, settings IconPackSettings) error { extension := filepath.Ext(path) // Check if the input file has a valid image extension @@ -64,7 +70,7 @@ func ConvertToIco(path string, destination string) error { return fmt.Errorf("invalid image extension: %s", extension) } - maskPath, err := GetMaskPath(0) + maskPath, err := GetMaskPath(settings.CornerRadius, settings.Opacity) if err != nil { return err @@ -81,7 +87,7 @@ func ConvertToIco(path string, destination string) error { } // Execute ImageMagick command silently - cmd := exec.Command(imageMagickPath, args...) + cmd = exec.Command(imageMagickPath, args...) var stderr bytes.Buffer cmd.Stderr = &stderr @@ -112,7 +118,7 @@ func ConvertToPng(path string, destination string) error { args := []string{path, "-alpha", "on", "-background", "none", tempIconPath} // Execute magick command silently - cmd := exec.Command(imageMagickPath, args...) + cmd = exec.Command(imageMagickPath, args...) var stderr bytes.Buffer cmd.Stderr = &stderr @@ -161,7 +167,7 @@ func GetImageWidth(path string) (int, error) { args := []string{"identify", "-ping", "-format", "%w", path} // Execute magick command silently - cmd := exec.Command(imageMagickPath, args...) + cmd = exec.Command(imageMagickPath, args...) var stderr bytes.Buffer cmd.Stderr = &stderr diff --git a/main.go b/main.go index eb0d091..f245356 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "os" + "os/exec" "path" "path/filepath" "strings" @@ -18,6 +19,8 @@ import ( "github.com/wailsapp/wails/v2/pkg/options/windows" ) +var cmd *exec.Cmd + //go:embed all:frontend/dist var assets embed.FS From c90f2014cf788c482e60d1b757b04d4423390538 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 15:19:56 +0300 Subject: [PATCH 113/221] customize scrollbar --- frontend/package.json | 1 + frontend/src/index.css | 2 +- frontend/tailwind.config.js | 1 + frontend/yarn.lock | 5 +++++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 4c03d70..bacc81f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,6 +50,7 @@ "eslint-plugin-react-refresh": "^0.4.7", "path": "^0.12.7", "postcss": "^8.4.38", + "tailwind-scrollbar": "^3.1.0", "tailwindcss": "^3.4.4", "typescript": "^5.5.2", "url": "^0.11.3", diff --git a/frontend/src/index.css b/frontend/src/index.css index 8148d53..74e7293 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -82,7 +82,7 @@ overflow: hidden; } * { - @apply border-border; + @apply border-border scrollbar-thin scrollbar-thumb-primary scrollbar-track-transparent; } body { @apply bg-background text-foreground; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 857eb81..e12c406 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -96,6 +96,7 @@ module.exports = { }, }, plugins: [ + require('tailwind-scrollbar'), require("tailwindcss-animate"), plugin(function ({ matchUtilities, theme }) { matchUtilities( diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 56bccda..cf37065 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2591,6 +2591,11 @@ tailwind-merge@^2.3.0: dependencies: "@babel/runtime" "^7.24.1" +tailwind-scrollbar@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/tailwind-scrollbar/-/tailwind-scrollbar-3.1.0.tgz#ff7596407b6da5209261d8ff03860ab9206a59e3" + integrity sha512-pmrtDIZeHyu2idTejfV59SbaJyvp1VRjYxAjZBH0jnyrPRo6HL1kD5Glz8VPagasqr6oAx6M05+Tuw429Z8jxg== + tailwindcss-animate@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz#318b692c4c42676cc9e67b19b78775742388bef4" From b13a8bf3e62cb5efb1ceba31c7fc51d45b4c067c Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 15:57:03 +0300 Subject: [PATCH 114/221] simplify packs frontend --- frontend/src/components/Packs.tsx | 164 +++++++++++++++++------------- 1 file changed, 91 insertions(+), 73 deletions(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index a8baecc..206f3fa 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -60,23 +60,10 @@ import { } from "./ui/settings-group"; import Image from "./Image"; import { Slider } from "./ui/my-slider"; +import { Skeleton } from "./ui/skeleton"; export default function Packs() { const [pack, setPack] = useState(""); - const [selectedPack, setSelectedPack] = useState( - main.IconPack.createFrom({ - metadata: { - id: "", - name: "", - description: "", - icon: "", - }, - files: [], - settings: { - enabled: false, - }, - }) - ); const [packInfos, setPackInfos] = useState(); const [selectedPackKeyCount, setSelectedPackKeyCount] = useState(0); @@ -88,9 +75,7 @@ export default function Packs() { }; const reloadSelectedPack = () => { - if (pack) { - GetIconPack(pack).then(setSelectedPack); - } + setSelectedPackKeyCount(selectedPackKeyCount + 1); }; useEffect(() => { @@ -101,10 +86,6 @@ export default function Packs() { reloadSelectedPack(); }, [pack]); - useEffect(() => { - setSelectedPackKeyCount(selectedPackKeyCount + 1); - }, [selectedPack]); - return ( @@ -140,7 +121,7 @@ export default function Packs() { {pack && ( @@ -209,62 +190,73 @@ function PackTrigger({ } interface PackContentProps { - iconPack: main.IconPack; + iconPackId: string; setPack: (pack: string) => void; loadPackInfo: () => void; } -function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { +function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { + const [loading, setLoading] = useState(true); const [editMode, setEditMode] = useState(false); - const [iconPackInfo, setIconPackInfo] = useState(iconPack); + const [iconPackInfo, setIconPackInfo] = useState( + main.IconPack.createFrom({}) + ); + const [editedIconPackInfo, setEditedIconPackInfo] = useState( + main.IconPack.createFrom({}) + ); const dialogRef = useRef(null); - const [loading, setLoading] = useState(true); - const [cornerRadius, setCornerRadius] = useState( - iconPack.settings.cornerRadius - ); - const [opacity, setOpacity] = useState(iconPack.settings.opacity); + const [cornerRadius, setCornerRadius] = useState(-1); + const [opacity, setOpacity] = useState(-1); - // Reset the icon pack when the pack changes useEffect(() => { - setLoading(true); - setIconPackInfo(iconPack); - setLoading(false); - }, [iconPack]); + GetIconPack(iconPackId).then((iconPack) => { + setIconPackInfo(iconPack); + setCornerRadius(iconPack.settings.cornerRadius); + setOpacity(iconPack.settings.opacity); + setLoading(false); + }); + }, []); const handleChange = ( field: keyof main.IconPack["metadata"], - value: string + value: string, + editMode?: boolean ) => { - setIconPackInfo( - (prev) => - ({ - ...prev, - metadata: { - ...prev.metadata, - [field]: value, - }, - } as main.IconPack) - ); + const newIconPackInfo = { + ...iconPackInfo, + metadata: { + ...iconPackInfo.metadata, + [field]: value, + }, + } as main.IconPack; + + if (editMode) { + setEditedIconPackInfo(newIconPackInfo); + } else { + setIconPackInfo(newIconPackInfo); + } }; const handleEdit = () => { - setIconPackInfo(iconPack); + setEditedIconPackInfo(iconPackInfo); setEditMode(true); }; const handleSave = () => { - SetIconPackInfo(iconPackInfo).then(loadPackInfo); + setIconPackInfo(editedIconPackInfo); + SetIconPackInfo(editedIconPackInfo).then(() => { + loadPackInfo(); + }); setEditMode(false); }; const handleCancel = () => { - setIconPackInfo(iconPack); setEditMode(false); }; const handleDelete = () => { - DeleteIconPack(iconPack.metadata.id).then(() => { + DeleteIconPack(iconPackId).then(() => { setPack(""); loadPackInfo(); }); @@ -272,7 +264,8 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { const handleSettingChange = ( field: keyof main.IconPack["settings"], - value: boolean | number + value: boolean | number, + editMode?: boolean ) => { const newIconPackInfo = { ...iconPackInfo, @@ -282,8 +275,12 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { }, } as main.IconPack; - setIconPackInfo(newIconPackInfo); - SetIconPackInfo(newIconPackInfo).then(loadPackInfo); + if (editMode) { + setEditedIconPackInfo(newIconPackInfo); + } else { + setIconPackInfo(newIconPackInfo); + SetIconPackInfo(newIconPackInfo).then(loadPackInfo); + } }; const fields: (keyof main.IconPack["metadata"])[] = [ @@ -298,9 +295,20 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { } }, []); + if (loading) { + var skeletons = []; + + for (let i = 0; i < 4; i++) { + skeletons.push() + } + return ( +
{skeletons}
+ ); + } + return (
@@ -310,8 +318,12 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) {
handleChange("icon", icon)} + icon={ + editMode + ? editedIconPackInfo.metadata.icon + : iconPackInfo.metadata.icon + } + onIconChange={(icon) => handleChange("icon", icon, true)} sizeClass="w-12 h-12" editSizeClass="w-7 h-7" editable={editMode} @@ -324,11 +336,15 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) {
{editMode ? ( handleChange(field, e.target.value)} + value={editedIconPackInfo.metadata[field]} + onChange={(e) => + handleChange(field, e.target.value, true) + } /> ) : ( -
{iconPack.metadata[field]}
+
+ {iconPackInfo.metadata[field]} +
)}
))} @@ -378,7 +394,7 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { @@ -392,12 +408,12 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { GetIconFolder().then((folder) => { if (folder) { AddFilesToIconPackFromFolder( - iconPack.metadata.id, + iconPackInfo.metadata.id, folder, true ).then(() => { loadPackInfo(); - GetIconPack(iconPack.metadata.id).then((iconPack) => { + GetIconPack(iconPackInfo.metadata.id).then((iconPack) => { setIconPackInfo(iconPack); }); }); @@ -411,12 +427,14 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { variant={"secondary"} className="flex gap-2.5" onClick={() => { - AddFilesToIconPackFromDesktop(iconPack.metadata.id).then(() => { - loadPackInfo(); - GetIconPack(iconPack.metadata.id).then((iconPack) => { - setIconPackInfo(iconPack); - }); - }); + AddFilesToIconPackFromDesktop(iconPackInfo.metadata.id).then( + () => { + loadPackInfo(); + GetIconPack(iconPackInfo.metadata.id).then((iconPack) => { + setIconPackInfo(iconPack); + }); + } + ); }} > Add Icons From Desktop @@ -428,12 +446,12 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { GetIconFile().then((file) => { if (file) { AddFileToIconPackFromPath( - iconPack.metadata.id, + iconPackInfo.metadata.id, file, true ).then(() => { loadPackInfo(); - GetIconPack(iconPack.metadata.id).then((iconPack) => { + GetIconPack(iconPackInfo.metadata.id).then((iconPack) => { setIconPackInfo(iconPack); }); }); @@ -471,8 +489,8 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) {
-
0%
- 0%
+ setCornerRadius(value[0] as number)} onPointerUp={() => handleSettingChange("cornerRadius", cornerRadius) @@ -526,7 +544,7 @@ function PackContent({ iconPack, setPack, loadPackInfo }: PackContentProps) { key={file.id} src={ "packs\\" + - iconPack.metadata.id + + iconPackInfo.metadata.id + "\\icons\\" + file.id + ".png" From 685a0ca2c79fec7f1b423ac08ae5f4526ea007b5 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 16:21:46 +0300 Subject: [PATCH 115/221] add loading indicators --- frontend/src/components/Packs.tsx | 154 +++++++++++++++++++++--------- 1 file changed, 107 insertions(+), 47 deletions(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 206f3fa..46375d4 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -12,6 +12,10 @@ import { CircleHelp, Edit, FolderSearch, + Loader, + Loader2, + LoaderCircleIcon, + LoaderPinwheel, Monitor, Trash, Upload, @@ -209,6 +213,18 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { const [cornerRadius, setCornerRadius] = useState(-1); const [opacity, setOpacity] = useState(-1); + const [applyRunning, setApplyRunning] = useState(false); + const [addIconsFromFolderRunning, setAddIconsFromFolderRunning] = + useState(false); + const [addIconsFromDesktopRunning, setAddIconsFromDesktopRunning] = + useState(false); + const [addIconsRunning, setAddIconsRunning] = useState(false); + const running = + applyRunning || + addIconsFromFolderRunning || + addIconsFromDesktopRunning || + addIconsRunning; + useEffect(() => { GetIconPack(iconPackId).then((iconPack) => { setIconPackInfo(iconPack); @@ -289,6 +305,69 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { "author", ]; + const handleApplyIconPack = () => { + setApplyRunning(true); + ApplyIconPack(iconPackInfo.metadata.id).finally(() => { + setApplyRunning(false); + }); + }; + + const handleAddIconsFromFolder = () => { + GetIconFolder().then((folder) => { + if (folder) { + setAddIconsFromFolderRunning(true); + + AddFilesToIconPackFromFolder( + iconPackInfo.metadata.id, + folder, + true + ).then(() => { + GetIconPack(iconPackInfo.metadata.id) + .then((iconPack) => { + setIconPackInfo(iconPack); + }) + .finally(() => { + setAddIconsFromFolderRunning(false); + }); + }); + } + }); + }; + + const handleAddIconsFromDesktop = () => { + setAddIconsFromDesktopRunning(true); + + AddFilesToIconPackFromDesktop(iconPackInfo.metadata.id).then(() => { + GetIconPack(iconPackInfo.metadata.id) + .then((iconPack) => { + setIconPackInfo(iconPack); + }) + .finally(() => { + setAddIconsFromDesktopRunning(false); + }); + }); + }; + + const handleAddIcon = () => { + GetIconFile().then((file) => { + if (file) { + setAddIconsRunning(true); + + AddFileToIconPackFromPath(iconPackInfo.metadata.id, file, true).then( + () => { + GetIconPack(iconPackInfo.metadata.id) + .then((iconPack) => { + setIconPackInfo(iconPack); + }) + .finally(() => { + setAddIconsRunning(false); + }); + } + ); + } + }); + }; + const openDialog = useCallback(() => { if (dialogRef.current) { dialogRef.current.openDialog(); @@ -299,7 +378,7 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { var skeletons = []; for (let i = 0; i < 4; i++) { - skeletons.push() + skeletons.push(); } return (
{skeletons}
@@ -394,8 +473,10 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) {
@@ -404,62 +485,41 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) {
From 81f183039a37833b8791326b6a1c6dfefc5c7709 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 17:11:40 +0300 Subject: [PATCH 116/221] use env variables for generalized path --- frontend/src/components/Packs.tsx | 15 +++++------- iconpack.go | 2 +- utils.go | 38 +++++++++++++++++++++++++++---- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 46375d4..f928835 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -12,10 +12,7 @@ import { CircleHelp, Edit, FolderSearch, - Loader, Loader2, - LoaderCircleIcon, - LoaderPinwheel, Monitor, Trash, Upload, @@ -375,13 +372,13 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { }, []); if (loading) { - var skeletons = []; - - for (let i = 0; i < 4; i++) { - skeletons.push(); - } return ( -
{skeletons}
+
+ + + + +
); } diff --git a/iconpack.go b/iconpack.go index 79cf30a..d35d328 100644 --- a/iconpack.go +++ b/iconpack.go @@ -411,7 +411,7 @@ func (pack *IconPack) Apply() error { } func (a *App) Test() { - a.AddFilesToIconPackFromFolder("02edc52e-152a-4224-b63f-1282cb9cfc53", "C:\\Users\\bedoy\\Desktop", true) + runtime.LogDebugf(a.ctx, ConvertToFullPath("${HOMEDRIVE}\\a\\Discord.lnk")) } func (a *App) Test2() { diff --git a/utils.go b/utils.go index d9022ca..e22d622 100644 --- a/utils.go +++ b/utils.go @@ -109,13 +109,38 @@ func GenerateBase64PngFromPath(filePath string) string { func ConvertToGeneralPath(path string) string { desktop, public := get_desktop_paths() + // List of common environment variables to replace + envVars := []string{ + "PROGRAMFILES", + "PROGRAMFILES(X86)", + "APPDATA", + "LOCALAPPDATA", + "PROGRAMDATA", + "USERPROFILE", + "PUBLIC", + "WINDIR", + "SYSTEMROOT", + "HOMEDRIVE", + "SYSTEMDRIVE", + } + if strings.HasPrefix(strings.ToLower(path), strings.ToLower(desktop)) { - return strings.ReplaceAll(path, desktop, "") + path = strings.ReplaceAll(path, desktop, "") } else if strings.HasPrefix(strings.ToLower(path), strings.ToLower(public)) { - return strings.ReplaceAll(path, public, "") - } else { - return path + path = strings.ReplaceAll(path, public, "") + } + + // Replace environment variables + for _, envVar := range envVars { + placeholder := "${" + envVar + "}" + envValue := os.Getenv(envVar) + + if strings.Contains(strings.ToLower(path), strings.ToLower(envValue)) { + path = strings.ReplaceAll(path, envValue, placeholder) + } } + + return path } func ConvertToFullPath(path string) string { @@ -125,12 +150,17 @@ func ConvertToFullPath(path string) string { path1 := strings.ReplaceAll(path, "", desktop) path2 := strings.ReplaceAll(path, "", public) + // Convert the env variable path + path1 = os.ExpandEnv(path1) + path2 = os.ExpandEnv(path2) + if _, err := os.Stat(path1); os.IsNotExist(err) { return path2 } else { return path1 } } else { + path = os.ExpandEnv(path) return path } } From 94a308a6e164a6d249e19c30279142f14c8267fb Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 17:31:58 +0300 Subject: [PATCH 117/221] fix programfiles env variable order --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index e22d622..c6b371d 100644 --- a/utils.go +++ b/utils.go @@ -111,8 +111,8 @@ func ConvertToGeneralPath(path string) string { // List of common environment variables to replace envVars := []string{ - "PROGRAMFILES", "PROGRAMFILES(X86)", + "PROGRAMFILES", "APPDATA", "LOCALAPPDATA", "PROGRAMDATA", From 9dbf1eeee1f806f6bc20de70cad74273745a8f27 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 17:55:04 +0300 Subject: [PATCH 118/221] fix public desktop path --- app_paths.go | 3 ++- iconpack.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app_paths.go b/app_paths.go index c7ea79e..07ee440 100644 --- a/app_paths.go +++ b/app_paths.go @@ -134,7 +134,8 @@ func get_desktop_paths() (string, string) { homedir := userDir.HomeDir desktop := filepath.Join(homedir, "Desktop") - public := "C:\\Users\\Public\\Desktop" + publicDir := os.Getenv("PUBLIC") + public := filepath.Join(publicDir, "Desktop") return desktop, public } diff --git a/iconpack.go b/iconpack.go index d35d328..bee12aa 100644 --- a/iconpack.go +++ b/iconpack.go @@ -412,6 +412,7 @@ func (pack *IconPack) Apply() error { func (a *App) Test() { runtime.LogDebugf(a.ctx, ConvertToFullPath("${HOMEDRIVE}\\a\\Discord.lnk")) + runtime.LogDebug(a.ctx, "Desktop paths: "+fmt.Sprint(get_desktop_paths())) } func (a *App) Test2() { From 4102c907a8e19ac07f6c212ed912a85c17306e8a Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 18:32:59 +0300 Subject: [PATCH 119/221] change path generalization logic --- iconpack.go | 2 +- utils.go | 35 ++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/iconpack.go b/iconpack.go index bee12aa..a4b7e71 100644 --- a/iconpack.go +++ b/iconpack.go @@ -411,7 +411,7 @@ func (pack *IconPack) Apply() error { } func (a *App) Test() { - runtime.LogDebugf(a.ctx, ConvertToFullPath("${HOMEDRIVE}\\a\\Discord.lnk")) + runtime.LogDebugf(a.ctx, ConvertToFullPath("${DESKTOP}/*/../Discord.lnk")) runtime.LogDebug(a.ctx, "Desktop paths: "+fmt.Sprint(get_desktop_paths())) } diff --git a/utils.go b/utils.go index c6b371d..354d4cd 100644 --- a/utils.go +++ b/utils.go @@ -125,9 +125,9 @@ func ConvertToGeneralPath(path string) string { } if strings.HasPrefix(strings.ToLower(path), strings.ToLower(desktop)) { - path = strings.ReplaceAll(path, desktop, "") + path = strings.ReplaceAll(path, desktop, "${DESKTOP}") } else if strings.HasPrefix(strings.ToLower(path), strings.ToLower(public)) { - path = strings.ReplaceAll(path, public, "") + path = strings.ReplaceAll(path, public, "${DESKTOP}") } // Replace environment variables @@ -144,23 +144,28 @@ func ConvertToGeneralPath(path string) string { } func ConvertToFullPath(path string) string { - if strings.Contains(path, "") { + path = filepath.Clean(path) + + os.Setenv("DESKTOP", "") + path = os.ExpandEnv(path) + + paths := []string{path} + + if strings.Contains(strings.ToUpper(path), "") { desktop, public := get_desktop_paths() - path1 := strings.ReplaceAll(path, "", desktop) - path2 := strings.ReplaceAll(path, "", public) + path1 := strings.ReplaceAll(path, "", desktop) + path2 := strings.ReplaceAll(path, "", public) - // Convert the env variable path - path1 = os.ExpandEnv(path1) - path2 = os.ExpandEnv(path2) + paths = []string{path1, path2} + } - if _, err := os.Stat(path1); os.IsNotExist(err) { - return path2 - } else { - return path1 + for _, p := range paths { + if _, err := os.Stat(p); !os.IsNotExist(err) { + path = p + break } - } else { - path = os.ExpandEnv(path) - return path } + + return path } From ec97e25379f42b10887e7e485ef102d3178eb330 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 20:12:47 +0300 Subject: [PATCH 120/221] add filepath globbing --- iconpack.go | 2 +- utils.go | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/iconpack.go b/iconpack.go index a4b7e71..1e95db7 100644 --- a/iconpack.go +++ b/iconpack.go @@ -411,7 +411,7 @@ func (pack *IconPack) Apply() error { } func (a *App) Test() { - runtime.LogDebugf(a.ctx, ConvertToFullPath("${DESKTOP}/*/../Discord.lnk")) + runtime.LogDebug(a.ctx, fmt.Sprint(ConvertToFullPath("${DESKTOP}\\o*.lnk"))) runtime.LogDebug(a.ctx, "Desktop paths: "+fmt.Sprint(get_desktop_paths())) } diff --git a/utils.go b/utils.go index 354d4cd..7209287 100644 --- a/utils.go +++ b/utils.go @@ -160,12 +160,17 @@ func ConvertToFullPath(path string) string { paths = []string{path1, path2} } - for _, p := range paths { - if _, err := os.Stat(p); !os.IsNotExist(err) { - path = p - break + for _, path := range paths { + matches, err := filepath.Glob(path) + if err != nil { + runtime.LogErrorf(appContext, "Error globbing path: %s", err) + continue + } + + if len(matches) > 0 { + return matches[0] } } - return path + return "" } From ae35b8e670ed6d5ee38f8e266156dcc625b43685 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 17 Jul 2024 21:34:14 +0300 Subject: [PATCH 121/221] add caching for icon packs --- dialog.go | 20 +++++++++ frontend/src/components/Packs.tsx | 59 ++++---------------------- frontend/src/wailsjs/go/main/App.d.ts | 4 ++ frontend/src/wailsjs/go/main/App.js | 8 ++++ iconpack.go | 60 +++++++++++++++++++++------ 5 files changed, 87 insertions(+), 64 deletions(-) diff --git a/dialog.go b/dialog.go index b5e7dd3..8f58287 100644 --- a/dialog.go +++ b/dialog.go @@ -126,6 +126,26 @@ func (a *App) GetIconFile() string { return path } +func (a *App) GetIconFiles() []string { + paths, err := runtime.OpenMultipleFilesDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "Select file", + CanCreateDirectories: true, + Filters: []runtime.FileFilter{ + { + DisplayName: "Shortcut", + Pattern: "*.lnk", + }, + }, + }) + + if err != nil { + runtime.LogWarning(a.ctx, err.Error()) + return nil + } + + return paths +} + func (a *App) OpenFileInExplorer(path string) { runtime.LogInfo(a.ctx, "Opening file in explorer: "+path) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index f928835..ff34c09 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -11,7 +11,6 @@ import { Switch } from "@/components/ui/switch"; import { CircleHelp, Edit, - FolderSearch, Loader2, Monitor, Trash, @@ -32,13 +31,11 @@ import { import SelectImage from "./SelectImage"; import { AddFilesToIconPackFromDesktop, - AddFilesToIconPackFromFolder, - AddFileToIconPackFromPath, + AddFilesToIconPackFromPath, AddIconPack, ApplyIconPack, DeleteIconPack, - GetIconFile, - GetIconFolder, + GetIconFiles, GetIconPack, GetIconPackInfo, SetIconPackInfo, @@ -211,16 +208,11 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { const [opacity, setOpacity] = useState(-1); const [applyRunning, setApplyRunning] = useState(false); - const [addIconsFromFolderRunning, setAddIconsFromFolderRunning] = - useState(false); + useState(false); const [addIconsFromDesktopRunning, setAddIconsFromDesktopRunning] = useState(false); const [addIconsRunning, setAddIconsRunning] = useState(false); - const running = - applyRunning || - addIconsFromFolderRunning || - addIconsFromDesktopRunning || - addIconsRunning; + const running = applyRunning || addIconsFromDesktopRunning || addIconsRunning; useEffect(() => { GetIconPack(iconPackId).then((iconPack) => { @@ -309,28 +301,6 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { }); }; - const handleAddIconsFromFolder = () => { - GetIconFolder().then((folder) => { - if (folder) { - setAddIconsFromFolderRunning(true); - - AddFilesToIconPackFromFolder( - iconPackInfo.metadata.id, - folder, - true - ).then(() => { - GetIconPack(iconPackInfo.metadata.id) - .then((iconPack) => { - setIconPackInfo(iconPack); - }) - .finally(() => { - setAddIconsFromFolderRunning(false); - }); - }); - } - }); - }; - const handleAddIconsFromDesktop = () => { setAddIconsFromDesktopRunning(true); @@ -346,11 +316,11 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { }; const handleAddIcon = () => { - GetIconFile().then((file) => { - if (file) { + GetIconFiles().then((files) => { + if (files) { setAddIconsRunning(true); - AddFileToIconPackFromPath(iconPackInfo.metadata.id, file, true).then( + AddFilesToIconPackFromPath(iconPackInfo.metadata.id, files, true).then( () => { GetIconPack(iconPackInfo.metadata.id) .then((iconPack) => { @@ -479,19 +449,6 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) {
-
diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index b3dfaff..192bdf4 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -8,6 +8,8 @@ export function AddFilesToIconPackFromDesktop(arg1:string):Promise; export function AddFilesToIconPackFromFolder(arg1:string,arg2:string,arg3:boolean):Promise; +export function AddFilesToIconPackFromPath(arg1:string,arg2:Array,arg3:boolean):Promise; + export function AddIconPack(arg1:string,arg2:string,arg3:string,arg4:string):Promise; export function ApplyIconPack(arg1:string):Promise; @@ -24,6 +26,8 @@ export function GetConfigField(arg1:string):Promise; export function GetIconFile():Promise; +export function GetIconFiles():Promise>; + export function GetIconFolder():Promise; export function GetIconPack(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 4716359..18df8cc 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -14,6 +14,10 @@ export function AddFilesToIconPackFromFolder(arg1, arg2, arg3) { return window['go']['main']['App']['AddFilesToIconPackFromFolder'](arg1, arg2, arg3); } +export function AddFilesToIconPackFromPath(arg1, arg2, arg3) { + return window['go']['main']['App']['AddFilesToIconPackFromPath'](arg1, arg2, arg3); +} + export function AddIconPack(arg1, arg2, arg3, arg4) { return window['go']['main']['App']['AddIconPack'](arg1, arg2, arg3, arg4); } @@ -46,6 +50,10 @@ export function GetIconFile() { return window['go']['main']['App']['GetIconFile'](); } +export function GetIconFiles() { + return window['go']['main']['App']['GetIconFiles'](); +} + export function GetIconFolder() { return window['go']['main']['App']['GetIconFolder'](); } diff --git a/iconpack.go b/iconpack.go index 1e95db7..175df92 100644 --- a/iconpack.go +++ b/iconpack.go @@ -49,6 +49,8 @@ var allowedFileExtensions = []string{".lnk"} var iconPacks []IconPack +var iconPackCache map[string]IconPack = map[string]IconPack{} + func CreateIconPack(name string, version string, author string, icon string) (IconPack, error) { var iconPack IconPack iconPack.Metadata.Id = uuid.NewString() @@ -97,6 +99,8 @@ func WriteIconPack(iconPack IconPack) error { return err } + iconPackCache[iconPack.Metadata.Id] = iconPack + return nil } @@ -123,10 +127,16 @@ func ReadIconPack(id string) (IconPack, error) { return IconPack{}, err } + iconPackCache[id] = iconPack + return iconPack, nil } func (a *App) GetIconPack(id string) IconPack { + if iconPack, ok := iconPackCache[id]; ok { + return iconPack + } + iconPack, err := ReadIconPack(id) if err != nil { @@ -187,6 +197,13 @@ func GetIconPackInfo() ([]IconPack, error) { Metadata: metadata, Settings: settings, }) + + cachedPack := iconPackCache[metadata.Id] + if cachedPack.Metadata.Id == metadata.Id { + cachedPack.Metadata = metadata + cachedPack.Settings = settings + iconPackCache[metadata.Id] = cachedPack + } } } @@ -215,6 +232,12 @@ func (a *App) SetIconPackInfo(iconPack IconPack) { for i, pack := range iconPacks { if pack.Metadata.Id == iconPack.Metadata.Id { iconPacks[i] = iconPack + cachedPack := iconPackCache[iconPack.Metadata.Id] + if cachedPack.Metadata.Id == iconPack.Metadata.Id { + cachedPack.Metadata = iconPack.Metadata + cachedPack.Settings = iconPack.Settings + iconPackCache[iconPack.Metadata.Id] = cachedPack + } break } } @@ -307,6 +330,7 @@ func (a *App) DeleteIconPack(id string) error { for i, pack := range iconPacks { if pack.Metadata.Id == id { iconPacks = append(iconPacks[:i], iconPacks[i+1:]...) + iconPackCache[id] = IconPack{} break } } @@ -322,20 +346,35 @@ func (a *App) AddFileToIconPackFromPath(id string, path string, save bool) { return } - for i, pack := range iconPacks { - if pack.Metadata.Id == id { - iconPacks[i].Files = append(iconPacks[i].Files, fileInfo) - if save { - WriteIconPack(iconPacks[i]) - } - + cachedPack := iconPackCache[id] + if cachedPack.Metadata.Id != id { + cachedPack, err = ReadIconPack(id) + if err != nil { + runtime.LogError(a.ctx, fmt.Sprintf("Failed to read icon pack: %s", err.Error())) return } } + cachedPack.Files = append(cachedPack.Files, fileInfo) + iconPackCache[id] = cachedPack + + if save { + WriteIconPack(iconPackCache[id]) + return + } runtime.LogError(a.ctx, "icon pack not found") } +func (a *App) AddFilesToIconPackFromPath(id string, path []string, save bool) { + for _, p := range path { + a.AddFileToIconPackFromPath(id, p, false) + } + + if save { + WriteIconPack(iconPackCache[id]) + } +} + func (a *App) AddFilesToIconPackFromFolder(id string, path string, save bool) { files, err := os.ReadDir(path) @@ -349,12 +388,7 @@ func (a *App) AddFilesToIconPackFromFolder(id string, path string, save bool) { } if save { - for i, pack := range iconPacks { - if pack.Metadata.Id == id { - WriteIconPack(iconPacks[i]) - break - } - } + WriteIconPack(iconPackCache[id]) } } From b708347a5900e63842f9d50016b3809ba821c719 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 18 Jul 2024 17:55:16 +0300 Subject: [PATCH 122/221] do not regenerate icons when settings are identical --- iconpack.go | 65 +++++++++++++++++++++++++++++++++++++++-------------- utils.go | 12 ++++++++++ 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/iconpack.go b/iconpack.go index 175df92..2845b30 100644 --- a/iconpack.go +++ b/iconpack.go @@ -415,35 +415,66 @@ func (a *App) ApplyIconPack(id string) { } func (pack *IconPack) Apply() error { - for _, file := range pack.Files { + targetFolder := filepath.Join(activeIconFolder, pack.Metadata.Id) + err := create_folder(targetFolder) + if err != nil { + return err + } + for _, file := range pack.Files { if file.HasIcon { - iconPath := filepath.Join(packsFolder, pack.Metadata.Id, "icons", file.Id+".png") - if _, err := os.Stat(iconPath); err != nil { - runtime.LogError(appContext, err.Error()) - continue - } + targetPath := filepath.Join(targetFolder, file.Id+".ico") - // Copy to icons folder - targetFolder := filepath.Join(activeIconFolder, pack.Metadata.Id) - err := create_folder(targetFolder) - if err != nil { - runtime.LogError(appContext, err.Error()) - continue + targetPathExists := false + if _, err := os.Stat(targetPath); err == nil { + targetPathExists = true } - targetPath := filepath.Join(targetFolder, file.Id+".ico") - err = ConvertToIco(iconPath, targetPath, pack.Settings) - if err != nil { - runtime.LogError(appContext, err.Error()) - continue + if !targetPathExists || !pack.IsApplied() { + iconPath := filepath.Join(packsFolder, pack.Metadata.Id, "icons", file.Id+".png") + if _, err := os.Stat(iconPath); err != nil { + runtime.LogError(appContext, err.Error()) + continue + } + err = ConvertToIco(iconPath, targetPath, pack.Settings) + + if err != nil { + runtime.LogError(appContext, err.Error()) + continue + } } } } + // Copy settings.json to apply.json + settingsPath := filepath.Join(packsFolder, pack.Metadata.Id, "settings.json") + applyPath := filepath.Join(packsFolder, pack.Metadata.Id, "apply.json") + err = copy_file(settingsPath, applyPath) + if err != nil { + runtime.LogError(appContext, err.Error()) + return err + } + return nil } +func (pack *IconPack) IsApplied() bool { + applyPath := filepath.Join(packsFolder, pack.Metadata.Id, "apply.json") + + if _, err := os.Stat(applyPath); os.IsNotExist(err) { + return false + } + + var apply IconPackSettings + err := readJSON(applyPath, &apply) + if err != nil { + runtime.LogError(appContext, err.Error()) + return false + } + + return apply.CornerRadius == pack.Settings.CornerRadius && apply.Opacity == pack.Settings.Opacity +} + func (a *App) Test() { runtime.LogDebug(a.ctx, fmt.Sprint(ConvertToFullPath("${DESKTOP}\\o*.lnk"))) runtime.LogDebug(a.ctx, "Desktop paths: "+fmt.Sprint(get_desktop_paths())) diff --git a/utils.go b/utils.go index 7209287..fa58ab8 100644 --- a/utils.go +++ b/utils.go @@ -174,3 +174,15 @@ func ConvertToFullPath(path string) string { return "" } + +func copy_file(src string, dst string) error { + input, err := os.ReadFile(src) + if err != nil { + return err + } + err = os.WriteFile(dst, input, 0o644) + if err != nil { + return err + } + return nil +} From d0780d6bfbd51d84bf6a0cbfd00429ad376ab393 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 18 Jul 2024 18:10:30 +0300 Subject: [PATCH 123/221] add settings for file matching --- config.go | 6 ++++++ frontend/public/locales/en-US.json | 1 + frontend/public/locales/tr-TR.json | 1 + .../SettingItems/MatchByDestinationSetting.tsx | 14 ++++++++++++++ .../SettingItems/RenameMatchedFilesSetting.tsx | 14 ++++++++++++++ frontend/src/components/Settings.tsx | 15 +++++++++++++++ frontend/src/wailsjs/go/models.ts | 4 ++++ 7 files changed, 55 insertions(+) create mode 100644 frontend/src/components/SettingItems/MatchByDestinationSetting.tsx create mode 100644 frontend/src/components/SettingItems/RenameMatchedFilesSetting.tsx diff --git a/config.go b/config.go index 3beb533..17c1d97 100644 --- a/config.go +++ b/config.go @@ -34,6 +34,8 @@ type Config struct { WindowEffect *int `json:"windowEffect"` // 0 = Auto, 1 = None, 2 = Mica, 3 = Acrylic, 4 = Tabbed CheckForUpdates *bool `json:"checkForUpdates"` // true, false LastUpdateCheck *int `json:"lastUpdateCheck"` // unix timestamp + MatchByDestination *bool `json:"matchByDestination"` // true, false + RenameMatchedFiles *bool `json:"renameMatchedFiles"` // true, false } func GetDefaultConfig() Config { @@ -59,6 +61,8 @@ func GetDefaultConfig() Config { defaultWindowEffect := 0 defaultCheckForUpdates := true defaultLastUpdateCheck := 0 + defaultMatchByDestination := true + defaultRenameMatchedFiles := false return Config{ Theme: &defaultTheme, @@ -83,6 +87,8 @@ func GetDefaultConfig() Config { WindowEffect: &defaultWindowEffect, CheckForUpdates: &defaultCheckForUpdates, LastUpdateCheck: &defaultLastUpdateCheck, + MatchByDestination: &defaultMatchByDestination, + RenameMatchedFiles: &defaultRenameMatchedFiles, } } diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index 196b598..bc21e88 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -24,6 +24,7 @@ "categories": { "general": "General", "application": "Application", + "icon_pack": "Icon Pack", "system": "System", "advanced": "Advanced", "update": "Update" diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index 7ef00ef..9750b09 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -24,6 +24,7 @@ "categories": { "general": "Genel", "application": "Uygulama", + "icon_pack": "İkon Paketi", "system": "Sistem", "advanced": "Gelişmiş", "update": "Güncelleme" diff --git a/frontend/src/components/SettingItems/MatchByDestinationSetting.tsx b/frontend/src/components/SettingItems/MatchByDestinationSetting.tsx new file mode 100644 index 0000000..e75b2ff --- /dev/null +++ b/frontend/src/components/SettingItems/MatchByDestinationSetting.tsx @@ -0,0 +1,14 @@ +import { useTranslation } from "react-i18next"; +import { SwitchConfig } from "./Presets/SwitchConfig"; + +export function MatchByDestinationSetting() { + const { t } = useTranslation(); + + return ( + + ); +} diff --git a/frontend/src/components/SettingItems/RenameMatchedFilesSetting.tsx b/frontend/src/components/SettingItems/RenameMatchedFilesSetting.tsx new file mode 100644 index 0000000..ae6a828 --- /dev/null +++ b/frontend/src/components/SettingItems/RenameMatchedFilesSetting.tsx @@ -0,0 +1,14 @@ +import { useTranslation } from "react-i18next"; +import { SwitchConfig } from "./Presets/SwitchConfig"; + +export function RenameMatchedFilesSetting() { + const { t } = useTranslation(); + + return ( + + ); +} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 5ccb1fc..7764c41 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -16,6 +16,8 @@ import { CheckForUpdatesSetting } from "./SettingItems/CheckForUpdatesSetting"; import { UpdateSetting } from "./SettingItems/UpdateSetting"; import { useEffect, useState } from "react"; import { useStorage } from "@/contexts/storage-provider"; +import { MatchByDestinationSetting } from "./SettingItems/MatchByDestinationSetting"; +import { RenameMatchedFilesSetting } from "./SettingItems/RenameMatchedFilesSetting"; export default function Settings() { const { t } = useTranslation(); @@ -47,6 +49,13 @@ export default function Settings() { > {t("settings.categories.application")} + setTab("icon_pack")} + className="px-12 py-2 w-full" + > + {t("settings.categories.icon_pack")} + setTab("system")} @@ -86,6 +95,12 @@ export default function Settings() { + + + + + + Edit your system settings here. diff --git a/frontend/src/wailsjs/go/models.ts b/frontend/src/wailsjs/go/models.ts index 3ac639c..e2065de 100644 --- a/frontend/src/wailsjs/go/models.ts +++ b/frontend/src/wailsjs/go/models.ts @@ -23,6 +23,8 @@ export namespace main { windowEffect?: number; checkForUpdates?: boolean; lastUpdateCheck?: number; + matchByDestination?: boolean; + renameMatchedFiles?: boolean; static createFrom(source: any = {}) { return new Config(source); @@ -52,6 +54,8 @@ export namespace main { this.windowEffect = source["windowEffect"]; this.checkForUpdates = source["checkForUpdates"]; this.lastUpdateCheck = source["lastUpdateCheck"]; + this.matchByDestination = source["matchByDestination"]; + this.renameMatchedFiles = source["renameMatchedFiles"]; } } export class FileInfo { From 147085cd089c05bf72f74db12c063d71efe5cecb Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 18 Jul 2024 18:35:48 +0300 Subject: [PATCH 124/221] add file matching --- iconpack.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/iconpack.go b/iconpack.go index 2845b30..797efa2 100644 --- a/iconpack.go +++ b/iconpack.go @@ -268,7 +268,7 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { } fileInfo.Destination = ConvertToGeneralPath(fileInfo.Destination) - if strings.ToLower(filepath.Ext(link.StringData.IconLocation)) == ".ico" { + if packId != "" && strings.ToLower(filepath.Ext(link.StringData.IconLocation)) == ".ico" { iconPath := filepath.Join(packsFolder, packId, "icons", fileInfo.Id+".png") err = ConvertToPng(link.StringData.IconLocation, iconPath) if err != nil { @@ -444,6 +444,9 @@ func (pack *IconPack) Apply() error { } } } + + match := file.MatchFile() + runtime.LogDebug(appContext, "Match: "+match) } // Copy settings.json to apply.json @@ -475,6 +478,55 @@ func (pack *IconPack) IsApplied() bool { return apply.CornerRadius == pack.Settings.CornerRadius && apply.Opacity == pack.Settings.Opacity } +func (fileInfo *FileInfo) MatchFile() string { + pathPattern := fileInfo.Path + path := ConvertToFullPath(pathPattern) + if path != "" { + if *config.RenameMatchedFiles && fileInfo.Name != filepath.Base(path) { + newPath := filepath.Join(filepath.Dir(path), fileInfo.Name+fileInfo.Extension) + os.Rename(path, newPath) + path = newPath + } + + return path + } + + if *config.MatchByDestination { + pathDir := ConvertToFullPath(filepath.Dir(pathPattern)) + + files, err := os.ReadDir(pathDir) + if err != nil { + runtime.LogError(appContext, err.Error()) + return "" + } + + for _, file := range files { + if filepath.Ext(file.Name()) == filepath.Ext(pathPattern) { + currentFilePath := filepath.Join(pathDir, file.Name()) + currentFileInfo, err := CreateFileInfo("", currentFilePath) + if err != nil { + runtime.LogError(appContext, err.Error()) + return "" + } + if ConvertToFullPath(currentFileInfo.Destination) == ConvertToFullPath(fileInfo.Destination) { + runtime.LogDebug(appContext, "Matched file: "+currentFilePath) + + if *config.RenameMatchedFiles && currentFileInfo.Name != fileInfo.Name { + newPath := filepath.Join(pathDir, fileInfo.Name+fileInfo.Extension) + os.Rename(currentFilePath, newPath) + currentFilePath = newPath + runtime.LogDebug(appContext, "Renamed file: "+currentFilePath) + } + + return currentFilePath + } + } + } + } + + return "" +} + func (a *App) Test() { runtime.LogDebug(a.ctx, fmt.Sprint(ConvertToFullPath("${DESKTOP}\\o*.lnk"))) runtime.LogDebug(a.ctx, "Desktop paths: "+fmt.Sprint(get_desktop_paths())) From 46df4cec8bb529c2c0200bb9388d6098d2222f41 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 18 Jul 2024 18:43:16 +0300 Subject: [PATCH 125/221] remove redundant use of full path --- iconpack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iconpack.go b/iconpack.go index 797efa2..ce3192f 100644 --- a/iconpack.go +++ b/iconpack.go @@ -508,7 +508,7 @@ func (fileInfo *FileInfo) MatchFile() string { runtime.LogError(appContext, err.Error()) return "" } - if ConvertToFullPath(currentFileInfo.Destination) == ConvertToFullPath(fileInfo.Destination) { + if currentFileInfo.Destination == fileInfo.Destination { runtime.LogDebug(appContext, "Matched file: "+currentFilePath) if *config.RenameMatchedFiles && currentFileInfo.Name != fileInfo.Name { From b1b7e03fcb254fbd25e6e1f1c5754ea22ba95c09 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 13 Aug 2024 17:13:08 +0300 Subject: [PATCH 126/221] parallelize icon generation --- iconpack.go | 56 +++++++++++++++++++++++++++++++++++------------------ image.go | 42 +++++++++++++--------------------------- main.go | 15 ++++++++++++-- 3 files changed, 63 insertions(+), 50 deletions(-) diff --git a/iconpack.go b/iconpack.go index ce3192f..1286626 100644 --- a/iconpack.go +++ b/iconpack.go @@ -8,6 +8,7 @@ import ( "path/filepath" "sort" "strings" + "sync" "github.com/google/uuid" "github.com/wailsapp/wails/v2/pkg/runtime" @@ -383,10 +384,18 @@ func (a *App) AddFilesToIconPackFromFolder(id string, path string, save bool) { return } + var wg sync.WaitGroup + for _, file := range files { - a.AddFileToIconPackFromPath(id, path+"\\"+file.Name(), false) + wg.Add(1) + go func(file os.DirEntry) { + defer wg.Done() + a.AddFileToIconPackFromPath(id, path+"\\"+file.Name(), false) + }(file) } + wg.Wait() + if save { WriteIconPack(iconPackCache[id]) } @@ -421,34 +430,43 @@ func (pack *IconPack) Apply() error { return err } + var wg sync.WaitGroup + for _, file := range pack.Files { - if file.HasIcon { - targetPath := filepath.Join(targetFolder, file.Id+".ico") + wg.Add(1) - targetPathExists := false - if _, err := os.Stat(targetPath); err == nil { - targetPathExists = true - } + go func(file FileInfo) { + defer wg.Done() + if file.HasIcon { + targetPath := filepath.Join(targetFolder, file.Id+".ico") - if !targetPathExists || !pack.IsApplied() { - iconPath := filepath.Join(packsFolder, pack.Metadata.Id, "icons", file.Id+".png") - if _, err := os.Stat(iconPath); err != nil { - runtime.LogError(appContext, err.Error()) - continue + targetPathExists := false + if _, err := os.Stat(targetPath); err == nil { + targetPathExists = true } - err = ConvertToIco(iconPath, targetPath, pack.Settings) - if err != nil { - runtime.LogError(appContext, err.Error()) - continue + if !targetPathExists || !pack.IsApplied() { + iconPath := filepath.Join(packsFolder, pack.Metadata.Id, "icons", file.Id+".png") + if _, err := os.Stat(iconPath); err != nil { + runtime.LogError(appContext, err.Error()) + return + } + err = ConvertToIco(iconPath, targetPath, pack.Settings) + + if err != nil { + runtime.LogError(appContext, err.Error()) + return + } } } - } - match := file.MatchFile() - runtime.LogDebug(appContext, "Match: "+match) + match := file.MatchFile() + runtime.LogDebug(appContext, "Match: "+match) + }(file) } + wg.Wait() + // Copy settings.json to apply.json settingsPath := filepath.Join(packsFolder, pack.Metadata.Id, "settings.json") applyPath := filepath.Join(packsFolder, pack.Metadata.Id, "apply.json") diff --git a/image.go b/image.go index 2a1c0a5..d425b95 100644 --- a/image.go +++ b/image.go @@ -1,11 +1,9 @@ package main import ( - "bytes" "fmt" "math" "os" - "os/exec" "path/filepath" "strconv" "strings" @@ -42,6 +40,7 @@ func GetMaskPath(radius, opacity int) (string, error) { opacityPercent := fmt.Sprintf("%.2f", float64(opacity)/100.0) maskArgs := []string{ + imageMagickPath, "-size", "256x256", "xc:none", "-draw", fmt.Sprintf("fill rgba(0,0,0,%s) roundrectangle 0,0,255,255,%d,%d", opacityPercent, roundedRadius, roundedRadius), @@ -49,13 +48,9 @@ func GetMaskPath(radius, opacity int) (string, error) { } // Execute ImageMagick command to create mask - cmd = exec.Command(imageMagickPath, maskArgs...) - var maskStderr bytes.Buffer - cmd.Stderr = &maskStderr - - err := cmd.Run() + _, err := sendCommand(maskArgs...) if err != nil { - return "", fmt.Errorf("error creating mask: %w\n%s", err, maskStderr.String()) + return "", fmt.Errorf("error creating mask: %ws", err) } return maskPath, nil @@ -78,6 +73,7 @@ func ConvertToIco(path string, destination string, settings IconPackSettings) er // Build ImageMagick command arguments to apply the mask and convert to ICO args := []string{ + imageMagickPath, path, maskPath, "-compose", "DstIn", @@ -87,13 +83,9 @@ func ConvertToIco(path string, destination string, settings IconPackSettings) er } // Execute ImageMagick command silently - cmd = exec.Command(imageMagickPath, args...) - var stderr bytes.Buffer - cmd.Stderr = &stderr - - err = cmd.Run() + _, err = sendCommand(args...) if err != nil { - return fmt.Errorf("error converting image: %w\n%s", err, stderr.String()) + return fmt.Errorf("error converting image: %w", err) } return nil @@ -115,16 +107,12 @@ func ConvertToPng(path string, destination string) error { tempIconPath := filepath.Join(tempIconFolder, "icon.png") // Build magick command arguments - args := []string{path, "-alpha", "on", "-background", "none", tempIconPath} + args := []string{imageMagickPath, path, "-alpha", "on", "-background", "none", tempIconPath} // Execute magick command silently - cmd = exec.Command(imageMagickPath, args...) - var stderr bytes.Buffer - cmd.Stderr = &stderr - - err = cmd.Run() + _, err = sendCommand(args...) if err != nil { - return fmt.Errorf("error converting image: %w\n%s", err, stderr.String()) + return fmt.Errorf("error converting image: %w", err) } // Delete the icons other than the largest one @@ -164,19 +152,15 @@ func GetImageWidth(path string) (int, error) { } // Build magick command arguments - args := []string{"identify", "-ping", "-format", "%w", path} + args := []string{imageMagickPath, "identify", "-ping", "-format", "%w", path} // Execute magick command silently - cmd = exec.Command(imageMagickPath, args...) - var stderr bytes.Buffer - cmd.Stderr = &stderr - - output, err := cmd.Output() + output, err := sendCommand(args...) if err != nil { - return -1, fmt.Errorf("error getting image width: %w\n%s", err, stderr.String()) + return -1, fmt.Errorf("error getting image width: %w\n%s", err, output) } - width, err := strconv.Atoi(strings.TrimSpace(string(output))) + width, err := strconv.Atoi(strings.TrimSpace(output)) if err != nil { return -1, err diff --git a/main.go b/main.go index f245356..3ed1703 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "path" "path/filepath" "strings" + "syscall" "time" "github.com/wailsapp/wails/v2" @@ -19,8 +20,6 @@ import ( "github.com/wailsapp/wails/v2/pkg/options/windows" ) -var cmd *exec.Cmd - //go:embed all:frontend/dist var assets embed.FS @@ -157,3 +156,15 @@ func main() { log.Fatal(err) } } + +func sendCommand(command ...string) (string, error) { + // Use `exec.Command` to send a command to the existing cmd window + cmd := exec.Command("cmd.exe", append([]string{"/C"}, command...)...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + HideWindow: true, + } + + output, err := cmd.Output() + + return string(output), err +} From a87873bba663f8be36594a746066753c8757495e Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 13 Aug 2024 17:53:01 +0300 Subject: [PATCH 127/221] change icon id each generation --- frontend/src/wailsjs/go/models.ts | 2 ++ iconpack.go | 28 ++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/frontend/src/wailsjs/go/models.ts b/frontend/src/wailsjs/go/models.ts index e2065de..0e50ab5 100644 --- a/frontend/src/wailsjs/go/models.ts +++ b/frontend/src/wailsjs/go/models.ts @@ -66,6 +66,7 @@ export namespace main { destinationPath: string; extension: string; hasIcon: boolean; + iconId: string; static createFrom(source: any = {}) { return new FileInfo(source); @@ -80,6 +81,7 @@ export namespace main { this.destinationPath = source["destinationPath"]; this.extension = source["extension"]; this.hasIcon = source["hasIcon"]; + this.iconId = source["iconId"]; } } export class IconPackSettings { diff --git a/iconpack.go b/iconpack.go index 1286626..12e2e55 100644 --- a/iconpack.go +++ b/iconpack.go @@ -24,6 +24,7 @@ type FileInfo struct { Destination string `json:"destinationPath"` Extension string `json:"extension"` HasIcon bool `json:"hasIcon"` + IconId string `json:"iconId"` } type Metadata struct { @@ -418,7 +419,11 @@ func (a *App) ApplyIconPack(id string) { err = pack.Apply() - if err != nil { + // Save icon pack + if err == nil { + runtime.LogInfo(appContext, fmt.Sprintf("Applied icon pack %s, attempting to save", pack.Metadata.Id)) + WriteIconPack(pack) + } else { runtime.LogError(appContext, err.Error()) } } @@ -432,20 +437,35 @@ func (pack *IconPack) Apply() error { var wg sync.WaitGroup - for _, file := range pack.Files { + for i := range pack.Files { wg.Add(1) - go func(file FileInfo) { + // Use a pointer to the file in the slice + file := &pack.Files[i] + + go func(file *FileInfo) { defer wg.Done() if file.HasIcon { - targetPath := filepath.Join(targetFolder, file.Id+".ico") + targetPath := filepath.Join(targetFolder, file.IconId+".ico") targetPathExists := false if _, err := os.Stat(targetPath); err == nil { targetPathExists = true } + // Regenerate if !targetPathExists || !pack.IsApplied() { + if targetPathExists { + err = os.Remove(targetPath) + if err != nil { + runtime.LogWarningf(appContext, "Failed to remove old icon %s: %s", targetPath, err.Error()) + } + } + + // Update the IconId directly on the file pointer + file.IconId = uuid.NewString() + targetPath = filepath.Join(targetFolder, file.IconId+".ico") + iconPath := filepath.Join(packsFolder, pack.Metadata.Id, "icons", file.Id+".png") if _, err := os.Stat(iconPath); err != nil { runtime.LogError(appContext, err.Error()) From b01896518d961e93a3c4cc59270a8eef897febae Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 13 Aug 2024 18:44:12 +0300 Subject: [PATCH 128/221] change lnk icons on apply --- app_paths.go | 65 ++++++++++++++++++++++++++ frontend/public/scripts/setlnkicon.vbs | 18 +++++++ frontend/src/components/Packs.tsx | 2 - frontend/src/wailsjs/go/main/App.d.ts | 4 -- frontend/src/wailsjs/go/main/App.js | 8 ---- iconpack.go | 59 ++++++++++++++++++----- 6 files changed, 131 insertions(+), 25 deletions(-) create mode 100644 frontend/public/scripts/setlnkicon.vbs diff --git a/app_paths.go b/app_paths.go index 07ee440..afd83f9 100644 --- a/app_paths.go +++ b/app_paths.go @@ -1,7 +1,9 @@ package main import ( + "embed" "errors" + "io/fs" "os" "os/user" "path" @@ -10,12 +12,18 @@ import ( "github.com/wailsapp/wails/v2/pkg/runtime" ) +//go:embed frontend/public/scripts +var scriptsFolderEmbedded embed.FS + +var setLnkIconScriptPath string + var packsFolder string var logsFolder string var savedConfigFolder string var activeIconFolder string var tempFolder string var maskFolder string +var scriptsFolder string var configPath string var appIconPath string @@ -40,9 +48,11 @@ func path_init() error { activeIconFolder = path.Join(appFolder, "icons") tempFolder = path.Join(appFolder, "temp") maskFolder = path.Join(appFolder, "masks") + scriptsFolder = path.Join(appFolder, "scripts") configPath = path.Join(appFolder, "config.json") appIconPath = path.Join(appFolder, "appicon.png") + setLnkIconScriptPath = path.Join(scriptsFolder, "setlnkicon.vbs") runtime.LogTrace(appContext, "Attempting to create folders") err = create_folder(appFolder) @@ -74,6 +84,10 @@ func path_init() error { if err != nil { return err } + err = create_folder(scriptsFolder) + if err != nil { + return err + } runtime.LogTrace(appContext, "Creating folders complete") @@ -99,6 +113,57 @@ func path_init() error { imageMagickPath = filepath.Join(installationDirectory, "ImageMagick-7.1.1-35-portable-Q16-x64", "magick.exe") runtime.LogDebugf(appContext, "ImageMagick path: %s", imageMagickPath) + // Copy all files in scriptsFolderEmbedded to scriptsFolder + runtime.LogTrace(appContext, "Attempting to copy scripts from embedded folder to scripts folder") + + err = fs.WalkDir(scriptsFolderEmbedded, ".", func(filePath string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + // Skip directories, but ensure they exist in the target location + if d.IsDir() { + return nil + } + + // Remove the prefix "frontend/public/scripts" from the file path + relativePath, err := filepath.Rel("frontend/public/scripts", filePath) + if err != nil { + return err + } + + // Construct the full destination path + destFile := filepath.Join(scriptsFolder, relativePath) + + // Check if destination file already exists + if _, err := os.Stat(destFile); err == nil { + return nil + } + + // Create directories if they do not exist + if err := os.MkdirAll(filepath.Dir(destFile), 0o755); err != nil { + return err + } + + // Copy the file contents to the target destination + contents, err := scriptsFolderEmbedded.ReadFile(filePath) + if err != nil { + return err + } + err = os.WriteFile(destFile, contents, 0o755) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + return err + } + + runtime.LogTrace(appContext, "Copying scripts complete") + runtime.LogTrace(appContext, "Path initialization complete") return nil diff --git a/frontend/public/scripts/setlnkicon.vbs b/frontend/public/scripts/setlnkicon.vbs new file mode 100644 index 0000000..b5cf8a9 --- /dev/null +++ b/frontend/public/scripts/setlnkicon.vbs @@ -0,0 +1,18 @@ +Dim Arg, shortcutPath, shortcutName, iconPath, iconIndex +Set Arg = WScript.Arguments + +shortcutPath = Arg(0) +shortcutName = Arg(1) +iconPath = Arg(2) +iconIndex = Arg(3) + +Set objShell = CreateObject("Shell.Application") +Set objFolder = objShell.NameSpace(shortcutPath) + +Set objFolderItem = objFolder.ParseName(shortcutName) +Set objShortcut = objFolderItem.GetLink + +objShortcut.SetIconLocation iconPath, iconIndex +objShortcut.Save + +set Arg = Nothing \ No newline at end of file diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index ff34c09..3fc8206 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -39,7 +39,6 @@ import { GetIconPack, GetIconPackInfo, SetIconPackInfo, - Test, } from "@/wailsjs/go/main/App"; import { main } from "@/wailsjs/go/models"; import { @@ -87,7 +86,6 @@ export default function Packs() { return ( - {packInfos?.map((pack) => ( ; export function SetIconPackInfo(arg1:main.IconPack):Promise; -export function Test():Promise; - -export function Test2():Promise; - export function Update(arg1:string):Promise; export function UpdateAsAdmin(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 18df8cc..3219467 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -106,14 +106,6 @@ export function SetIconPackInfo(arg1) { return window['go']['main']['App']['SetIconPackInfo'](arg1); } -export function Test() { - return window['go']['main']['App']['Test'](); -} - -export function Test2() { - return window['go']['main']['App']['Test2'](); -} - export function Update(arg1) { return window['go']['main']['App']['Update'](arg1); } diff --git a/iconpack.go b/iconpack.go index 12e2e55..6c8bd31 100644 --- a/iconpack.go +++ b/iconpack.go @@ -306,6 +306,20 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { return fileInfo, nil } +func GetAppliedIcon(path string) (string, error) { + link, err := lnk.File(path) + + if err != nil { + return "", fmt.Errorf("failed to open .lnk file: %s", err.Error()) + } + + if link.StringData.IconLocation != "" { + return link.StringData.IconLocation, nil + } + + return "", errors.New("icon not found") +} + func (a *App) AddIconPack(name string, version string, author string, icon string) error { iconPack, err := CreateIconPack(name, version, author, icon) @@ -445,7 +459,11 @@ func (pack *IconPack) Apply() error { go func(file *FileInfo) { defer wg.Done() - if file.HasIcon { + + match := file.MatchFile() + runtime.LogDebug(appContext, "Match: "+match) + + if file.HasIcon && match != "" { targetPath := filepath.Join(targetFolder, file.IconId+".ico") targetPathExists := false @@ -478,10 +496,24 @@ func (pack *IconPack) Apply() error { return } } - } - match := file.MatchFile() - runtime.LogDebug(appContext, "Match: "+match) + appliedIconPath, err := GetAppliedIcon(match) + if err != nil { + runtime.LogWarningf(appContext, "Failed to get applied icon for %s: %s", match, err.Error()) + } + runtime.LogDebug(appContext, "Applied icon path: "+appliedIconPath) + + if appliedIconPath == targetPath { + runtime.LogDebug(appContext, "Icon already applied") + } else { + runtime.LogDebug(appContext, "Applying icon") + + err = SetIcon(match, targetPath) + if err != nil { + runtime.LogWarningf(appContext, "Failed to set icon for %s: %s", match, err.Error()) + } + } + } }(file) } @@ -565,13 +597,18 @@ func (fileInfo *FileInfo) MatchFile() string { return "" } -func (a *App) Test() { - runtime.LogDebug(a.ctx, fmt.Sprint(ConvertToFullPath("${DESKTOP}\\o*.lnk"))) - runtime.LogDebug(a.ctx, "Desktop paths: "+fmt.Sprint(get_desktop_paths())) -} +func SetIcon(path string, iconPath string) error { + switch filepath.Ext(path) { + case ".lnk": + _, err := sendCommand("cscript.exe", setLnkIconScriptPath, filepath.Dir(path), filepath.Base(path), iconPath, "0") -func (a *App) Test2() { - iconPacks, _ = GetIconPackInfo() + if err != nil { + return err + } + + runtime.LogDebug(appContext, "Applied icon: "+iconPath) + return nil + } - fmt.Println(iconPacks) + return errors.New("unsupported file type") } From c1cb263af6b4077e28ea63ffc3a22f2599da4e67 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 13 Aug 2024 20:01:58 +0300 Subject: [PATCH 129/221] add checkbox for deleting the generated icons --- frontend/package.json | 1 + frontend/src/components/Packs.tsx | 23 ++++++++++++++++++-- frontend/src/components/ui/checkbox.tsx | 28 +++++++++++++++++++++++++ frontend/src/wailsjs/go/main/App.d.ts | 2 +- frontend/src/wailsjs/go/main/App.js | 4 ++-- frontend/yarn.lock | 14 +++++++++++++ iconpack.go | 13 ++++++++++-- 7 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/ui/checkbox.tsx diff --git a/frontend/package.json b/frontend/package.json index bacc81f..1ec3ac9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.9.0", + "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 3fc8206..efdbc34 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -58,6 +58,7 @@ import { import Image from "./Image"; import { Slider } from "./ui/my-slider"; import { Skeleton } from "./ui/skeleton"; +import { Checkbox } from "./ui/checkbox"; export default function Packs() { const [pack, setPack] = useState(""); @@ -201,6 +202,7 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { main.IconPack.createFrom({}) ); const dialogRef = useRef(null); + const [deleteGeneratedIcons, setDeleteGeneratedIcons] = useState(false); const [cornerRadius, setCornerRadius] = useState(-1); const [opacity, setOpacity] = useState(-1); @@ -259,7 +261,7 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { }; const handleDelete = () => { - DeleteIconPack(iconPackId).then(() => { + DeleteIconPack(iconPackId, deleteGeneratedIcons).then(() => { setPack(""); loadPackInfo(); }); @@ -335,6 +337,7 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { const openDialog = useCallback(() => { if (dialogRef.current) { + setDeleteGeneratedIcons(false); dialogRef.current.openDialog(); } }, []); @@ -410,7 +413,23 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { title="Are you sure you want to delete this pack?" description="This action can not be undone." onCancel={handleDelete} - /> + > +
+ + setDeleteGeneratedIcons(!deleteGeneratedIcons) + } + /> + +
+ ) : ( <> diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..ddbdd01 --- /dev/null +++ b/frontend/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index 184ec7e..4a7764f 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -16,7 +16,7 @@ export function ApplyIconPack(arg1:string):Promise; export function CheckForUpdate():Promise; -export function DeleteIconPack(arg1:string):Promise; +export function DeleteIconPack(arg1:string,arg2:boolean):Promise; export function GetBase64Png():Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 3219467..c884471 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -30,8 +30,8 @@ export function CheckForUpdate() { return window['go']['main']['App']['CheckForUpdate'](); } -export function DeleteIconPack(arg1) { - return window['go']['main']['App']['DeleteIconPack'](arg1); +export function DeleteIconPack(arg1, arg2) { + return window['go']['main']['App']['DeleteIconPack'](arg1, arg2); } export function GetBase64Png() { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index cf37065..b44856f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -516,6 +516,20 @@ dependencies: "@radix-ui/react-primitive" "2.0.0" +"@radix-ui/react-checkbox@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz#a559c4303957d797acee99914480b755aa1f27d6" + integrity sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-previous" "1.1.0" + "@radix-ui/react-use-size" "1.1.0" + "@radix-ui/react-collection@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" diff --git a/iconpack.go b/iconpack.go index 6c8bd31..9e8eac1 100644 --- a/iconpack.go +++ b/iconpack.go @@ -332,17 +332,26 @@ func (a *App) AddIconPack(name string, version string, author string, icon strin return WriteIconPack(iconPack) } -func (a *App) DeleteIconPack(id string) error { +func (a *App) DeleteIconPack(id string, deleteGeneratedIcons bool) error { iconPackPath := path.Join(packsFolder, id) if _, err := os.Stat(iconPackPath); os.IsNotExist(err) { return err } - if err := os.RemoveAll(iconPackPath); err != nil { return err } + if deleteGeneratedIcons { + activeIconsPath := path.Join(activeIconFolder, id) + if _, err := os.Stat(activeIconsPath); os.IsNotExist(err) { + return err + } + if err := os.RemoveAll(activeIconsPath); err != nil { + return err + } + } + for i, pack := range iconPacks { if pack.Metadata.Id == id { iconPacks = append(iconPacks[:i], iconPacks[i+1:]...) From 60eccb0cb67dfead4c44531d48917aca76291ff3 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 13 Aug 2024 21:16:27 +0300 Subject: [PATCH 130/221] add missing translations --- frontend/public/locales/en-US.json | 98 ++++++++++++++++++++ frontend/public/locales/tr-TR.json | 98 ++++++++++++++++++++ frontend/src/components/Packs.tsx | 143 ++++++++++++++++++++--------- 3 files changed, 298 insertions(+), 41 deletions(-) diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index bc21e88..a4f1823 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -1,4 +1,5 @@ { + "save": "Save", "cancel": "Cancel", "yes": "Yes", "no": "No", @@ -7,6 +8,8 @@ "export": "Export", "error": "Error", "show": "Show", + "create": "Create", + "delete": "Delete", "nav": { "my_packs": "My Packs", @@ -14,6 +17,91 @@ "settings": "Settings" }, + "my_packs": { + "create_new_pack": { + "label": "Create New Pack", + "title": "Create Icon Pack" + }, + + "delete_pack": { + "confirmation_title": "Are you sure you want to delete this pack?", + "confirmation_description": "This action cannot be undone.", + "delete_generated_icons": "Delete Generated Icons" + }, + + "card": { + "pack_information": { + "label": "Pack Information", + + "information": { + "name": { + "label": "Name", + "placeholder": "My Pack", + + "message": { + "name_required": "Name is required", + "name_max": "Name can not exceed 32 characters" + } + }, + + "version": { + "label": "Version", + "placeholder": "v1.0.0", + + "message": { + "version_required": "Version is required", + "version_format": "Version must be in the format v1.0.0" + } + }, + + "author": { + "label": "Author", + "placeholder": "Your name", + + "message": { + "author_max": "Author can not exceed 32 characters" + } + }, + + "icon": { + "label": "Icon" + } + } + }, + + "pack_actions": { + "label": "Pack Actions", + "apply_icon_pack": "Apply Icon Pack", + "add_icons_from_desktop": "Add Icons from Desktop", + "add_icons": "Add Icons" + }, + + "pack_settings": { + "label": "Pack Settings", + + "setting": { + "enabled": { + "label": "Enabled" + }, + + "corner_radius": { + "label": "Corner Radius", + "description": "Change the corner radius of the icons in this pack." + }, + + "opacity": { + "label": "Opacity", + "description": "Change the opacity of the icons in this pack." + } + } + }, + + "icons": { + "label": "Icons" + } + } + }, + "settings": { "restart_the_app_for_changes_to_take_effect": "Restart the app for changes to take effect.", "are_you_sure_you_want_to_import_this_config": "Are you sure you want to import this config?", @@ -95,6 +183,16 @@ "description": "Save window size, position, and state." }, + "match_by_destination": { + "label": "Match Shortuts by Destination", + "description": "Match shortcuts by destination if matching by path is not successful." + }, + + "rename_matched_files": { + "label": "Rename Matched Files", + "description": "Rename matched files if the names does not match." + }, + "check_for_updates": { "label": "Check For Updates On Startup", "description": "Check for updates on startup." diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index 9750b09..df111d6 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -1,4 +1,5 @@ { + "save": "Kaydet", "cancel": "İptal", "yes": "Evet", "no": "Hayır", @@ -7,6 +8,8 @@ "export": "Dışa Aktar", "error": "Hata", "show": "Göster", + "create": "Oluştur", + "delete": "Sil", "nav": { "my_packs": "Paketlerim", @@ -14,6 +17,91 @@ "settings": "Ayarlar" }, + "my_packs": { + "create_new_pack": { + "label": "Yeni Paket Oluştur", + "title": "İkon Paketi Oluştur" + }, + + "delete_pack": { + "confirmation_title": "Bu paketi silmek istediğinizden emin misiniz?", + "confirmation_description": "Bu işlem geri alınamaz.", + "delete_generated_icons": "Oluşturulan ikonları sil" + }, + + "card": { + "pack_information": { + "label": "Paket Bilgileri", + + "information": { + "name": { + "label": "Ad", + "placeholder": "Benim Paketim", + + "message": { + "name_required": "Ad gereklidir", + "name_max": "Ad, 32 karakteri geçemez" + } + }, + + "version": { + "label": "Versiyon", + "placeholder": "v1.0.0", + + "message": { + "version_required": "Versiyon gereklidir", + "version_format": "Versiyon, \"v1.0.0\" formatında olmalıdır" + } + }, + + "author": { + "label": "Yazar", + "placeholder": "Adınız", + + "message": { + "author_max": "Yazar, 32 karakteri geçemez" + } + }, + + "icon": { + "label": "İkon" + } + } + }, + + "pack_actions": { + "label": "Paket İşlemleri", + "apply_icon_pack": "İkon Paketini Uygula", + "add_icons_from_desktop": "Masaüstü İkonlarını Ekle", + "add_icons": "İkon Ekle" + }, + + "pack_settings": { + "label": "Paket Ayarları", + + "setting": { + "enabled": { + "label": "Etkin" + }, + + "corner_radius": { + "label": "Köşe Yarıçapı", + "description": "Bu paketteki simgelerin köşe yarıçapını değiştirin." + }, + + "opacity": { + "label": "Opaklık", + "description": "Bu paketteki simgelerin opaklığını değiştirin." + } + } + }, + + "icons": { + "label": "İkonlar" + } + } + }, + "settings": { "restart_the_app_for_changes_to_take_effect": "Değişikliklerin etkili olması için uygulamayı yeniden başlatın.", "are_you_sure_you_want_to_import_this_config": "Bu ayarları yüklemek istediğinize emin misiniz?", @@ -95,6 +183,16 @@ "description": "Pencere boyutunu, konumunu ve durumunu kaydet." }, + "match_by_destination": { + "label": "Kısayolları Hedeflerine Göre Eşleştir", + "description": "Dosya yolu ile eşleştirme başarısız olursa, kısayolları hedeflerine göre eşleştir." + }, + + "rename_matched_files": { + "label": "Eşleşen Dosyaları Yeniden Adlandır", + "description": "İsimler eşleşmiyorsa, eşleşen dosyaları yeniden adlandır." + }, + "check_for_updates": { "label": "Başlangıçta Güncellemeleri Kontrol Et", "description": "Başlangıçta güncellemeleri kontrol et." diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index efdbc34..ceb9fee 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -59,8 +59,11 @@ import Image from "./Image"; import { Slider } from "./ui/my-slider"; import { Skeleton } from "./ui/skeleton"; import { Checkbox } from "./ui/checkbox"; +import { useTranslation } from "react-i18next"; export default function Packs() { + const { t } = useTranslation(); + const [pack, setPack] = useState(""); const [packInfos, setPackInfos] = useState(); const [selectedPackKeyCount, setSelectedPackKeyCount] = useState(0); @@ -99,13 +102,13 @@ export default function Packs() { - Create Icon Pack + {t("my_packs.create_new_pack.title")}
- Pack Information + {t("my_packs.card.pack_information.label")}
@@ -379,7 +384,11 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { {fields.map((field) => (
- {field.charAt(0).toUpperCase() + field.slice(1)} + {t( + "my_packs.card.pack_information.information." + + field + + ".label" + )}
{editMode ? (
@@ -426,7 +437,7 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { htmlFor="deleteGeneratedIcons" className="font-medium text-sm leading-none select-none" > - Delete generated icons + {t("my_packs.delete_pack.delete_generated_icons")}
@@ -434,14 +445,14 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { ) : ( <> )} @@ -451,7 +462,7 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) {
- Pack Actions + {t("my_packs.card.pack_actions.label")}
@@ -477,7 +488,7 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { ) : ( )} - Add Icons From Desktop + {t("my_packs.card.pack_actions.add_icons_from_desktop")}
- Pack Settings + {t("my_packs.card.pack_settings.label")}
- Enabled + + {t("my_packs.card.pack_settings.setting.enabled.label")} +
- Corner Radius + + {t("my_packs.card.pack_settings.setting.corner_radius.label")} + - Change the corner radius of the icons in this pack. + {t( + "my_packs.card.pack_settings.setting.corner_radius.description" + )}
@@ -542,9 +559,11 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) {
- Opacity + + {t("my_packs.card.pack_settings.setting.opacity.label")} + - Change the opacity of the icons in this pack. + {t("my_packs.card.pack_settings.setting.opacity.description")}
@@ -567,7 +586,9 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) {
-
Icons
+
+ {t("my_packs.card.icons.label")} +
{iconPackInfo.files?.map((file) => file.hasIcon ? ( @@ -598,23 +619,39 @@ interface CreatePackFormProps { } function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { + const { t } = useTranslation(); + const formSchema = z.object({ icon: z.string(), name: z .string() - .min(1, { message: "Name is required" }) - .max(32, { message: "Name can not exceed 32 characters" }) - // Check for windows file name compatibility - .regex(/^(?!\.)/, { message: "Name can not start with a period" }) - .regex(/^(?!.*\.$)/, { - message: "Name can not end with a period", + .min(1, { + message: t( + "my_packs.card.pack_information.information.name.message.name_required" + ), + }) + .max(32, { + message: t( + "my_packs.card.pack_information.information.name.message.name_max" + ), }), - version: z.string().regex(/^v[0-9]{1,4}\.[0-9]{1,4}\.[0-9]{1,4}$/, { - message: "Version must be in the format: v1.0.0", - }), - author: z + version: z .string() - .max(32, { message: "Author can not exceed 32 characters" }), + .min(1, { + message: t( + "my_packs.card.pack_information.information.version.message.version_required" + ), + }) + .regex(/^v[0-9]{1,4}\.[0-9]{1,4}\.[0-9]{1,4}$/, { + message: t( + "my_packs.card.pack_information.information.version.message.version_format" + ), + }), + author: z.string().max(32, { + message: t( + "my_packs.card.pack_information.information.author.message.author_max" + ), + }), }); const form = useForm>({ @@ -646,7 +683,9 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { name="icon" render={({ field }) => ( - Icon + + {t("my_packs.card.pack_information.information.icon.label")} + ( - Name + + {" "} + {t("my_packs.card.pack_information.information.name.label")} + - + @@ -675,9 +722,16 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { name="version" render={({ field }) => ( - Version + + {t("my_packs.card.pack_information.information.version.label")} + - + @@ -688,16 +742,23 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { name="author" render={({ field }) => ( - Author + + {t("my_packs.card.pack_information.information.author.label")} + - + )} /> From b77e8be6362d1ef275e75fd93cb745ae1c47657a Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sat, 17 Aug 2024 14:15:51 +0300 Subject: [PATCH 131/221] add edit screen --- frontend/package.json | 1 + frontend/public/locales/en-US.json | 1 + frontend/public/locales/tr-TR.json | 1 + frontend/src/App.tsx | 8 +- frontend/src/components/Image.tsx | 7 +- frontend/src/components/Packs.tsx | 245 ++++++++++++++++++++--- frontend/src/components/ui/accordion.tsx | 56 ++++++ frontend/yarn.lock | 29 +++ 8 files changed, 312 insertions(+), 36 deletions(-) create mode 100644 frontend/src/components/ui/accordion.tsx diff --git a/frontend/package.json b/frontend/package.json index 1ec3ac9..e4cfe87 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.9.0", + "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-label": "^2.1.0", diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index a4f1823..9ee5f82 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -72,6 +72,7 @@ "pack_actions": { "label": "Pack Actions", "apply_icon_pack": "Apply Icon Pack", + "edit_icon_pack": "Edit Icon Pack", "add_icons_from_desktop": "Add Icons from Desktop", "add_icons": "Add Icons" }, diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index df111d6..2f578e2 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -72,6 +72,7 @@ "pack_actions": { "label": "Paket İşlemleri", "apply_icon_pack": "İkon Paketini Uygula", + "edit_icon_pack": "İkon Paketini Düzenle", "add_icons_from_desktop": "Masaüstü İkonlarını Ekle", "add_icons": "İkon Ekle" }, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9d53323..d5157f4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,7 +16,7 @@ import Packs from "./components/Packs"; function App() { const { config, initialConfig } = useConfig(); const { t } = useTranslation(); - const { setValue } = useStorage(); + const { setValue, getValue } = useStorage(); const [tab, setTab] = useState("packs"); useLayoutEffect(() => { @@ -101,13 +101,13 @@ function App() {
- setTab("packs")}> + setTab("packs")} disabled={getValue("editingIconPack")}> {t("nav.my_packs")} - setTab("edit")}> + setTab("edit")} disabled={getValue("editingIconPack")}> {t("nav.edit")} - setTab("settings")}> + setTab("settings")} disabled={getValue("editingIconPack")}> {t("nav.settings")}
diff --git a/frontend/src/components/Image.tsx b/frontend/src/components/Image.tsx index 33c2efe..d0df840 100644 --- a/frontend/src/components/Image.tsx +++ b/frontend/src/components/Image.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import { Skeleton } from "./ui/skeleton"; +import { CircleHelp } from "lucide-react"; // Define the props type for the Image component interface ImageProps { @@ -7,6 +8,7 @@ interface ImageProps { className?: string; cornerRadius?: number; opacity?: number; + unkown?: boolean; } const Image: React.FC = ({ @@ -14,13 +16,16 @@ const Image: React.FC = ({ className, cornerRadius = 0, opacity = 100, + unkown = false, ...rest }) => { const [loading, setLoading] = useState(true); return ( <> - + {unkown ? : + + } { + setValue("editingIconPack", editingIconPack); + }); const [pack, setPack] = useState(""); const [packInfos, setPackInfos] = useState(); @@ -89,41 +102,61 @@ export default function Packs() { return ( - + {packInfos?.map((pack) => ( ))} - - - - - - - - {t("my_packs.create_new_pack.title")} - - - - + {!editingIconPack && ( + + + + + + + + + {" "} + {t("my_packs.create_new_pack.title")} + + + + + + )} - {pack && ( + {pack && !editingIconPack && ( + )} + + {pack && editingIconPack && ( + )} @@ -134,12 +167,16 @@ interface PackTriggerProps { iconPack: main.IconPack; setPack: (pack: string) => void; reloadSelectedPack: () => void; + editingIconPack: boolean; + disabled?: boolean; } function PackTrigger({ iconPack, setPack, reloadSelectedPack, + editingIconPack, + disabled, ...props }: PackTriggerProps) { const [enabledState, setEnabledState] = useState(iconPack.settings.enabled); @@ -161,6 +198,7 @@ function PackTrigger({ value={iconPack.metadata.id} onClick={() => setPack(iconPack.metadata.id)} className="flex justify-between p-4 w-full" + disabled={disabled} {...props} >
@@ -173,18 +211,22 @@ function PackTrigger({ ) : ( )} -
-
- {iconPack.metadata.name} + {!editingIconPack && ( +
+
+ {iconPack.metadata.name} +
+
{iconPack.metadata.version}
-
{iconPack.metadata.version}
-
+ )}
- e.stopPropagation()} - /> + {!editingIconPack && ( + e.stopPropagation()} + /> + )} ); } @@ -193,9 +235,15 @@ interface PackContentProps { iconPackId: string; setPack: (pack: string) => void; loadPackInfo: () => void; + setEditingIconPack: (editingIconPack: boolean) => void; } -function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { +function PackContent({ + iconPackId, + setPack, + loadPackInfo, + setEditingIconPack, +}: PackContentProps) { const { t } = useTranslation(); const [loading, setLoading] = useState(true); @@ -299,6 +347,10 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { "author", ]; + const handleEditIconPack = () => { + setEditingIconPack(true); + }; + const handleApplyIconPack = () => { setApplyRunning(true); ApplyIconPack(iconPackInfo.metadata.id).finally(() => { @@ -474,6 +526,16 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { {applyRunning && } {t("my_packs.card.pack_actions.apply_icon_pack")} + +
@@ -613,6 +675,127 @@ function PackContent({ iconPackId, setPack, loadPackInfo }: PackContentProps) { ); } +function PackEdit({ + iconPackId, + setPack, + loadPackInfo, + setEditingIconPack, +}: PackContentProps) { + const { t } = useTranslation(); + + const [loading, setLoading] = useState(true); + const [iconPackInfo, setIconPackInfo] = useState( + main.IconPack.createFrom({}) + ); + + const [addIconsFromDesktopRunning, setAddIconsFromDesktopRunning] = + useState(false); + const [addIconsRunning, setAddIconsRunning] = useState(false); + const running = addIconsFromDesktopRunning || addIconsRunning; + + useEffect(() => { + GetIconPack(iconPackId).then((iconPack) => { + setIconPackInfo(iconPack); + setLoading(false); + }); + }, []); + + const handleAddIconsFromDesktop = () => { + setAddIconsFromDesktopRunning(true); + + setTimeout(() => { + setAddIconsFromDesktopRunning(false); + }, 2000); + }; + + const handleAddIcon = () => { + setAddIconsRunning(true); + + setTimeout(() => { + setAddIconsRunning(false); + }, 2000); + }; + + if (loading) { + return ( +
+ + + + +
+ ); + } + + return ( +
+
+
+ + +
+
+ + +
+
+
+ + {iconPackInfo.files.map((file) => ( + + +
+ + {file.name} +
+
+ + {String(setPack) + + String(loadPackInfo) + + String(setEditingIconPack)} + +
+ ))} +
+
+
+ ); +} + interface CreatePackFormProps { loadPackInfo: () => void; dialogCloseRef: React.RefObject; diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx new file mode 100644 index 0000000..b897488 --- /dev/null +++ b/frontend/src/components/ui/accordion.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + #chevron]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b44856f..d65533d 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -509,6 +509,21 @@ resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA== +"@radix-ui/react-accordion@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-1.2.0.tgz#aed0770fcb16285db992d81873ccd7a014c7f17d" + integrity sha512-HJOzSX8dQqtsp/3jVxCU3CXEONF7/2jlGAB28oX8TTw1Dz8JYbEI1UcL8355PuLBE41/IRRMvCw7VkiK/jcUOQ== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collapsible" "1.1.0" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-arrow@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz#744f388182d360b86285217e43b6c63633f39e7a" @@ -530,6 +545,20 @@ "@radix-ui/react-use-previous" "1.1.0" "@radix-ui/react-use-size" "1.1.0" +"@radix-ui/react-collapsible@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.0.tgz#4d49ddcc7b7d38f6c82f1fd29674f6fab5353e77" + integrity sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-collection@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" From f5bab3d0b1fcf2507fa7549554972d1f75a69d76 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 18 Aug 2024 16:13:53 +0300 Subject: [PATCH 132/221] simplify - refactor --- app_paths.go | 44 ++- dialog.go | 45 +++ frontend/src/components/Packs.tsx | 401 +++++++++++++----------- frontend/src/components/SelectImage.tsx | 78 +++-- frontend/src/wailsjs/go/main/App.d.ts | 16 +- frontend/src/wailsjs/go/main/App.js | 32 +- frontend/src/wailsjs/go/models.ts | 4 +- iconpack.go | 305 +++++++++++------- utils.go | 5 + 9 files changed, 585 insertions(+), 345 deletions(-) diff --git a/app_paths.go b/app_paths.go index afd83f9..fa866ab 100644 --- a/app_paths.go +++ b/app_paths.go @@ -6,7 +6,6 @@ import ( "io/fs" "os" "os/user" - "path" "path/filepath" "github.com/wailsapp/wails/v2/pkg/runtime" @@ -17,6 +16,8 @@ var scriptsFolderEmbedded embed.FS var setLnkIconScriptPath string +var appFolder string + var packsFolder string var logsFolder string var savedConfigFolder string @@ -30,6 +31,8 @@ var appIconPath string var installationDirectory string var imageMagickPath string +var tempPngPaths map[string]string = map[string]string{} + func path_init() error { appData, err := os.UserConfigDir() if err != nil { @@ -40,19 +43,19 @@ func path_init() error { } runtime.LogDebug(appContext, "Found user config directory: "+appData) - appFolder := path.Join(appData, "iconium") + appFolder = filepath.Join(appData, "iconium") - packsFolder = path.Join(appFolder, "packs") - logsFolder = path.Join(appFolder, "logs") - savedConfigFolder = path.Join(appFolder, "savedconfigs") - activeIconFolder = path.Join(appFolder, "icons") - tempFolder = path.Join(appFolder, "temp") - maskFolder = path.Join(appFolder, "masks") - scriptsFolder = path.Join(appFolder, "scripts") + packsFolder = filepath.Join(appFolder, "packs") + logsFolder = filepath.Join(appFolder, "logs") + savedConfigFolder = filepath.Join(appFolder, "savedconfigs") + activeIconFolder = filepath.Join(appFolder, "icons") + tempFolder = filepath.Join(appFolder, "temp") + maskFolder = filepath.Join(appFolder, "masks") + scriptsFolder = filepath.Join(appFolder, "scripts") - configPath = path.Join(appFolder, "config.json") - appIconPath = path.Join(appFolder, "appicon.png") - setLnkIconScriptPath = path.Join(scriptsFolder, "setlnkicon.vbs") + configPath = filepath.Join(appFolder, "config.json") + appIconPath = filepath.Join(appFolder, "appicon.png") + setLnkIconScriptPath = filepath.Join(scriptsFolder, "setlnkicon.vbs") runtime.LogTrace(appContext, "Attempting to create folders") err = create_folder(appFolder) @@ -170,7 +173,7 @@ func path_init() error { } func get_logs_folder() (string, error) { - logsFolder = path.Join(os.Getenv("APPDATA"), "iconium", "logs") + logsFolder = filepath.Join(os.Getenv("APPDATA"), "iconium", "logs") // Create folder if it doesn't exist if _, err := os.Stat(logsFolder); os.IsNotExist(err) { @@ -183,7 +186,7 @@ func get_logs_folder() (string, error) { } func get_config_path() string { - configPath = path.Join(os.Getenv("APPDATA"), "iconium", "config.json") + configPath = filepath.Join(os.Getenv("APPDATA"), "iconium", "config.json") return configPath } @@ -204,3 +207,16 @@ func get_desktop_paths() (string, string) { return desktop, public } + +func (a *App) ClearTempPngPaths() { + for k := range tempPngPaths { + err := os.Remove(tempPngPaths[k]) + if err != nil { + runtime.LogErrorf(appContext, "Error removing tempPngPath: %s", err) + continue + } + delete(tempPngPaths, k) + } + + runtime.LogDebug(appContext, "Cleared tempPngPaths") +} diff --git a/dialog.go b/dialog.go index 8f58287..4aa4f33 100644 --- a/dialog.go +++ b/dialog.go @@ -1,9 +1,12 @@ package main import ( + "os" "os/exec" + "path/filepath" "strings" + "github.com/google/uuid" "github.com/wailsapp/wails/v2/pkg/runtime" ) @@ -92,6 +95,48 @@ func (a *App) GetBase64Png() string { return base64Png } +func (a *App) GetTempPng(id string) string { + oldPackPngPath, ok := tempPngPaths[id] + + tempPackPngPath := filepath.Join(tempFolder, "iconium-"+uuid.NewString()+".png") + + path, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "Select image", + Filters: []runtime.FileFilter{ + { + DisplayName: "PNG", + Pattern: "*.png", + }, + }, + }) + + if err != nil { + runtime.LogWarning(a.ctx, err.Error()) + return "" + } + if path == "" { + return "" + } + + err = copy_file(path, tempPackPngPath) + if err != nil { + runtime.LogErrorf(a.ctx, "Error copying pack.png file: %s", err.Error()) + return "" + } + + runtime.LogInfof(a.ctx, "Trimming path: %s (cut %s)", tempPackPngPath, appFolder) + trimmedPath := strings.TrimPrefix(tempPackPngPath, appFolder) + + tempPngPaths[id] = tempPackPngPath + + // Remove old pack png + if ok { + os.Remove(oldPackPngPath) + } + + return trimmedPath +} + func (a *App) GetIconFolder() string { path, err := runtime.OpenDirectoryDialog(a.ctx, runtime.OpenDialogOptions{ Title: "Select folder", diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 60e2c88..3ee0717 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -8,15 +8,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; -import { - CircleHelp, - Edit, - Loader2, - Monitor, - Pencil, - Trash, - Upload, -} from "lucide-react"; +import { Edit, Loader2, Monitor, Pencil, Trash, Upload } from "lucide-react"; import { AreYouSureDialog, AreYouSureDialogRef, @@ -35,11 +27,13 @@ import { AddFilesToIconPackFromPath, AddIconPack, ApplyIconPack, + ClearTempPngPaths, DeleteIconPack, GetIconFiles, GetIconPack, - GetIconPackInfo, - SetIconPackInfo, + GetIconPackList, + SetIconPackField, + SetIconPackMetadata, } from "@/wailsjs/go/main/App"; import { main } from "@/wailsjs/go/models"; import { @@ -68,6 +62,7 @@ import { AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; + export default function Packs() { const { t } = useTranslation(); const { setValue } = useStorage(); @@ -77,15 +72,15 @@ export default function Packs() { setValue("editingIconPack", editingIconPack); }); - const [pack, setPack] = useState(""); - const [packInfos, setPackInfos] = useState(); + const [selectedPackId, setSelectedPackId] = useState(""); + const [iconPacks, setIconPacks] = useState(); const [selectedPackKeyCount, setSelectedPackKeyCount] = useState(0); const dialogCloseRef = useRef(null); - const loadPackInfo = async () => { - const packInfos = await GetIconPackInfo(); - setPackInfos(packInfos); + const loadIconPacks = async () => { + const packs = await GetIconPackList(); + setIconPacks(packs); }; const reloadSelectedPack = () => { @@ -93,24 +88,31 @@ export default function Packs() { }; useEffect(() => { - loadPackInfo(); + loadIconPacks(); }, []); useEffect(() => { reloadSelectedPack(); - }, [pack]); + }, [selectedPackId]); return ( - + - {packInfos?.map((pack) => ( + {iconPacks?.map((pack) => ( - - {" "} - {t("my_packs.create_new_pack.title")} - + {t("my_packs.create_new_pack.title")} @@ -141,21 +140,22 @@ export default function Packs() { )} - {pack && !editingIconPack && ( + {selectedPackId && !editingIconPack && ( )} - {pack && editingIconPack && ( + {selectedPackId && editingIconPack && ( )} @@ -164,59 +164,70 @@ export default function Packs() { } interface PackTriggerProps { - iconPack: main.IconPack; - setPack: (pack: string) => void; + packId: string; + selectedPackId: string; + setSelectedPackId: (packId: string) => void; reloadSelectedPack: () => void; editingIconPack: boolean; disabled?: boolean; } function PackTrigger({ - iconPack, - setPack, + packId, + selectedPackId, + setSelectedPackId, reloadSelectedPack, editingIconPack, disabled, ...props }: PackTriggerProps) { - const [enabledState, setEnabledState] = useState(iconPack.settings.enabled); + const [iconPack, setIconPack] = useState(); + const [enabledState, setEnabledState] = useState(false); useEffect(() => { - setEnabledState(iconPack.settings.enabled); - }, [iconPack]); + GetIconPack(packId).then((iconPack) => { + setIconPack(iconPack); + setEnabledState(iconPack.settings.enabled); + }); + }, [packId]); const handleEnable = () => { - iconPack.settings.enabled = !iconPack.settings.enabled; - SetIconPackInfo(iconPack).then(() => { - setEnabledState(!enabledState); - reloadSelectedPack(); - }); + SetIconPackField(packId, "settings.json", "enabled", !enabledState).then( + () => { + setEnabledState(!enabledState); + if (packId === selectedPackId) { + reloadSelectedPack(); + } + } + ); }; return ( setPack(iconPack.metadata.id)} + value={packId} + onClick={() => setSelectedPackId(packId)} className="flex justify-between p-4 w-full" disabled={disabled} {...props} >
- {iconPack.metadata.icon ? ( - pack-icon - ) : ( - - )} + {!editingIconPack && (
- {iconPack.metadata.name} + {iconPack?.metadata.name}
-
{iconPack.metadata.version}
+
{iconPack?.metadata.version}
)}
@@ -233,30 +244,29 @@ function PackTrigger({ interface PackContentProps { iconPackId: string; - setPack: (pack: string) => void; - loadPackInfo: () => void; + setSelectedPackId: (packId: string) => void; + loadIconPacks: () => void; setEditingIconPack: (editingIconPack: boolean) => void; + reloadSelectedPack: () => void; } function PackContent({ iconPackId, - setPack, - loadPackInfo, + setSelectedPackId, + loadIconPacks, setEditingIconPack, + reloadSelectedPack, }: PackContentProps) { const { t } = useTranslation(); const [loading, setLoading] = useState(true); - const [editMode, setEditMode] = useState(false); - const [iconPackInfo, setIconPackInfo] = useState( - main.IconPack.createFrom({}) - ); - const [editedIconPackInfo, setEditedIconPackInfo] = useState( - main.IconPack.createFrom({}) - ); + const [editingMetadata, setEditingMetadata] = useState(false); + const [iconPack, setIconPack] = useState(); + const [editedIconPack, setEditedIconPack] = useState(); const dialogRef = useRef(null); const [deleteGeneratedIcons, setDeleteGeneratedIcons] = useState(false); + const [enabled, setEnabled] = useState(false); const [cornerRadius, setCornerRadius] = useState(-1); const [opacity, setOpacity] = useState(-1); @@ -268,79 +278,90 @@ function PackContent({ const running = applyRunning || addIconsFromDesktopRunning || addIconsRunning; useEffect(() => { - GetIconPack(iconPackId).then((iconPack) => { - setIconPackInfo(iconPack); - setCornerRadius(iconPack.settings.cornerRadius); - setOpacity(iconPack.settings.opacity); + GetIconPack(iconPackId).then((pack) => { + setIconPack(pack); + setEnabled(pack.settings.enabled); + setOpacity(pack.settings.opacity); + setCornerRadius(pack.settings.cornerRadius); setLoading(false); }); }, []); - const handleChange = ( + const handleMetadataChange = ( field: keyof main.IconPack["metadata"], - value: string, - editMode?: boolean + value: string ) => { - const newIconPackInfo = { - ...iconPackInfo, + if (iconPack === undefined) { + return; + } + + const newIconPack = { + ...iconPack, metadata: { - ...iconPackInfo.metadata, + ...iconPack.metadata, [field]: value, }, } as main.IconPack; - if (editMode) { - setEditedIconPackInfo(newIconPackInfo); - } else { - setIconPackInfo(newIconPackInfo); + setEditedIconPack(newIconPack); + }; + + const handleSettingChange = ( + field: keyof main.IconPack["settings"], + value: boolean | number + ) => { + if (iconPack === undefined) { + return; } + + SetIconPackField(iconPackId, "settings.json", field, value).then(() => { + const newIconPack = { + ...iconPack, + settings: { + ...iconPack.settings, + [field]: value, + }, + } as main.IconPack; + + setIconPack(newIconPack); + + if (field === "enabled") { + console.log("loadIconPacks"); + loadIconPacks(); + } + }); }; - const handleEdit = () => { - setEditedIconPackInfo(iconPackInfo); - setEditMode(true); + const handleEditStart = () => { + setEditedIconPack(iconPack); + setEditingMetadata(true); }; - const handleSave = () => { - setIconPackInfo(editedIconPackInfo); - SetIconPackInfo(editedIconPackInfo).then(() => { - loadPackInfo(); + const handleEditSave = () => { + if (editedIconPack === undefined) { + return; + } + + SetIconPackMetadata(iconPackId, editedIconPack.metadata).then(() => { + setIconPack(editedIconPack); + loadIconPacks(); + reloadSelectedPack(); + setEditingMetadata(false); }); - setEditMode(false); }; - const handleCancel = () => { - setEditMode(false); + const handleEditCancel = () => { + setEditingMetadata(false); + ClearTempPngPaths(); }; const handleDelete = () => { DeleteIconPack(iconPackId, deleteGeneratedIcons).then(() => { - setPack(""); - loadPackInfo(); + setSelectedPackId(""); + loadIconPacks(); }); }; - const handleSettingChange = ( - field: keyof main.IconPack["settings"], - value: boolean | number, - editMode?: boolean - ) => { - const newIconPackInfo = { - ...iconPackInfo, - settings: { - ...iconPackInfo.settings, - [field]: value, - }, - } as main.IconPack; - - if (editMode) { - setEditedIconPackInfo(newIconPackInfo); - } else { - setIconPackInfo(newIconPackInfo); - SetIconPackInfo(newIconPackInfo).then(loadPackInfo); - } - }; - const fields: (keyof main.IconPack["metadata"])[] = [ "name", "version", @@ -353,7 +374,7 @@ function PackContent({ const handleApplyIconPack = () => { setApplyRunning(true); - ApplyIconPack(iconPackInfo.metadata.id).finally(() => { + ApplyIconPack(iconPackId).finally(() => { setApplyRunning(false); }); }; @@ -361,10 +382,10 @@ function PackContent({ const handleAddIconsFromDesktop = () => { setAddIconsFromDesktopRunning(true); - AddFilesToIconPackFromDesktop(iconPackInfo.metadata.id).then(() => { - GetIconPack(iconPackInfo.metadata.id) - .then((iconPack) => { - setIconPackInfo(iconPack); + AddFilesToIconPackFromDesktop(iconPackId).then(() => { + GetIconPack(iconPackId) + .then((pack) => { + setIconPack(pack); }) .finally(() => { setAddIconsFromDesktopRunning(false); @@ -377,17 +398,15 @@ function PackContent({ if (files) { setAddIconsRunning(true); - AddFilesToIconPackFromPath(iconPackInfo.metadata.id, files, true).then( - () => { - GetIconPack(iconPackInfo.metadata.id) - .then((iconPack) => { - setIconPackInfo(iconPack); - }) - .finally(() => { - setAddIconsRunning(false); - }); - } - ); + AddFilesToIconPackFromPath(iconPackId, files, true).then(() => { + GetIconPack(iconPackId) + .then((pack) => { + setIconPack(pack); + }) + .finally(() => { + setAddIconsRunning(false); + }); + }); } }); }; @@ -399,7 +418,7 @@ function PackContent({ } }, []); - if (loading) { + if (loading || iconPack === undefined) { return (
@@ -422,15 +441,16 @@ function PackContent({
handleChange("icon", icon, true)} - sizeClass="w-12 h-12" - editSizeClass="w-7 h-7" - editable={editMode} + packId={iconPackId} + editable={editingMetadata} + unkown />
{fields.map((field) => ( @@ -442,26 +462,28 @@ function PackContent({ ".label" )}
- {editMode ? ( + {editingMetadata ? ( - handleChange(field, e.target.value, true) + handleMetadataChange(field, e.target.value) } /> ) : ( -
- {iconPackInfo.metadata[field]} -
+
{iconPack.metadata[field]}
)}
))}
- {!editMode ? ( + {!editingMetadata ? ( <> - @@ -578,10 +600,11 @@ function PackContent({ - handleSettingChange("enabled", enabled) - } + checked={enabled} + onCheckedChange={(enabled) => { + setEnabled(enabled as boolean); + handleSettingChange("enabled", enabled); + }} /> @@ -605,7 +628,7 @@ function PackContent({ onPointerUp={() => handleSettingChange("cornerRadius", cornerRadius) } - defaultValue={[iconPackInfo.settings.cornerRadius]} + defaultValue={[iconPack.settings.cornerRadius]} min={0} max={50} step={1} @@ -634,7 +657,7 @@ function PackContent({ setOpacity(value[0] as number)} onPointerUp={() => handleSettingChange("opacity", opacity)} - defaultValue={[iconPackInfo.settings.opacity]} + defaultValue={[iconPack.settings.opacity]} min={10} max={100} step={1} @@ -652,17 +675,11 @@ function PackContent({ {t("my_packs.card.icons.label")}
- {iconPackInfo.files?.map((file) => + {iconPack.files?.map((file) => file.hasIcon ? ( void; + loadIconPacks: () => void; + setEditingIconPack: (editingIconPack: boolean) => void; +} + function PackEdit({ iconPackId, - setPack, - loadPackInfo, + setSelectedPackId: setPack, + loadIconPacks: loadPackInfo, setEditingIconPack, -}: PackContentProps) { +}: PackEditProps) { const { t } = useTranslation(); const [loading, setLoading] = useState(true); - const [iconPackInfo, setIconPackInfo] = useState( - main.IconPack.createFrom({}) - ); + const [iconPack, setIconPack] = useState(); const [addIconsFromDesktopRunning, setAddIconsFromDesktopRunning] = useState(false); @@ -694,8 +716,8 @@ function PackEdit({ const running = addIconsFromDesktopRunning || addIconsRunning; useEffect(() => { - GetIconPack(iconPackId).then((iconPack) => { - setIconPackInfo(iconPack); + GetIconPack(iconPackId).then((pack) => { + setIconPack(pack); setLoading(false); }); }, []); @@ -716,7 +738,7 @@ function PackEdit({ }, 2000); }; - if (loading) { + if (loading || iconPack === undefined) { return (
@@ -765,14 +787,14 @@ function PackEdit({
- {iconPackInfo.files.map((file) => ( + {iconPack.files.map((file) => (
) { - AddIconPack(data.name, data.version, data.author, data.icon).then(() => { + AddIconPack(data.name, data.version, data.author).then(() => { loadPackInfo(); dialogCloseRef.current?.click(); }); } + useEffect(() => { + return () => { + // This function runs before the component unmounts + ClearTempPngPaths(); + }; + }, []); + return (
( + render={() => ( {t("my_packs.card.pack_information.information.icon.label")} - form.setValue("icon", icon)} - /> + )} @@ -890,6 +914,7 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { void; - editable?: boolean; + src?: string; + packId?: string; className?: string; - sizeClass: string; - editSizeClass: string; + sizeClass?: string; + editSizeClass?: string; + editable?: boolean; + unkown?: boolean; } const SelectImage: React.FC = ({ - icon, - onIconChange, - editable = true, + src: originalSrc = "", + packId = "temp", className, - sizeClass, - editSizeClass, + sizeClass = "w-12 h-12", + editSizeClass = "w-7 h-7", + editable = false, + unkown = true, ...rest }) => { - const handleIconEdit = () => { - GetBase64Png().then((base64) => { - onIconChange(base64); + const [loading, setLoading] = useState(true); + const [iconPath, setIconPath] = useState(originalSrc); + const src = editable ? iconPath : originalSrc; + + const handleUpload = () => { + GetTempPng(packId).then((path) => { + if (path) { + setIconPath(path); + } }); }; - const selectedIcon = icon ? ( - pack-icon - ) : ( - - ); + useEffect(() => { + if (!editable) { + setIconPath(originalSrc); + setLoading(true); + } + }, [editable]); return ( ); }; diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index 4a7764f..c2c768e 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -10,12 +10,14 @@ export function AddFilesToIconPackFromFolder(arg1:string,arg2:string,arg3:boolea export function AddFilesToIconPackFromPath(arg1:string,arg2:Array,arg3:boolean):Promise; -export function AddIconPack(arg1:string,arg2:string,arg3:string,arg4:string):Promise; +export function AddIconPack(arg1:string,arg2:string,arg3:string):Promise; export function ApplyIconPack(arg1:string):Promise; export function CheckForUpdate():Promise; +export function ClearTempPngPaths():Promise; + export function DeleteIconPack(arg1:string,arg2:boolean):Promise; export function GetBase64Png():Promise; @@ -32,10 +34,12 @@ export function GetIconFolder():Promise; export function GetIconPack(arg1:string):Promise; -export function GetIconPackInfo():Promise>; +export function GetIconPackList():Promise>; export function GetLoadConfigPath():Promise; +export function GetTempPng(arg1:string):Promise; + export function GetVersion():Promise; export function NeedsAdminPrivileges():Promise; @@ -52,7 +56,13 @@ export function SendNotification(arg1:string,arg2:string,arg3:string,arg4:string export function SetConfigField(arg1:string,arg2:any):Promise; -export function SetIconPackInfo(arg1:main.IconPack):Promise; +export function SetIconPack(arg1:main.IconPack):Promise; + +export function SetIconPackField(arg1:string,arg2:string,arg3:string,arg4:any):Promise; + +export function SetIconPackMetadata(arg1:string,arg2:main.Metadata):Promise; + +export function UUID():Promise; export function Update(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index c884471..f7c90eb 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -18,8 +18,8 @@ export function AddFilesToIconPackFromPath(arg1, arg2, arg3) { return window['go']['main']['App']['AddFilesToIconPackFromPath'](arg1, arg2, arg3); } -export function AddIconPack(arg1, arg2, arg3, arg4) { - return window['go']['main']['App']['AddIconPack'](arg1, arg2, arg3, arg4); +export function AddIconPack(arg1, arg2, arg3) { + return window['go']['main']['App']['AddIconPack'](arg1, arg2, arg3); } export function ApplyIconPack(arg1) { @@ -30,6 +30,10 @@ export function CheckForUpdate() { return window['go']['main']['App']['CheckForUpdate'](); } +export function ClearTempPngPaths() { + return window['go']['main']['App']['ClearTempPngPaths'](); +} + export function DeleteIconPack(arg1, arg2) { return window['go']['main']['App']['DeleteIconPack'](arg1, arg2); } @@ -62,14 +66,18 @@ export function GetIconPack(arg1) { return window['go']['main']['App']['GetIconPack'](arg1); } -export function GetIconPackInfo() { - return window['go']['main']['App']['GetIconPackInfo'](); +export function GetIconPackList() { + return window['go']['main']['App']['GetIconPackList'](); } export function GetLoadConfigPath() { return window['go']['main']['App']['GetLoadConfigPath'](); } +export function GetTempPng(arg1) { + return window['go']['main']['App']['GetTempPng'](arg1); +} + export function GetVersion() { return window['go']['main']['App']['GetVersion'](); } @@ -102,8 +110,20 @@ export function SetConfigField(arg1, arg2) { return window['go']['main']['App']['SetConfigField'](arg1, arg2); } -export function SetIconPackInfo(arg1) { - return window['go']['main']['App']['SetIconPackInfo'](arg1); +export function SetIconPack(arg1) { + return window['go']['main']['App']['SetIconPack'](arg1); +} + +export function SetIconPackField(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['SetIconPackField'](arg1, arg2, arg3, arg4); +} + +export function SetIconPackMetadata(arg1, arg2) { + return window['go']['main']['App']['SetIconPackMetadata'](arg1, arg2); +} + +export function UUID() { + return window['go']['main']['App']['UUID'](); } export function Update(arg1) { diff --git a/frontend/src/wailsjs/go/models.ts b/frontend/src/wailsjs/go/models.ts index 0e50ab5..adff52a 100644 --- a/frontend/src/wailsjs/go/models.ts +++ b/frontend/src/wailsjs/go/models.ts @@ -105,7 +105,7 @@ export namespace main { name: string; version: string; author: string; - icon: string; + iconName: string; static createFrom(source: any = {}) { return new Metadata(source); @@ -117,7 +117,7 @@ export namespace main { this.name = source["name"]; this.version = source["version"]; this.author = source["author"]; - this.icon = source["icon"]; + this.iconName = source["iconName"]; } } export class IconPack { diff --git a/iconpack.go b/iconpack.go index 9e8eac1..2f0d47d 100644 --- a/iconpack.go +++ b/iconpack.go @@ -7,6 +7,7 @@ import ( "path" "path/filepath" "sort" + "strconv" "strings" "sync" @@ -28,11 +29,11 @@ type FileInfo struct { } type Metadata struct { - Id string `json:"id"` - Name string `json:"name"` - Version string `json:"version"` - Author string `json:"author"` - Icon string `json:"icon"` + Id string `json:"id"` + Name string `json:"name"` + Version string `json:"version"` + Author string `json:"author"` + IconName string `json:"iconName"` } type IconPack struct { @@ -49,17 +50,14 @@ type IconPackSettings struct { var allowedFileExtensions = []string{".lnk"} -var iconPacks []IconPack - var iconPackCache map[string]IconPack = map[string]IconPack{} -func CreateIconPack(name string, version string, author string, icon string) (IconPack, error) { +func CreateIconPack(name string, version string, author string) (IconPack, error) { var iconPack IconPack iconPack.Metadata.Id = uuid.NewString() iconPack.Metadata.Name = name iconPack.Metadata.Version = version iconPack.Metadata.Author = author - iconPack.Metadata.Icon = icon iconPack.Files = []FileInfo{} iconPack.Settings.Enabled = false @@ -134,115 +132,186 @@ func ReadIconPack(id string) (IconPack, error) { return iconPack, nil } -func (a *App) GetIconPack(id string) IconPack { +func (a *App) GetIconPack(id string) (IconPack, error) { if iconPack, ok := iconPackCache[id]; ok { - return iconPack + return iconPack, nil } iconPack, err := ReadIconPack(id) if err != nil { runtime.LogError(a.ctx, fmt.Sprintf("Failed to read icon pack: %s", err.Error())) - return IconPack{} + return IconPack{}, err } - return iconPack + return iconPack, nil } -func GetIconPackInfo() ([]IconPack, error) { - files, err := os.ReadDir(packsFolder) +func (a *App) SetIconPack(iconPack IconPack) error { + err := WriteIconPack(iconPack) + if err != nil { - return nil, err + runtime.LogError(a.ctx, fmt.Sprintf("Failed to write icon pack: %s", err.Error())) + return err } - // Sort files by added date - sort.Slice(files, func(i, j int) bool { - infoI, err := os.Stat(path.Join(packsFolder, files[i].Name())) - if err != nil { - return false - } - - infoJ, err := os.Stat(path.Join(packsFolder, files[j].Name())) - if err != nil { - return false - } + return nil +} - return infoI.ModTime().Before(infoJ.ModTime()) - }) +func (a *App) SetIconPackField(packId string, fileName string, field string, value interface{}) { + // Check if the icon pack exists + iconPack, err := a.GetIconPack(packId) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Icon pack %s not found: %s", packId, err.Error())) + return + } - for _, file := range files { - if file.IsDir() { - packPath := path.Join(packsFolder, file.Name()) - metadataPath := path.Join(packPath, "metadata.json") - settingsPath := path.Join(packPath, "settings.json") + packFolder := path.Join(packsFolder, packId) - if _, err := os.Stat(metadataPath); os.IsNotExist(err) { - continue - } + // Check if the file exists + filePath := path.Join(packFolder, fileName) + if _, err := os.Stat(filePath); os.IsNotExist(err) { + runtime.LogError(appContext, fmt.Sprintf("File %s not found", filePath)) + return + } - if _, err := os.Stat(settingsPath); os.IsNotExist(err) { - continue - } + // Read the file + var jsonData map[string]interface{} + err = readJSON(filePath, &jsonData) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Failed to read file %s: %s", filePath, err.Error())) + return + } - var metadata Metadata - var settings IconPackSettings + // Set the field + jsonData[field] = value - if err := readJSON(metadataPath, &metadata); err != nil { - return nil, err - } + // Write the file + err = writeJSON(filePath, jsonData) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Failed to write file %s: %s", filePath, err.Error())) + return + } - if err := readJSON(settingsPath, &settings); err != nil { - return nil, err + // Update cache + switch fileName { + case "metadata.json": + switch field { + case "id": + iconPack.Metadata.Id = value.(string) + case "name": + iconPack.Metadata.Name = value.(string) + case "version": + iconPack.Metadata.Version = value.(string) + case "author": + iconPack.Metadata.Author = value.(string) + } + case "settings.json": + switch field { + case "enabled": + iconPack.Settings.Enabled = value.(bool) + case "cornerRadius": + intVal, err := strconv.Atoi(fmt.Sprintf("%v", value)) + if err != nil { + runtime.LogWarning(appContext, fmt.Sprintf("Failed to convert %v to int: %s", value, err.Error())) + return } - - iconPacks = append(iconPacks, IconPack{ - Metadata: metadata, - Settings: settings, - }) - - cachedPack := iconPackCache[metadata.Id] - if cachedPack.Metadata.Id == metadata.Id { - cachedPack.Metadata = metadata - cachedPack.Settings = settings - iconPackCache[metadata.Id] = cachedPack + iconPack.Settings.CornerRadius = intVal + case "opacity": + intVal, err := strconv.Atoi(fmt.Sprintf("%v", value)) + if err != nil { + runtime.LogWarning(appContext, fmt.Sprintf("Failed to convert %v to int: %s", value, err.Error())) + return } + iconPack.Settings.Opacity = intVal } } - return iconPacks, nil + a.SetIconPack(iconPack) } -func (a *App) GetIconPackInfo() []IconPack { - if iconPacks == nil { - iconPacks, _ = GetIconPackInfo() +func (a *App) SetIconPackMetadata(packId string, metadata Metadata) { + // Check if the icon pack exists + iconPack, err := a.GetIconPack(packId) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Icon pack %s not found: %s", packId, err.Error())) + return } - return iconPacks -} + tempPackPngPath, ok := tempPngPaths[packId] -func (a *App) SetIconPackInfo(iconPack IconPack) { - metadataFile := path.Join(packsFolder, iconPack.Metadata.Id, "metadata.json") - settingsFile := path.Join(packsFolder, iconPack.Metadata.Id, "settings.json") + if ok { + runtime.LogDebugf(appContext, "Copying temp pack png: %s", tempPackPngPath) - if err := writeJSON(metadataFile, iconPack.Metadata); err != nil { - runtime.LogError(a.ctx, fmt.Sprintf("Failed to write metadata file: %s", err.Error())) + metadata.IconName = uuid.New().String() + + // Copy temp pack png + err = copy_file(tempPackPngPath, path.Join(packsFolder, iconPack.Metadata.Id, metadata.IconName+".png")) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Failed to copy temp pack png: %s", err.Error())) + return + } + + // Remove temp pack png + err = os.Remove(tempPackPngPath) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Failed to remove temp pack png: %s", err.Error())) + return + } + delete(tempPngPaths, packId) + + os.Remove(path.Join(packsFolder, iconPack.Metadata.Id, iconPack.Metadata.IconName+".png")) } - if err := writeJSON(settingsFile, iconPack.Settings); err != nil { - runtime.LogError(a.ctx, fmt.Sprintf("Failed to write settings file: %s", err.Error())) + + // Update cache + iconPack.Metadata = metadata + a.SetIconPack(iconPack) +} + +func CacheIconPacks() error { + // Clear cache + for k := range iconPackCache { + delete(iconPackCache, k) } - for i, pack := range iconPacks { - if pack.Metadata.Id == iconPack.Metadata.Id { - iconPacks[i] = iconPack - cachedPack := iconPackCache[iconPack.Metadata.Id] - if cachedPack.Metadata.Id == iconPack.Metadata.Id { - cachedPack.Metadata = iconPack.Metadata - cachedPack.Settings = iconPack.Settings - iconPackCache[iconPack.Metadata.Id] = cachedPack + files, err := os.ReadDir(packsFolder) + if err != nil { + return err + } + + for _, file := range files { + if file.IsDir() { + iconPack, err := ReadIconPack(file.Name()) + + if err != nil { + runtime.LogWarningf(appContext, fmt.Sprintf("Failed to read icon pack (%s): %s", file.Name(), err.Error())) + continue } - break + + iconPackCache[iconPack.Metadata.Id] = iconPack } } + + return nil +} + +func (a *App) GetIconPackList() []IconPack { + if len(iconPackCache) == 0 { + CacheIconPacks() + } + + iconPacks := make([]IconPack, 0, len(iconPackCache)) + + for _, iconPack := range iconPackCache { + iconPacks = append(iconPacks, iconPack) + } + + // Sort icon packs by name + sort.Slice(iconPacks, func(i, j int) bool { + return iconPacks[i].Metadata.Name < iconPacks[j].Metadata.Name + }) + + return iconPacks } func CreateFileInfo(packId string, path string) (FileInfo, error) { @@ -288,13 +357,10 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { return FileInfo{}, err } defer file.Close() - fileStat, err := file.Stat() - if err != nil { return FileInfo{}, err } - if fileStat.IsDir() { fileInfo.Extension = "dir" } @@ -320,16 +386,36 @@ func GetAppliedIcon(path string) (string, error) { return "", errors.New("icon not found") } -func (a *App) AddIconPack(name string, version string, author string, icon string) error { - iconPack, err := CreateIconPack(name, version, author, icon) - +func (a *App) AddIconPack(name string, version string, author string) error { + iconPack, err := CreateIconPack(name, version, author) if err != nil { return err } - iconPacks = append(iconPacks, iconPack) + tempPackPngPath, ok := tempPngPaths["temp"] - return WriteIconPack(iconPack) + if ok { + runtime.LogDebug(appContext, "Adding pack png for "+iconPack.Metadata.Id) + + iconPack.Metadata.IconName = uuid.NewString() + + // Copy temp pack png + err = copy_file(tempPackPngPath, path.Join(packsFolder, iconPack.Metadata.Id, iconPack.Metadata.IconName+".png")) + if err != nil { + return err + } + + // Remove temp pack png + err = os.Remove(tempPackPngPath) + if err != nil { + return err + } + delete(tempPngPaths, "temp") + + a.SetIconPack(iconPack) + } + + return nil } func (a *App) DeleteIconPack(id string, deleteGeneratedIcons bool) error { @@ -352,38 +438,29 @@ func (a *App) DeleteIconPack(id string, deleteGeneratedIcons bool) error { } } - for i, pack := range iconPacks { - if pack.Metadata.Id == id { - iconPacks = append(iconPacks[:i], iconPacks[i+1:]...) - iconPackCache[id] = IconPack{} - break - } - } + delete(iconPackCache, id) return nil } func (a *App) AddFileToIconPackFromPath(id string, path string, save bool) { fileInfo, err := CreateFileInfo(id, path) - if err != nil { runtime.LogError(a.ctx, err.Error()) return } - cachedPack := iconPackCache[id] - if cachedPack.Metadata.Id != id { - cachedPack, err = ReadIconPack(id) - if err != nil { - runtime.LogError(a.ctx, fmt.Sprintf("Failed to read icon pack: %s", err.Error())) - return - } + cachedPack, err := a.GetIconPack(id) + if err != nil { + runtime.LogError(a.ctx, fmt.Sprintf("Failed to read icon pack: %s", err.Error())) + return } cachedPack.Files = append(cachedPack.Files, fileInfo) + iconPackCache[id] = cachedPack if save { - WriteIconPack(iconPackCache[id]) + a.SetIconPack(cachedPack) return } @@ -396,13 +473,12 @@ func (a *App) AddFilesToIconPackFromPath(id string, path []string, save bool) { } if save { - WriteIconPack(iconPackCache[id]) + a.SetIconPack(iconPackCache[id]) } } func (a *App) AddFilesToIconPackFromFolder(id string, path string, save bool) { files, err := os.ReadDir(path) - if err != nil { runtime.LogError(a.ctx, err.Error()) return @@ -421,7 +497,12 @@ func (a *App) AddFilesToIconPackFromFolder(id string, path string, save bool) { wg.Wait() if save { - WriteIconPack(iconPackCache[id]) + runtime.LogInfo(a.ctx, fmt.Sprintf("Saving icon pack %s", id)) + + err = a.SetIconPack(iconPackCache[id]) + if err != nil { + runtime.LogError(a.ctx, err.Error()) + } } } @@ -433,7 +514,7 @@ func (a *App) AddFilesToIconPackFromDesktop(id string) { } func (a *App) ApplyIconPack(id string) { - pack, err := ReadIconPack(id) + pack, err := a.GetIconPack(id) if err != nil { runtime.LogError(appContext, err.Error()) @@ -445,7 +526,13 @@ func (a *App) ApplyIconPack(id string) { // Save icon pack if err == nil { runtime.LogInfo(appContext, fmt.Sprintf("Applied icon pack %s, attempting to save", pack.Metadata.Id)) - WriteIconPack(pack) + err = a.SetIconPack(pack) + + if err == nil { + runtime.LogInfo(appContext, fmt.Sprintf("Saved icon pack %s", pack.Metadata.Id)) + } else { + runtime.LogError(appContext, err.Error()) + } } else { runtime.LogError(appContext, err.Error()) } diff --git a/utils.go b/utils.go index fa58ab8..a34debe 100644 --- a/utils.go +++ b/utils.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "github.com/google/uuid" lnk "github.com/parsiya/golnk" "github.com/wailsapp/wails/v2/pkg/runtime" ) @@ -186,3 +187,7 @@ func copy_file(src string, dst string) error { } return nil } + +func (a *App) UUID() string { + return uuid.NewString() +} From 6e3e1ef60fa75c6a732d2dc5b46ad437fa73de8f Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 18 Aug 2024 16:25:05 +0300 Subject: [PATCH 133/221] fix settings scroll --- frontend/src/components/Settings.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 7764c41..2ecc6ca 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -79,13 +79,13 @@ export default function Settings() { - + - + @@ -95,13 +95,13 @@ export default function Settings() { - + - + Edit your system settings here. @@ -112,7 +112,7 @@ export default function Settings() { - + From edc83d2691ec03c9c68bfc96b2e7e9c86bb971dd Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 18 Aug 2024 16:26:55 +0300 Subject: [PATCH 134/221] remove edit tab --- frontend/src/App.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d5157f4..f844245 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -101,13 +101,10 @@ function App() {
- setTab("packs")} disabled={getValue("editingIconPack")}> + setTab("packs")} disabled={getValue("editingIconPack")} className="px-6"> {t("nav.my_packs")} - setTab("edit")} disabled={getValue("editingIconPack")}> - {t("nav.edit")} - - setTab("settings")} disabled={getValue("editingIconPack")}> + setTab("settings")} disabled={getValue("editingIconPack")} className="px-6"> {t("nav.settings")}
@@ -117,9 +114,6 @@ function App() { - - Edit your packs here. - From ef7c2a5eee5e39e717dcd9a9ccadb205baa0d21f Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Sun, 18 Aug 2024 16:44:24 +0300 Subject: [PATCH 135/221] fix z levels --- frontend/src/App.tsx | 2 +- frontend/src/components/Packs.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f844245..7412732 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -99,7 +99,7 @@ function App() {
- +
setTab("packs")} disabled={getValue("editingIconPack")} className="px-6"> {t("nav.my_packs")} diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 3ee0717..0533707 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -98,7 +98,7 @@ export default function Packs() { return ( {iconPacks?.map((pack) => ( @@ -751,7 +751,7 @@ function PackEdit({ return (
-
+
- +
- {iconPack.files.map((file) => ( - + {files.map((file, index) => ( +
- + {file.name}
- {String(setPack) + - String(loadPackInfo) + - String(setEditingIconPack)} +
+
+
+ + { + setUpdateArray((prevUpdateArray) => { + const newArray = [...prevUpdateArray]; + newArray[index] = prevUpdateArray[index] + 1; + return newArray; + }); + }} + editable + /> +
+ { + handleInputChange(index, "name", value); + }} + label={"Name"} + className="justify-between w-full" + /> +
+ { + handleInputChange(index, "description", value); + }} + label={"Description"} + /> + { + handleInputChange(index, "path", value); + Ext(value).then((ext) => { + handleInputChange(index, "extension", ext); + }); + }} + label={"Path"} + /> + + {file.extension === ".lnk" && ( + { + handleInputChange(index, "destinationPath", value); + }} + label={"Destination Path"} + /> + )} +
))} @@ -818,6 +923,75 @@ function PackEdit({ ); } +function TextInput({ + value, + placeholder, + onChange, + label, + className = "", +}: { + value: string; + placeholder?: string; + onChange: (value: string) => void; + label: string; + className?: string; +}) { + return ( +
+ + { + onChange(e.target.value); + }} + /> +
+ ); +} + +function PathInput({ + value, + placeholder, + onChange, + label, +}: { + value: string; + placeholder?: string; + onChange: (value: string) => void; + label: string; +}) { + const handleChoosePath = () => { + GetFilePath(value).then((path) => { + if (path) { + LogDebug("Path selected: " + path); + onChange(path); + } + }); + }; + + return ( +
+ + +
+ ); +} + interface CreatePackFormProps { loadPackInfo: () => void; dialogCloseRef: React.RefObject; @@ -876,7 +1050,7 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { }); } - useEffect(() => { + useEffect(() => { return () => { // This function runs before the component unmounts ClearTempPngPaths(); diff --git a/frontend/src/components/SelectImage.tsx b/frontend/src/components/SelectImage.tsx index 75c9d59..df985db 100644 --- a/frontend/src/components/SelectImage.tsx +++ b/frontend/src/components/SelectImage.tsx @@ -2,7 +2,8 @@ import React, { useEffect, useState } from "react"; import { Skeleton } from "./ui/skeleton"; import { CircleHelp, Upload } from "lucide-react"; import { Button } from "./ui/button"; -import { GetTempPng } from "@/wailsjs/go/main/App"; +import { GetTempPng, GetTempPngPath } from "@/wailsjs/go/main/App"; +import { LogDebug } from "@/wailsjs/runtime/runtime"; interface SelectImageProps { src?: string; @@ -12,6 +13,8 @@ interface SelectImageProps { editSizeClass?: string; editable?: boolean; unkown?: boolean; + alwaysShowOriginal?: boolean; + onChange?: (value: string) => void; } const SelectImage: React.FC = ({ @@ -22,27 +25,44 @@ const SelectImage: React.FC = ({ editSizeClass = "w-7 h-7", editable = false, unkown = true, + alwaysShowOriginal = true, + onChange, ...rest }) => { const [loading, setLoading] = useState(true); const [iconPath, setIconPath] = useState(originalSrc); - const src = editable ? iconPath : originalSrc; + const [imgKey, setImgKey] = useState(0); const handleUpload = () => { GetTempPng(packId).then((path) => { if (path) { setIconPath(path); + onChange?.(path); } }); }; useEffect(() => { - if (!editable) { + if (alwaysShowOriginal && !editable) { setIconPath(originalSrc); setLoading(true); + } else { + GetTempPngPath(packId).then((path) => { + LogDebug("GetTempPngPath: " + path); + if (path) { + setIconPath(path); + } else { + setIconPath(originalSrc); + } + setLoading(true); + }); } }, [editable]); + useEffect(() => { + setImgKey(imgKey + 1); + }, [loading]); + return (
- + diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index 33d1ad6..4acc48c 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -70,6 +70,8 @@ export function SetIconPack(arg1:main.IconPack):Promise; export function SetIconPackField(arg1:string,arg2:string,arg3:string,arg4:any):Promise; +export function SetIconPackFiles(arg1:string,arg2:Array):Promise; + export function SetIconPackMetadata(arg1:string,arg2:main.Metadata):Promise; export function UUID():Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 51e5a29..a95077c 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -138,6 +138,10 @@ export function SetIconPackField(arg1, arg2, arg3, arg4) { return window['go']['main']['App']['SetIconPackField'](arg1, arg2, arg3, arg4); } +export function SetIconPackFiles(arg1, arg2) { + return window['go']['main']['App']['SetIconPackFiles'](arg1, arg2); +} + export function SetIconPackMetadata(arg1, arg2) { return window['go']['main']['App']['SetIconPackMetadata'](arg1, arg2); } diff --git a/iconpack.go b/iconpack.go index 63e1467..33fb6dc 100644 --- a/iconpack.go +++ b/iconpack.go @@ -268,6 +268,45 @@ func (a *App) SetIconPackMetadata(packId string, metadata Metadata) { a.SetIconPack(iconPack) } +func (a *App) SetIconPackFiles(packId string, files []FileInfo) { + // Check if the icon pack exists + iconPack, err := a.GetIconPack(packId) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Icon pack %s not found: %s", packId, err.Error())) + return + } + + for _, file := range files { + file.HasIcon = false + + tempPngPath, ok := tempPngPaths[file.Id] + if ok { + runtime.LogDebugf(appContext, "Attempting to copy temp png: %s", tempPngPath) + + // Copy temp png + err = copy_file(tempPngPath, path.Join(packsFolder, packId, "icons", file.Id+".png")) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Failed to copy temp png: %s", err.Error())) + continue + } + + // Remove temp png + err = os.Remove(tempPngPath) + if err != nil { + runtime.LogError(appContext, fmt.Sprintf("Failed to remove temp png: %s", err.Error())) + continue + } + delete(tempPngPaths, file.Id) + + file.HasIcon = true + } + } + + // Update cache + iconPack.Files = files + a.SetIconPack(iconPack) +} + func CacheIconPacks() error { // Clear cache for k := range iconPackCache { From eec9d9eb6bc33b963777c5523914934461553e3c Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Mon, 19 Aug 2024 17:57:25 +0300 Subject: [PATCH 139/221] add empty icon creation button for edit tab --- app_paths.go | 28 +++++++ frontend/src/components/Packs.tsx | 110 ++++++++++++++++++++++---- frontend/src/wailsjs/go/main/App.d.ts | 14 ++++ frontend/src/wailsjs/go/main/App.js | 28 +++++++ iconpack.go | 6 +- main.go | 2 +- utils.go | 93 ++++++++++++++++++++++ 7 files changed, 263 insertions(+), 18 deletions(-) diff --git a/app_paths.go b/app_paths.go index 46d2126..a154239 100644 --- a/app_paths.go +++ b/app_paths.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + "github.com/google/uuid" "github.com/wailsapp/wails/v2/pkg/runtime" ) @@ -231,3 +232,30 @@ func (a *App) GetTempPngPath(id string) string { return "" } } + +func (a *App) AddTempPngPath(id string, path string) { + if !contains(allowedImageExtensionsPng, filepath.Ext(path)) { + runtime.LogError(appContext, "Extension is not allowed: "+path) + return + } + + oldPath, ok := tempPngPaths[id] + + tempPngPath := filepath.Join(tempFolder, "iconium-"+uuid.NewString()+".png") + err := ConvertToPng(path, tempPngPath) + + if err != nil { + runtime.LogErrorf(appContext, "Error converting to png: %s", err) + return + } + + if ok { + err = os.Remove(oldPath) + if err != nil { + runtime.LogErrorf(appContext, "Error removing old temp png: %s", err) + return + } + } + + tempPngPaths[id] = tempPngPath +} diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index d23e0d4..3ea8133 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -14,6 +14,7 @@ import { Loader2, Monitor, Pencil, + SquarePlus, Trash, Upload, } from "lucide-react"; @@ -34,9 +35,13 @@ import { AddFilesToIconPackFromDesktop, AddFilesToIconPackFromPath, AddIconPack, + AddTempPngPath, ApplyIconPack, ClearTempPngPaths, + CreateLastTab, DeleteIconPack, + Description, + Destination, Ext, GetFileInfoFromDesktop, GetFileInfoFromPaths, @@ -44,9 +49,14 @@ import { GetIconFiles, GetIconPack, GetIconPackList, + GetTempPngPath, + IconLocation, + Name, + ReadLastTab, SetIconPackField, SetIconPackFiles, SetIconPackMetadata, + UUID, } from "@/wailsjs/go/main/App"; import { main } from "@/wailsjs/go/models"; import { @@ -110,6 +120,20 @@ export default function Packs() { reloadSelectedPack(); }, [selectedPackId]); + useEffect(() => { + if (!editingIconPack && selectedPackId !== "") { + CreateLastTab(selectedPackId).then(() => { + window.location.reload(); + }); + } + }, [editingIconPack]); + + useEffect(() => { + ReadLastTab().then((packId) => { + setSelectedPackId(packId); + }); + }, []); + return ( ) : ( @@ -708,17 +730,10 @@ function PackContent({ interface PackEditProps { iconPackId: string; - setSelectedPackId: (packId: string) => void; - loadIconPacks: () => void; setEditingIconPack: (editingIconPack: boolean) => void; } -function PackEdit({ - iconPackId, - setSelectedPackId: setPack, - loadIconPacks: loadPackInfo, - setEditingIconPack, -}: PackEditProps) { +function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { const { t } = useTranslation(); const [loading, setLoading] = useState(true); @@ -772,6 +787,32 @@ function PackEdit({ }); }; + const handleAddEmptyIcon = () => { + setAddIconsRunning(true); + + UUID() + .then((uuid) => { + const oldFiles = files || []; + oldFiles.push( + main.FileInfo.createFrom({ + id: uuid, + name: "New file", + description: "", + path: "", + destinationPath: "", + extension: "", + hasIcon: false, + iconId: "", + }) + ); + console.log(oldFiles); + setFiles(oldFiles); + }) + .finally(() => { + setAddIconsRunning(false); + }); + }; + const handleInputChange = (index: number, field: string, value: string) => { setFiles((prevFiles) => prevFiles?.map((file, i) => @@ -806,10 +847,6 @@ function PackEdit({ return (
- -
@@ -878,7 +924,9 @@ function PackEdit({ return newArray; }); }} + key={updateArray[index]} editable + alwaysShowOriginal={false} />
{ handleInputChange(index, "extension", ext); + console.log(ext); + }); + if (file.name === "" || file.name === "New file") { + Name(value).then((name) => { + handleInputChange(index, "name", name); + }); + } + if (file.description === "") { + Description(value).then((description) => { + console.log(description); + handleInputChange(index, "description", description); + }); + } + if (file.destinationPath === "") { + Destination(value).then((destinationPath) => { + handleInputChange( + index, + "destinationPath", + destinationPath + ); + }); + } + GetTempPngPath(file.id).then((tempPngPath) => { + if (!tempPngPath && !file.hasIcon) { + IconLocation(value).then((iconLocation) => { + AddTempPngPath(file.id, iconLocation).then(() => { + setUpdateArray((prevUpdateArray) => { + const newArray = [...prevUpdateArray]; + newArray[index] = prevUpdateArray[index] + 1; + return newArray; + }); + }); + }); + } }); }} label={"Path"} diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index 4acc48c..f15df82 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -12,14 +12,22 @@ export function AddFilesToIconPackFromPath(arg1:string,arg2:Array,arg3:b export function AddIconPack(arg1:string,arg2:string,arg3:string):Promise; +export function AddTempPngPath(arg1:string,arg2:string):Promise; + export function ApplyIconPack(arg1:string):Promise; export function CheckForUpdate():Promise; export function ClearTempPngPaths():Promise; +export function CreateLastTab(arg1:string):Promise; + export function DeleteIconPack(arg1:string,arg2:boolean):Promise; +export function Description(arg1:string):Promise; + +export function Destination(arg1:string):Promise; + export function Ext(arg1:string):Promise; export function GetBase64Png():Promise; @@ -52,12 +60,18 @@ export function GetTempPngPath(arg1:string):Promise; export function GetVersion():Promise; +export function IconLocation(arg1:string):Promise; + +export function Name(arg1:string):Promise; + export function NeedsAdminPrivileges():Promise; export function OpenFileInExplorer(arg1:string):Promise; export function ReadConfig(arg1:string):Promise; +export function ReadLastTab():Promise; + export function RestartApplication(arg1:boolean,arg2:Array):Promise; export function SaveConfigDialog():Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index a95077c..ddd00c9 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -22,6 +22,10 @@ export function AddIconPack(arg1, arg2, arg3) { return window['go']['main']['App']['AddIconPack'](arg1, arg2, arg3); } +export function AddTempPngPath(arg1, arg2) { + return window['go']['main']['App']['AddTempPngPath'](arg1, arg2); +} + export function ApplyIconPack(arg1) { return window['go']['main']['App']['ApplyIconPack'](arg1); } @@ -34,10 +38,22 @@ export function ClearTempPngPaths() { return window['go']['main']['App']['ClearTempPngPaths'](); } +export function CreateLastTab(arg1) { + return window['go']['main']['App']['CreateLastTab'](arg1); +} + export function DeleteIconPack(arg1, arg2) { return window['go']['main']['App']['DeleteIconPack'](arg1, arg2); } +export function Description(arg1) { + return window['go']['main']['App']['Description'](arg1); +} + +export function Destination(arg1) { + return window['go']['main']['App']['Destination'](arg1); +} + export function Ext(arg1) { return window['go']['main']['App']['Ext'](arg1); } @@ -102,6 +118,14 @@ export function GetVersion() { return window['go']['main']['App']['GetVersion'](); } +export function IconLocation(arg1) { + return window['go']['main']['App']['IconLocation'](arg1); +} + +export function Name(arg1) { + return window['go']['main']['App']['Name'](arg1); +} + export function NeedsAdminPrivileges() { return window['go']['main']['App']['NeedsAdminPrivileges'](); } @@ -114,6 +138,10 @@ export function ReadConfig(arg1) { return window['go']['main']['App']['ReadConfig'](arg1); } +export function ReadLastTab() { + return window['go']['main']['App']['ReadLastTab'](); +} + export function RestartApplication(arg1, arg2) { return window['go']['main']['App']['RestartApplication'](arg1, arg2); } diff --git a/iconpack.go b/iconpack.go index 33fb6dc..6740fa5 100644 --- a/iconpack.go +++ b/iconpack.go @@ -276,9 +276,7 @@ func (a *App) SetIconPackFiles(packId string, files []FileInfo) { return } - for _, file := range files { - file.HasIcon = false - + for i, file := range files { tempPngPath, ok := tempPngPaths[file.Id] if ok { runtime.LogDebugf(appContext, "Attempting to copy temp png: %s", tempPngPath) @@ -300,6 +298,8 @@ func (a *App) SetIconPackFiles(packId string, files []FileInfo) { file.HasIcon = true } + + files[i] = file } // Update cache diff --git a/main.go b/main.go index 3ed1703..55577ec 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ func NewFileLoader() *FileLoader { func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) { var err error - requestedFilename := strings.TrimPrefix(filepath.Join("C:\\Users\\bedoy\\AppData\\Roaming\\iconium", req.URL.Path), "/") + requestedFilename := strings.TrimPrefix(filepath.Join(appFolder, req.URL.Path), "/") println("Requesting file:", requestedFilename) fileData, err := os.ReadFile(requestedFilename) if err != nil { diff --git a/utils.go b/utils.go index 06bd030..16850ca 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package main import ( "encoding/base64" "encoding/json" + "errors" "net/http" "os" "path/filepath" @@ -193,5 +194,97 @@ func (a *App) UUID() string { } func (a *App) Ext(path string) string { + path = ConvertToFullPath(path) + if path == "" { + return "" + } return filepath.Ext(path) } + +func (a *App) Name(path string) string { + path = ConvertToFullPath(path) + if path == "" { + return "" + } + base := filepath.Base(path) + return strings.TrimSuffix(base, filepath.Ext(base)) +} + +func (a *App) Description(path string) string { + path = ConvertToFullPath(path) + if path == "" { + return "" + } + link, err := lnk.File(path) + if err != nil { + return "" + } + + return link.StringData.NameString +} + +func (a *App) Destination(path string) string { + path = ConvertToFullPath(path) + if path == "" { + return "" + } + link, err := lnk.File(path) + if err != nil { + return "" + } + + var destination string + + if link.LinkInfo.LocalBasePath != "" { + destination = link.LinkInfo.LocalBasePath + } + if link.LinkInfo.LocalBasePathUnicode != "" { + destination = link.LinkInfo.LocalBasePathUnicode + } + + runtime.LogDebugf(appContext, "Destination: %s", destination) + + return ConvertToGeneralPath(destination) +} + +func (a *App) IconLocation(path string) string { + path = ConvertToFullPath(path) + if path == "" { + return "" + } + link, err := lnk.File(path) + if err != nil { + return "" + } + + return link.StringData.IconLocation +} + +func (a *App) CreateLastTab(path string) { + tempFilePath := filepath.Join(tempFolder, "iconium-last-tab.txt") + + if err := os.WriteFile(tempFilePath, []byte(path), 0o644); err != nil { + runtime.LogErrorf(appContext, "Error writing last tab path: %s", err) + } +} + +func (a *App) ReadLastTab() string { + tempFilePath := filepath.Join(tempFolder, "iconium-last-tab.txt") + + if _, err := os.Stat(tempFilePath); errors.Is(err, os.ErrNotExist) { + return "" + } + + content, err := os.ReadFile(tempFilePath) + if err != nil { + runtime.LogErrorf(appContext, "Error reading last tab path: %s", err) + return "" + } + + // Delete the temp file + if err := os.Remove(tempFilePath); err != nil { + runtime.LogErrorf(appContext, "Error removing temp file: %s", err) + } + + return string(content) +} From 51b90515c2480488ffb813143d662f7102070395 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Mon, 19 Aug 2024 18:02:53 +0300 Subject: [PATCH 140/221] modify edit page skeleton --- frontend/src/components/Packs.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 3ea8133..3e42afd 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -836,11 +836,18 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { if (loading || files === undefined) { return ( -
- - - - +
+ + + + + + + + + + +
); } From d133ba6e37b8aa92672dc1d93060d992263a6560 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Mon, 19 Aug 2024 18:27:20 +0300 Subject: [PATCH 141/221] implement icon deletion --- app_paths.go | 14 ++++++++ frontend/src/components/Packs.tsx | 23 ++++++++++--- frontend/src/components/ui/accordion.tsx | 44 +++++++++++++++--------- frontend/src/wailsjs/go/main/App.d.ts | 4 +++ frontend/src/wailsjs/go/main/App.js | 8 +++++ iconpack.go | 7 ++++ 6 files changed, 80 insertions(+), 20 deletions(-) diff --git a/app_paths.go b/app_paths.go index a154239..d5a394a 100644 --- a/app_paths.go +++ b/app_paths.go @@ -34,6 +34,7 @@ var installationDirectory string var imageMagickPath string var tempPngPaths map[string]string = map[string]string{} +var deletePngPaths map[string]([]string) = map[string]([]string){} func path_init() error { appData, err := os.UserConfigDir() @@ -259,3 +260,16 @@ func (a *App) AddTempPngPath(id string, path string) { tempPngPaths[id] = tempPngPath } + +func (a *App) AddDeletePngPath(packId, fileId string) { + paths := deletePngPaths[packId] + path := filepath.Join(packsFolder, packId, "icons", fileId+".png") + paths = append(paths, path) + deletePngPaths[packId] = paths +} + +func (a *App) ClearDeletePngPaths() { + for k := range deletePngPaths { + delete(deletePngPaths, k) + } +} diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 3e42afd..ed96abd 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -12,6 +12,7 @@ import { Edit, FolderOpen, Loader2, + LucideTrash, Monitor, Pencil, SquarePlus, @@ -32,11 +33,13 @@ import { } from "@/components/ui/dialog"; import SelectImage from "./SelectImage"; import { + AddDeletePngPath, AddFilesToIconPackFromDesktop, AddFilesToIconPackFromPath, AddIconPack, AddTempPngPath, ApplyIconPack, + ClearDeletePngPaths, ClearTempPngPaths, CreateLastTab, DeleteIconPack, @@ -389,6 +392,7 @@ function PackContent({ const handleEditCancel = () => { setEditingMetadata(false); ClearTempPngPaths(); + ClearDeletePngPaths(); }; const handleDelete = () => { @@ -805,7 +809,6 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { iconId: "", }) ); - console.log(oldFiles); setFiles(oldFiles); }) .finally(() => { @@ -813,6 +816,12 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { }); }; + const handleRemoveIcon = (index: number) => { + if (!files) return; + setFiles((prevFiles) => prevFiles?.filter((_, i) => i !== index)); + AddDeletePngPath(iconPackId, files[index].id); + } + const handleInputChange = (index: number, field: string, value: string) => { setFiles((prevFiles) => prevFiles?.map((file, i) => @@ -900,10 +909,17 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) {
- + {files.map((file, index) => ( - + { + e.stopPropagation() + handleRemoveIcon(index) + }}> + + + }>
- {file.name}
diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx index b897488..98e2d45 100644 --- a/frontend/src/components/ui/accordion.tsx +++ b/frontend/src/components/ui/accordion.tsx @@ -1,10 +1,10 @@ -import * as React from "react" -import * as AccordionPrimitive from "@radix-ui/react-accordion" -import { ChevronDown } from "lucide-react" +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Accordion = AccordionPrimitive.Root +const Accordion = AccordionPrimitive.Root; const AccordionItem = React.forwardRef< React.ElementRef, @@ -15,28 +15,40 @@ const AccordionItem = React.forwardRef< className={cn("border-b", className)} {...props} /> -)) -AccordionItem.displayName = "AccordionItem" +)); +AccordionItem.displayName = "AccordionItem"; + +// Define an interface for AccordionTrigger props +interface AccordionTriggerProps + extends React.ComponentPropsWithoutRef { + end?: React.ReactNode; // Add the 'end' prop +} const AccordionTrigger = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( + AccordionTriggerProps +>(({ className, children, end, ...props }, ref) => ( #chevron]:rotate-180", + "flex flex-1 items-center justify-between font-medium transition-all hover:underline [&[data-state=open]>div>#chevron]:rotate-180", className )} {...props} > {children} - +
+ {end} + +
-)) -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; const AccordionContent = React.forwardRef< React.ElementRef, @@ -49,8 +61,8 @@ const AccordionContent = React.forwardRef< >
{children}
-)) +)); -AccordionContent.displayName = AccordionPrimitive.Content.displayName +AccordionContent.displayName = AccordionPrimitive.Content.displayName; -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index f15df82..db1f65c 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -2,6 +2,8 @@ // This file is automatically generated. DO NOT EDIT import {main} from '../models'; +export function AddDeletePngPath(arg1:string,arg2:string):Promise; + export function AddFileToIconPackFromPath(arg1:string,arg2:string,arg3:boolean):Promise; export function AddFilesToIconPackFromDesktop(arg1:string):Promise; @@ -18,6 +20,8 @@ export function ApplyIconPack(arg1:string):Promise; export function CheckForUpdate():Promise; +export function ClearDeletePngPaths():Promise; + export function ClearTempPngPaths():Promise; export function CreateLastTab(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index ddd00c9..4454778 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -2,6 +2,10 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT +export function AddDeletePngPath(arg1, arg2) { + return window['go']['main']['App']['AddDeletePngPath'](arg1, arg2); +} + export function AddFileToIconPackFromPath(arg1, arg2, arg3) { return window['go']['main']['App']['AddFileToIconPackFromPath'](arg1, arg2, arg3); } @@ -34,6 +38,10 @@ export function CheckForUpdate() { return window['go']['main']['App']['CheckForUpdate'](); } +export function ClearDeletePngPaths() { + return window['go']['main']['App']['ClearDeletePngPaths'](); +} + export function ClearTempPngPaths() { return window['go']['main']['App']['ClearTempPngPaths'](); } diff --git a/iconpack.go b/iconpack.go index 6740fa5..ef942f5 100644 --- a/iconpack.go +++ b/iconpack.go @@ -302,6 +302,13 @@ func (a *App) SetIconPackFiles(packId string, files []FileInfo) { files[i] = file } + // Delete the unused icons + paths := deletePngPaths[packId] + for _, path := range paths { + os.Remove(path) + } + a.ClearDeletePngPaths() + // Update cache iconPack.Files = files a.SetIconPack(iconPack) From e942256a563c23b40f1cf267181aa7acb8f2ed89 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Mon, 19 Aug 2024 20:10:46 +0300 Subject: [PATCH 142/221] generate nonexistent iconpack files --- iconpack.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/iconpack.go b/iconpack.go index ef942f5..6cb2cf5 100644 --- a/iconpack.go +++ b/iconpack.go @@ -64,10 +64,13 @@ func CreateIconPack(name string, version string, author string) (IconPack, error iconPack.Settings.CornerRadius = 0 iconPack.Settings.Opacity = 100 - err := WriteIconPack(iconPack) + var err error - if err != nil { - return IconPack{}, err + if name != "" { + err = WriteIconPack(iconPack) + if err != nil { + return IconPack{}, err + } } return iconPack, err @@ -116,15 +119,26 @@ func ReadIconPack(id string) (IconPack, error) { iconPack := IconPack{} + defaultPack, err := CreateIconPack("", "", "") + if err != nil { + runtime.LogWarningf(appContext, "Failed to create default icon pack: %s", err.Error()) + return IconPack{}, err + } + // Read metadata and files from their respective JSON files if err := readJSON(metadataPath, &iconPack.Metadata); err != nil { + runtime.LogErrorf(appContext, "Failed to read icon pack metadata: %s", err.Error()) return IconPack{}, err } if err := readJSON(settingsPath, &iconPack.Settings); err != nil { - return IconPack{}, err + runtime.LogWarningf(appContext, "Failed to read icon pack settings: %s", err.Error()) + iconPack.Settings = defaultPack.Settings + writeJSON(settingsPath, iconPack.Settings) } if err := readJSON(filesPath, &iconPack.Files); err != nil { - return IconPack{}, err + runtime.LogWarningf(appContext, "Failed to read icon pack files: %s", err.Error()) + iconPack.Files = []FileInfo{} + writeJSON(filesPath, iconPack.Files) } iconPackCache[id] = iconPack From e11ae02bad9a7d36a8d9ea1b49532cd9d7b7fddd Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 20 Aug 2024 00:16:49 +0300 Subject: [PATCH 143/221] add new buttons to top of the packs --- frontend/public/locales/en-US.json | 1 - frontend/public/locales/tr-TR.json | 1 - frontend/src/components/Packs.tsx | 171 ++++++++++++++++++-------- frontend/src/wailsjs/go/main/App.d.ts | 2 + frontend/src/wailsjs/go/main/App.js | 4 + iconpack.go | 6 + 6 files changed, 135 insertions(+), 50 deletions(-) diff --git a/frontend/public/locales/en-US.json b/frontend/public/locales/en-US.json index 9ee5f82..5b66c39 100644 --- a/frontend/public/locales/en-US.json +++ b/frontend/public/locales/en-US.json @@ -19,7 +19,6 @@ "my_packs": { "create_new_pack": { - "label": "Create New Pack", "title": "Create Icon Pack" }, diff --git a/frontend/public/locales/tr-TR.json b/frontend/public/locales/tr-TR.json index 2f578e2..5ae5513 100644 --- a/frontend/public/locales/tr-TR.json +++ b/frontend/public/locales/tr-TR.json @@ -19,7 +19,6 @@ "my_packs": { "create_new_pack": { - "label": "Yeni Paket Oluştur", "title": "İkon Paketi Oluştur" }, diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index ed96abd..3b8a18a 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -9,15 +9,19 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { + Download, Edit, FolderOpen, Loader2, LucideTrash, Monitor, Pencil, + Plus, + RefreshCw, SquarePlus, Trash, Upload, + UploadIcon, } from "lucide-react"; import { AreYouSureDialog, @@ -40,6 +44,7 @@ import { AddTempPngPath, ApplyIconPack, ClearDeletePngPaths, + ClearIconPackCache, ClearTempPngPaths, CreateLastTab, DeleteIconPack, @@ -104,6 +109,8 @@ export default function Packs() { const [iconPacks, setIconPacks] = useState(); const [selectedPackKeyCount, setSelectedPackKeyCount] = useState(0); + const [reloadingIconPacks, setReloadingIconPacks] = useState(false); + const dialogCloseRef = useRef(null); const loadIconPacks = async () => { @@ -115,6 +122,26 @@ export default function Packs() { setSelectedPackKeyCount(selectedPackKeyCount + 1); }; + const handleReloadIconPacks = async () => { + setReloadingIconPacks(true); + + // Start the timer for at least 250ms + const spinMinDuration = new Promise((resolve) => setTimeout(resolve, 250)); + + // Clear icon cache and reload packs + const reloadJob = ClearIconPackCache().then(() => { + return loadIconPacks().then(() => { + reloadSelectedPack(); + }); + }); + + // Wait for both the spin duration and the job to complete + await Promise.all([spinMinDuration, reloadJob]); + + // Stop the spin animation + setReloadingIconPacks(false); + }; + useEffect(() => { loadIconPacks(); }, []); @@ -139,48 +166,85 @@ export default function Packs() { return ( - - {iconPacks?.map((pack) => ( - - ))} - +
{!editingIconPack && ( - - - + + + + + + {t("my_packs.create_new_pack.title")} + + + + + + - - - - - {t("my_packs.create_new_pack.title")} - - - -
+
+
+ + +
+
)} - + + {iconPacks?.map((pack) => ( + + ))} + +
{selectedPackId && (editingIconPack ? ( @@ -209,6 +273,7 @@ interface PackTriggerProps { reloadSelectedPack: () => void; editingIconPack: boolean; disabled?: boolean; + loadIconPacks: () => void; } function PackTrigger({ @@ -218,6 +283,7 @@ function PackTrigger({ reloadSelectedPack, editingIconPack, disabled, + loadIconPacks, ...props }: PackTriggerProps) { const [iconPack, setIconPack] = useState(); @@ -234,6 +300,7 @@ function PackTrigger({ SetIconPackField(packId, "settings.json", "enabled", !enabledState).then( () => { setEnabledState(!enabledState); + loadIconPacks(); if (packId === selectedPackId) { reloadSelectedPack(); } @@ -820,7 +887,7 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { if (!files) return; setFiles((prevFiles) => prevFiles?.filter((_, i) => i !== index)); AddDeletePngPath(iconPackId, files[index].id); - } + }; const handleInputChange = (index: number, field: string, value: string) => { setFiles((prevFiles) => @@ -912,14 +979,22 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { {files.map((file, index) => ( - { - e.stopPropagation() - handleRemoveIcon(index) - }}> - - - }> + { + e.stopPropagation(); + handleRemoveIcon(index); + }} + > + + + } + >
; export function ClearDeletePngPaths():Promise; +export function ClearIconPackCache():Promise; + export function ClearTempPngPaths():Promise; export function CreateLastTab(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 4454778..1458cfd 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -42,6 +42,10 @@ export function ClearDeletePngPaths() { return window['go']['main']['App']['ClearDeletePngPaths'](); } +export function ClearIconPackCache() { + return window['go']['main']['App']['ClearIconPackCache'](); +} + export function ClearTempPngPaths() { return window['go']['main']['App']['ClearTempPngPaths'](); } diff --git a/iconpack.go b/iconpack.go index 6cb2cf5..4670b48 100644 --- a/iconpack.go +++ b/iconpack.go @@ -355,6 +355,12 @@ func CacheIconPacks() error { return nil } +func (a *App) ClearIconPackCache() { + for k := range iconPackCache { + delete(iconPackCache, k) + } +} + func (a *App) GetIconPackList() []IconPack { if len(iconPackCache) == 0 { CacheIconPacks() From 502d7ee852744859d39f0bb531f53b88d28c58ae Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 20 Aug 2024 00:23:37 +0300 Subject: [PATCH 144/221] fix update spinner animation --- .../components/SettingItems/UpdateSetting.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/SettingItems/UpdateSetting.tsx b/frontend/src/components/SettingItems/UpdateSetting.tsx index d9af4cc..f74c662 100644 --- a/frontend/src/components/SettingItems/UpdateSetting.tsx +++ b/frontend/src/components/SettingItems/UpdateSetting.tsx @@ -49,12 +49,20 @@ export function UpdateSetting() { }, [getValue]); const handleCheckForUpdate = () => { - // Check for updates and update state accordingly if (!isUpdating) { + // Start the spinner and set a minimum duration for the animation setIsChecking(true); - CheckForUpdate() - .then(setUpdateInfo) - .finally(() => setTimeout(() => setIsChecking(false), 200)); + const spinMinDuration = new Promise((resolve) => + setTimeout(resolve, 500) + ); + + // Check for updates and update state accordingly + const updateJob = CheckForUpdate().then(setUpdateInfo); + + // Wait for both the minimum spin duration and the update check to complete + Promise.all([spinMinDuration, updateJob]).finally(() => + setIsChecking(false) + ); } }; From 9722f2d86836f7ddee9072ec7d052f9b3aa631b9 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 20 Aug 2024 00:57:25 +0300 Subject: [PATCH 145/221] support more image types --- dialog.go | 6 +++--- image.go | 53 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/dialog.go b/dialog.go index a5a7826..843ba8e 100644 --- a/dialog.go +++ b/dialog.go @@ -104,8 +104,8 @@ func (a *App) GetTempPng(id string) string { Title: "Select image", Filters: []runtime.FileFilter{ { - DisplayName: "PNG", - Pattern: "*.png", + DisplayName: "Image File", + Pattern: "*.ico;*.png;*.jpg;*.jpeg;*.bmp;*.webp;*.svg", }, }, }) @@ -118,7 +118,7 @@ func (a *App) GetTempPng(id string) string { return "" } - err = copy_file(path, tempPackPngPath) + err = ConvertToPng(path, tempPackPngPath) if err != nil { runtime.LogErrorf(a.ctx, "Error copying pack.png file: %s", err.Error()) return "" diff --git a/image.go b/image.go index d425b95..8d35205 100644 --- a/image.go +++ b/image.go @@ -11,7 +11,7 @@ import ( "github.com/google/uuid" ) -var allowedImageExtensionsPng = []string{".ico"} +var allowedImageExtensionsPng = []string{".ico", ".png", ".jpg", ".jpeg", ".bmp", ".webp", ".svg"} var allowedImageExtensionsIco = []string{".png", ".jpg", ".jpeg"} @@ -61,7 +61,7 @@ func ConvertToIco(path string, destination string, settings IconPackSettings) er extension := filepath.Ext(path) // Check if the input file has a valid image extension - if !contains([]string{".png", ".jpg", ".jpeg", ".bmp", ".gif"}, extension) { + if extension != ".png" { return fmt.Errorf("invalid image extension: %s", extension) } @@ -106,39 +106,52 @@ func ConvertToPng(path string, destination string) error { defer os.RemoveAll(tempIconFolder) tempIconPath := filepath.Join(tempIconFolder, "icon.png") + if extension != ".ico" { + tempIconPath = destination + } + // Build magick command arguments args := []string{imageMagickPath, path, "-alpha", "on", "-background", "none", tempIconPath} + if extension != ".ico" { + args = []string{imageMagickPath, path, "-alpha", "on", "-background", "none", "-resize", "256x256\\!", tempIconPath} + } + // Execute magick command silently _, err = sendCommand(args...) if err != nil { return fmt.Errorf("error converting image: %w", err) } - // Delete the icons other than the largest one - files, err := os.ReadDir(tempIconFolder) - if err != nil { - return err - } - - largestWidth := -1 - var largestFile string - for _, file := range files { - width, err := GetImageWidth(filepath.Join(tempIconFolder, file.Name())) + if extension == ".ico" { + files, err := os.ReadDir(tempIconFolder) if err != nil { return err } - if width > largestWidth { - largestWidth = width - largestFile = file.Name() + largestFile := "icon.png" + + // Delete the icons other than the largest one + if len(files) > 1 { + largestWidth := -1 + for _, file := range files { + width, err := GetImageWidth(filepath.Join(tempIconFolder, file.Name())) + if err != nil { + return err + } + + if width > largestWidth { + largestWidth = width + largestFile = file.Name() + } + } } - } - // Move the largest icon to the destination - err = os.Rename(filepath.Join(tempIconFolder, largestFile), destination) - if err != nil { - return err + // Move the largest icon to the destination + err = os.Rename(filepath.Join(tempIconFolder, largestFile), destination) + if err != nil { + return err + } } return nil From 6763c100f8d481151f6aab51367e778a84cbfcd5 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 20 Aug 2024 01:07:16 +0300 Subject: [PATCH 146/221] reorder allowed image extensions --- dialog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dialog.go b/dialog.go index 843ba8e..df20959 100644 --- a/dialog.go +++ b/dialog.go @@ -105,7 +105,7 @@ func (a *App) GetTempPng(id string) string { Filters: []runtime.FileFilter{ { DisplayName: "Image File", - Pattern: "*.ico;*.png;*.jpg;*.jpeg;*.bmp;*.webp;*.svg", + Pattern: "*.png;*.jpg;*.jpeg;*.webp;*.svg;*.bmp;*.ico", }, }, }) From af52d2c8f3a37633e7375484c59dff8b56b6d46c Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 20 Aug 2024 16:15:03 +0300 Subject: [PATCH 147/221] fix tablist overflow --- frontend/src/components/Packs.tsx | 136 ++++++++++++++++++------------ 1 file changed, 80 insertions(+), 56 deletions(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 3b8a18a..577f794 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -113,6 +113,19 @@ export default function Packs() { const dialogCloseRef = useRef(null); + const tabsListRef = useRef(null); + const [hasOverflow, setHasOverflow] = useState(false); + useEffect(() => { + const element = tabsListRef.current as HTMLElement | null; + if (!element) { + return; + } + + const overflow = element.scrollHeight > element.clientHeight; + LogDebug(element.scrollHeight + " > " + element.clientHeight + " = " + overflow); + setHasOverflow(overflow); + }, [editingIconPack]); + const loadIconPacks = async () => { const packs = await GetIconPackList(); setIconPacks(packs); @@ -167,66 +180,67 @@ export default function Packs() { return (
- {!editingIconPack && ( -
-
- - - - - - - - - {t("my_packs.create_new_pack.title")} - - - - - - + + + + + + {t("my_packs.create_new_pack.title")} + + + - -
-
- - -
+ + +
- )} +
+ +
+
+ {iconPacks?.map((pack) => ( {t("my_packs.card.pack_actions.edit_icon_pack")} + +
From 341d7d88a09c7c688a004e237cf0bc2e9ef1cb72 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 20 Aug 2024 18:13:04 +0300 Subject: [PATCH 148/221] add import/export logic --- dialog.go | 91 +++++++++++++++++++ frontend/src/components/Packs.tsx | 27 +++++- frontend/src/wailsjs/go/main/App.d.ts | 4 + frontend/src/wailsjs/go/main/App.js | 8 ++ iconpack.go | 6 ++ utils.go | 125 ++++++++++++++++++++++++++ 6 files changed, 258 insertions(+), 3 deletions(-) diff --git a/dialog.go b/dialog.go index df20959..8407555 100644 --- a/dialog.go +++ b/dialog.go @@ -214,6 +214,97 @@ func (a *App) GetFilePath(generalPath string) string { return ConvertToGeneralPath(path) } +func (a *App) ExportIconPack(packId string) string { + // Check if the icon pack exists + _, err := a.GetIconPack(packId) + if err != nil { + runtime.LogErrorf(appContext, "Icon pack %s not found: %s", packId, err.Error()) + return "" + } + + path, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ + Title: "Export icon pack", + CanCreateDirectories: true, + Filters: []runtime.FileFilter{ + { + DisplayName: "Iconium File", + Pattern: "*.icnm", + }, + }, + }) + if err != nil { + runtime.LogWarning(a.ctx, err.Error()) + return "" + } + + iconPackPath := filepath.Join(packsFolder, packId) + runtime.LogInfof(a.ctx, "Exporting icon pack: %s", iconPackPath) + + err = zip_folder(iconPackPath, path) + + if err != nil { + runtime.LogErrorf(a.ctx, "Error exporting icon pack: %s", err.Error()) + return "" + } + + a.SendNotification("settings.icon_pack.exported", "", path, "success") + + return path +} + +func (a *App) ImportIconPack() string { + path, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "Import icon pack", + CanCreateDirectories: true, + Filters: []runtime.FileFilter{ + { + DisplayName: "Iconium File", + Pattern: "*.icnm", + }, + }, + }) + if err != nil { + runtime.LogWarning(a.ctx, err.Error()) + return "" + } + + runtime.LogInfof(a.ctx, "Importing icon pack: %s", path) + + extractFolder := filepath.Join(tempFolder, "iconium-"+uuid.NewString()) + defer os.RemoveAll(extractFolder) + + err = unzip_folder(path, extractFolder) + if err != nil { + runtime.LogErrorf(a.ctx, "Error importing icon pack: %s", err.Error()) + return "" + } + + files, err := os.ReadDir(extractFolder) + if err != nil { + runtime.LogErrorf(a.ctx, "Error importing icon pack: %s", err.Error()) + return "" + } + if len(files) != 1 { + runtime.LogErrorf(a.ctx, "Error importing icon pack") + return "" + } + + packId := uuid.NewString() + + tempIconPackPath := filepath.Join(extractFolder, files[0].Name()) + targetPath := filepath.Join(packsFolder, packId) + + err = os.Rename(tempIconPackPath, targetPath) + if err != nil { + runtime.LogErrorf(a.ctx, "Error importing icon pack: %s", err.Error()) + return "" + } + + a.SendNotification("settings.icon_pack.imported", "", "", "success") + + return packId +} + func (a *App) OpenFileInExplorer(path string) { runtime.LogInfo(a.ctx, "Opening file in explorer: "+path) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 577f794..94f80c0 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -50,6 +50,7 @@ import { DeleteIconPack, Description, Destination, + ExportIconPack, Ext, GetFileInfoFromDesktop, GetFileInfoFromPaths, @@ -59,6 +60,7 @@ import { GetIconPackList, GetTempPngPath, IconLocation, + ImportIconPack, Name, ReadLastTab, SetIconPackField, @@ -122,7 +124,9 @@ export default function Packs() { } const overflow = element.scrollHeight > element.clientHeight; - LogDebug(element.scrollHeight + " > " + element.clientHeight + " = " + overflow); + LogDebug( + element.scrollHeight + " > " + element.clientHeight + " = " + overflow + ); setHasOverflow(overflow); }, [editingIconPack]); @@ -155,6 +159,14 @@ export default function Packs() { setReloadingIconPacks(false); }; + const handleImportIconPack = () => { + ImportIconPack().then((id) => { + handleReloadIconPacks().then(() => { + setSelectedPackId(id); + }); + }); + }; + useEffect(() => { loadIconPacks(); }, []); @@ -226,6 +238,7 @@ export default function Packs() {
); }; diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index 3fbb8ce..7d2f2cb 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -26,6 +26,8 @@ export function ClearDeletePngPaths():Promise; export function ClearIconPackCache():Promise; +export function ClearSelectImages():Promise; + export function ClearTempPngPaths():Promise; export function CreateLastTab(arg1:string):Promise; @@ -64,6 +66,8 @@ export function GetIconPackList():Promise>; export function GetLoadConfigPath():Promise; +export function GetSelectImage(arg1:string,arg2:string):Promise; + export function GetTempPng(arg1:string):Promise; export function GetTempPngPath(arg1:string):Promise; @@ -105,3 +109,5 @@ export function UUID():Promise; export function Update(arg1:string):Promise; export function UpdateAsAdmin(arg1:string):Promise; + +export function UploadSelectImage(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index dc3fb13..9d91c19 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -50,6 +50,10 @@ export function ClearIconPackCache() { return window['go']['main']['App']['ClearIconPackCache'](); } +export function ClearSelectImages() { + return window['go']['main']['App']['ClearSelectImages'](); +} + export function ClearTempPngPaths() { return window['go']['main']['App']['ClearTempPngPaths'](); } @@ -126,6 +130,10 @@ export function GetLoadConfigPath() { return window['go']['main']['App']['GetLoadConfigPath'](); } +export function GetSelectImage(arg1, arg2) { + return window['go']['main']['App']['GetSelectImage'](arg1, arg2); +} + export function GetTempPng(arg1) { return window['go']['main']['App']['GetTempPng'](arg1); } @@ -209,3 +217,7 @@ export function Update(arg1) { export function UpdateAsAdmin(arg1) { return window['go']['main']['App']['UpdateAsAdmin'](arg1); } + +export function UploadSelectImage(arg1) { + return window['go']['main']['App']['UploadSelectImage'](arg1); +} diff --git a/frontend/src/wailsjs/go/models.ts b/frontend/src/wailsjs/go/models.ts index adff52a..ded2015 100644 --- a/frontend/src/wailsjs/go/models.ts +++ b/frontend/src/wailsjs/go/models.ts @@ -156,6 +156,26 @@ export namespace main { } + export class SelectImage { + id: string; + path: string; + tempPath: string; + isEmpty: boolean; + isOriginal: boolean; + + static createFrom(source: any = {}) { + return new SelectImage(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.path = source["path"]; + this.tempPath = source["tempPath"]; + this.isEmpty = source["isEmpty"]; + this.isOriginal = source["isOriginal"]; + } + } export class UpdateInfo { updateAvailable: boolean; currentVersion: string; diff --git a/go.mod b/go.mod index 15bcbd5..da1e145 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4 github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 github.com/minio/selfupdate v0.6.0 + github.com/orcaman/concurrent-map/v2 v2.0.1 github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff github.com/wailsapp/wails/v2 v2.9.1 ) diff --git a/go.sum b/go.sum index ffd7833..9e38e11 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/ github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c= +github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM= github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff h1:japdIZgV4tJIgn7NqUD7mAkLiPRsPK5LXVgjNwFtDA4= github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff/go.mod h1:A24WXUol4NXZlK8grjh/CsZnPlimfwaQFt5PQsqS27s= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= diff --git a/iconpack.go b/iconpack.go index c94bf56..a43e6f7 100644 --- a/iconpack.go +++ b/iconpack.go @@ -66,7 +66,7 @@ func CreateIconPack(name string, version string, author string) (IconPack, error var err error - if name != "" && version != "" && author != "" { + if name != "Unknown Pack" { err = WriteIconPack(iconPack) if err != nil { return IconPack{}, err From d0e18dedb615875b6be9aca4b250e5f37458e9ad Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Wed, 21 Aug 2024 23:25:48 +0300 Subject: [PATCH 154/221] implement image select actions --- app_paths.go | 144 +++++++++++++++++++----- frontend/src/components/Packs.tsx | 45 +++----- frontend/src/components/SelectImage.tsx | 80 ++++++++----- frontend/src/wailsjs/go/main/App.d.ts | 12 +- frontend/src/wailsjs/go/main/App.js | 24 +++- frontend/src/wailsjs/go/models.ts | 10 +- iconpack.go | 6 +- 7 files changed, 224 insertions(+), 97 deletions(-) diff --git a/app_paths.go b/app_paths.go index 6ac97af..f26cc2e 100644 --- a/app_paths.go +++ b/app_paths.go @@ -35,16 +35,17 @@ var installationDirectory string var imageMagickPath string var tempPngPaths map[string]string = map[string]string{} -var deletePngPaths map[string]([]string) = map[string]([]string){} +var deletePngPaths []string = []string{} var selectImages = cmap.New[SelectImage]() type SelectImage struct { - Id string `json:"id"` - Path string `json:"path"` - TempPath string `json:"tempPath"` - IsEmpty bool `json:"isEmpty"` - IsOriginal bool `json:"isOriginal"` + Id string `json:"id"` + Path string `json:"path"` + TempPath string `json:"tempPath"` + HasOriginal bool `json:"hasOriginal"` + HasTemp bool `json:"hasTemp"` + IsRemoved bool `json:"isRemoved"` } func path_init() error { @@ -272,25 +273,48 @@ func (a *App) AddTempPngPath(id string, path string) { tempPngPaths[id] = tempPngPath } -func (a *App) AddDeletePngPath(packId, fileId string) { - paths := deletePngPaths[packId] - path := filepath.Join(packsFolder, packId, "icons", fileId+".png") - paths = append(paths, path) - deletePngPaths[packId] = paths +func (a *App) RemoveTempPng(id string) { + err := os.Remove(tempPngPaths[id]) + if err != nil { + runtime.LogErrorf(appContext, "Error removing temp png: %s", err) + return + } + + delete(tempPngPaths, id) } -func (a *App) AddDeletePngRelativePath(packId, relPath string) { +func (a *App) AddDeletePngRelativePath(relPath string) { path := filepath.Join(appFolder, relPath) - paths := deletePngPaths[packId] - paths = append(paths, path) - deletePngPaths[packId] = paths + deletePngPaths = append(deletePngPaths, path) } func (a *App) ClearDeletePngPaths() { - for k := range deletePngPaths { - delete(deletePngPaths, k) + deletePngPaths = []string{} +} + +func (a *App) RemoveDeletePng(path string) { + paths := deletePngPaths + + for i := 0; i < len(paths); i++ { + if paths[i] == path { + paths = append(paths[:i], paths[i+1:]...) + break + } + } + + deletePngPaths = paths +} + +func (a *App) DeleteDeletePngPaths() { + for _, path := range deletePngPaths { + err := os.Remove(path) + if err != nil { + runtime.LogErrorf(appContext, "Error removing delete png: %s", err) + } } + + deletePngPaths = []string{} } func (a *App) GetSelectImage(id string, path string) SelectImage { @@ -299,18 +323,23 @@ func (a *App) GetSelectImage(id string, path string) SelectImage { return selectImage } + fullPath := filepath.Join(appFolder, path) + isEmpty := path == "" - if !isEmpty { - isEmpty = !exists(filepath.Join(appFolder, path)) + if filepath.Ext(path) != ".png" { + isEmpty = true + } else if !isEmpty { + isEmpty = !exists(fullPath) } selectImage = SelectImage{ - Id: id, - Path: path, - TempPath: "", - IsEmpty: isEmpty, - IsOriginal: !isEmpty, + Id: id, + Path: path, + TempPath: "", + HasOriginal: !isEmpty, + HasTemp: false, + IsRemoved: false, } selectImages.Set(id, selectImage) @@ -327,8 +356,71 @@ func (a *App) UploadSelectImage(id string) SelectImage { selectImage := a.GetSelectImage(id, "") selectImage.TempPath = tempPngPath - selectImage.IsEmpty = false - selectImage.IsOriginal = false + selectImage.HasTemp = true + selectImages.Set(id, selectImage) + + return selectImage +} + +func (a *App) SetSelectImage(id string, path string) { + selectImage := a.GetSelectImage(id, path) + + if !selectImage.HasTemp { + a.RemoveTempPng(id) + } + + a.AddTempPngPath(id, path) + + tempPngPath := a.GetTempPng(id) + + if tempPngPath != "" { + selectImage.TempPath, _ = filepath.Rel(appFolder, tempPngPath) + selectImage.HasTemp = true + } else { + selectImage.TempPath = "" + selectImage.HasTemp = false + } + + selectImages.Set(id, selectImage) + + runtime.LogDebugf(appContext, "Set select image: %s", path) +} + +func (a *App) ActionSelectImage(id string) SelectImage { + selectImage := a.GetSelectImage(id, "") + + if selectImage.HasOriginal { + if selectImage.HasTemp { + runtime.LogDebugf(appContext, "Removing temp png: %s", selectImage.TempPath) + + a.RemoveTempPng(id) + + selectImage.HasTemp = false + selectImage.TempPath = "" + } else { + if selectImage.IsRemoved { + runtime.LogDebugf(appContext, "Retrieving original png: %s", selectImage.Path) + + a.RemoveDeletePng(filepath.Join(appFolder, selectImage.Path)) + + selectImage.IsRemoved = false + } else { + runtime.LogDebugf(appContext, "Removing original png: %s", selectImage.Path) + + a.AddDeletePngRelativePath(selectImage.Path) + + selectImage.IsRemoved = true + } + } + } else if selectImage.HasTemp { + runtime.LogDebugf(appContext, "Removing temp png2: %s", selectImage.TempPath) + + a.RemoveTempPng(id) + + selectImage.HasTemp = false + selectImage.TempPath = "" + } + selectImages.Set(id, selectImage) return selectImage diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 00df4d5..8c5275b 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -37,17 +37,17 @@ import { } from "@/components/ui/dialog"; import SelectImage from "./SelectImage"; import { - AddDeletePngPath, + AddDeletePngRelativePath, AddFilesToIconPackFromDesktop, AddFilesToIconPackFromPath, AddIconPack, - AddTempPngPath, ApplyIconPack, ClearDeletePngPaths, ClearIconPackCache, ClearSelectImages, ClearTempPngPaths, CreateLastTab, + DeleteDeletePngPaths, DeleteIconPack, Description, Destination, @@ -67,6 +67,7 @@ import { SetIconPackField, SetIconPackFiles, SetIconPackMetadata, + SetSelectImage, UUID, } from "@/wailsjs/go/main/App"; import { main } from "@/wailsjs/go/models"; @@ -291,7 +292,7 @@ export default function Packs() { setSelectedPackId={setSelectedPackId} loadIconPacks={loadIconPacks} setEditingIconPack={setEditingIconPack} - reloadSelectedPack={reloadSelectedPack} + reloadIconPacks={handleReloadIconPacks} /> ))} @@ -385,7 +386,7 @@ interface PackContentProps { setSelectedPackId: (packId: string) => void; loadIconPacks: () => void; setEditingIconPack: (editingIconPack: boolean) => void; - reloadSelectedPack: () => void; + reloadIconPacks: () => void; } function PackContent({ @@ -393,7 +394,7 @@ function PackContent({ setSelectedPackId, loadIconPacks, setEditingIconPack, - reloadSelectedPack, + reloadIconPacks, }: PackContentProps) { const { t } = useTranslation(); @@ -482,12 +483,10 @@ function PackContent({ Promise.all([ ClearSelectImages(), + DeleteDeletePngPaths(), SetIconPackMetadata(iconPackId, editedIconPack.metadata), ]).then(() => { - setIconPack(editedIconPack); - loadIconPacks(); - reloadSelectedPack(); - setEditingMetadata(false); + reloadIconPacks(); }); }; @@ -597,7 +596,6 @@ function PackContent({ } packId={iconPackId} editable={editingMetadata} - unkown />
{fields.map((field) => ( @@ -936,7 +934,9 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { const handleRemoveIcon = (index: number) => { if (!files) return; setFiles((prevFiles) => prevFiles?.filter((_, i) => i !== index)); - AddDeletePngPath(iconPackId, files[index].id); + AddDeletePngRelativePath( + `packs\\${iconPackId}\\icons\\${files[index].id}.png` + ); }; const handleInputChange = (index: number, field: string, value: string) => { @@ -954,9 +954,11 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { const handleSaveEdit = () => { if (!files) return; - SetIconPackFiles(iconPackId, files).then(() => { - ClearTempPngPaths(); - ClearSelectImages().then(() => setEditingIconPack(false)); + DeleteDeletePngPaths().then(() => { + SetIconPackFiles(iconPackId, files).then(() => { + ClearTempPngPaths(); + ClearSelectImages().then(() => setEditingIconPack(false)); + }); }); }; @@ -1051,7 +1053,6 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { src={`packs/${iconPackId}/icons/${file.id}.png`} packId={file.id} key={updateArray[index]} - alwaysShowOriginal={false} /> {file.name}
@@ -1072,7 +1073,6 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { }); }} editable - alwaysShowOriginal={false} />
{ - if (!tempPngPath && !file.hasIcon) { - IconLocation(value).then((iconLocation) => { - AddTempPngPath(file.id, iconLocation).then(() => { - setUpdateArray((prevUpdateArray) => { - const newArray = [...prevUpdateArray]; - newArray[index] = prevUpdateArray[index] + 1; - return newArray; - }); - }); - }); - } - }); }} label={"Path"} /> diff --git a/frontend/src/components/SelectImage.tsx b/frontend/src/components/SelectImage.tsx index 69b3601..9ae1612 100644 --- a/frontend/src/components/SelectImage.tsx +++ b/frontend/src/components/SelectImage.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from "react"; -import { CircleHelp, Images } from "lucide-react"; +import { CircleHelp, CircleX, Images, RotateCw } from "lucide-react"; import { Button } from "./ui/button"; import { + ActionSelectImage, GetSelectImage, UploadSelectImage, } from "@/wailsjs/go/main/App"; @@ -14,8 +15,6 @@ interface SelectImageProps { sizeClass?: string; editSizeClass?: string; editable?: boolean; - unkown?: boolean; - alwaysShowOriginal?: boolean; onChange?: () => void; } @@ -26,8 +25,6 @@ const SelectImage: React.FC = ({ sizeClass = "w-12 h-12", editSizeClass = "w-7 h-7", editable = false, - unkown = true, - alwaysShowOriginal = true, onChange, ...rest }) => { @@ -35,8 +32,9 @@ const SelectImage: React.FC = ({ id: packId, path: originalSrc, tempPath: "", - isEmpty: true, - isOriginal: true, + hasOriginal: true, + hasTemp: false, + isRemoved: false, }); const handleUpload = () => { @@ -46,6 +44,14 @@ const SelectImage: React.FC = ({ }); }; + const handleAction = () => { + ActionSelectImage(packId).then((properties) => { + setImageProperties(properties); + console.log(properties); + onChange?.(); + }); + }; + useEffect(() => { GetSelectImage(packId, originalSrc).then((properties) => { setImageProperties(properties); @@ -54,28 +60,48 @@ const SelectImage: React.FC = ({ }, [editable]); return ( - + )} + + {(imageProperties.hasOriginal && !imageProperties.isRemoved) || + imageProperties.hasTemp ? ( + + ) : ( + + )} + +
); }; diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index 7d2f2cb..7e40930 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -2,9 +2,9 @@ // This file is automatically generated. DO NOT EDIT import {main} from '../models'; -export function AddDeletePngPath(arg1:string,arg2:string):Promise; +export function ActionSelectImage(arg1:string):Promise; -export function AddDeletePngRelativePath(arg1:string,arg2:string):Promise; +export function AddDeletePngRelativePath(arg1:string):Promise; export function AddFileToIconPackFromPath(arg1:string,arg2:string,arg3:boolean):Promise; @@ -32,6 +32,8 @@ export function ClearTempPngPaths():Promise; export function CreateLastTab(arg1:string):Promise; +export function DeleteDeletePngPaths():Promise; + export function DeleteIconPack(arg1:string,arg2:boolean):Promise; export function Description(arg1:string):Promise; @@ -88,6 +90,10 @@ export function ReadConfig(arg1:string):Promise; export function ReadLastTab():Promise; +export function RemoveDeletePng(arg1:string):Promise; + +export function RemoveTempPng(arg1:string):Promise; + export function RestartApplication(arg1:boolean,arg2:Array):Promise; export function SaveConfigDialog():Promise; @@ -104,6 +110,8 @@ export function SetIconPackFiles(arg1:string,arg2:Array):Promise< export function SetIconPackMetadata(arg1:string,arg2:main.Metadata):Promise; +export function SetSelectImage(arg1:string,arg2:string):Promise; + export function UUID():Promise; export function Update(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index 9d91c19..d0b7b0e 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -2,12 +2,12 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -export function AddDeletePngPath(arg1, arg2) { - return window['go']['main']['App']['AddDeletePngPath'](arg1, arg2); +export function ActionSelectImage(arg1) { + return window['go']['main']['App']['ActionSelectImage'](arg1); } -export function AddDeletePngRelativePath(arg1, arg2) { - return window['go']['main']['App']['AddDeletePngRelativePath'](arg1, arg2); +export function AddDeletePngRelativePath(arg1) { + return window['go']['main']['App']['AddDeletePngRelativePath'](arg1); } export function AddFileToIconPackFromPath(arg1, arg2, arg3) { @@ -62,6 +62,10 @@ export function CreateLastTab(arg1) { return window['go']['main']['App']['CreateLastTab'](arg1); } +export function DeleteDeletePngPaths() { + return window['go']['main']['App']['DeleteDeletePngPaths'](); +} + export function DeleteIconPack(arg1, arg2) { return window['go']['main']['App']['DeleteIconPack'](arg1, arg2); } @@ -174,6 +178,14 @@ export function ReadLastTab() { return window['go']['main']['App']['ReadLastTab'](); } +export function RemoveDeletePng(arg1) { + return window['go']['main']['App']['RemoveDeletePng'](arg1); +} + +export function RemoveTempPng(arg1) { + return window['go']['main']['App']['RemoveTempPng'](arg1); +} + export function RestartApplication(arg1, arg2) { return window['go']['main']['App']['RestartApplication'](arg1, arg2); } @@ -206,6 +218,10 @@ export function SetIconPackMetadata(arg1, arg2) { return window['go']['main']['App']['SetIconPackMetadata'](arg1, arg2); } +export function SetSelectImage(arg1, arg2) { + return window['go']['main']['App']['SetSelectImage'](arg1, arg2); +} + export function UUID() { return window['go']['main']['App']['UUID'](); } diff --git a/frontend/src/wailsjs/go/models.ts b/frontend/src/wailsjs/go/models.ts index ded2015..de818c7 100644 --- a/frontend/src/wailsjs/go/models.ts +++ b/frontend/src/wailsjs/go/models.ts @@ -160,8 +160,9 @@ export namespace main { id: string; path: string; tempPath: string; - isEmpty: boolean; - isOriginal: boolean; + hasOriginal: boolean; + hasTemp: boolean; + isRemoved: boolean; static createFrom(source: any = {}) { return new SelectImage(source); @@ -172,8 +173,9 @@ export namespace main { this.id = source["id"]; this.path = source["path"]; this.tempPath = source["tempPath"]; - this.isEmpty = source["isEmpty"]; - this.isOriginal = source["isOriginal"]; + this.hasOriginal = source["hasOriginal"]; + this.hasTemp = source["hasTemp"]; + this.isRemoved = source["isRemoved"]; } } export class UpdateInfo { diff --git a/iconpack.go b/iconpack.go index a43e6f7..17453ec 100644 --- a/iconpack.go +++ b/iconpack.go @@ -314,11 +314,7 @@ func (a *App) SetIconPackFiles(packId string, files []FileInfo) { } // Delete the unused icons - paths := deletePngPaths[packId] - for _, path := range paths { - os.Remove(path) - } - delete(deletePngPaths, packId) + a.DeleteDeletePngPaths() // Update cache iconPack.Files = files From 1220aa54059ad411b2f3560d4b88e2add0e18fa6 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 22 Aug 2024 03:05:44 +0300 Subject: [PATCH 155/221] add .exe and .dll icon extraction --- ExtractIcon/LICENSE | 25 +++++++++ ExtractIcon/extracticon.exe | Bin 0 -> 12288 bytes app_paths.go | 4 ++ build/windows/installer/project.nsi | 3 ++ dialog.go | 2 +- frontend/src/components/Packs.tsx | 3 -- iconpack.go | 7 ++- image.go | 81 ++++++++++++---------------- utils.go | 8 +++ 9 files changed, 80 insertions(+), 53 deletions(-) create mode 100644 ExtractIcon/LICENSE create mode 100644 ExtractIcon/extracticon.exe diff --git a/ExtractIcon/LICENSE b/ExtractIcon/LICENSE new file mode 100644 index 0000000..bf812af --- /dev/null +++ b/ExtractIcon/LICENSE @@ -0,0 +1,25 @@ +### License ### + +ExtractIcon (https://github.com/bertjohnson/extracticon) + +Licensed according to the MIT License (http://mit-license.org/). + +Copyright © Bert Johnson (https://bertjohnson.com/) of Allcloud Inc. (https://allcloud.com/). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ExtractIcon/extracticon.exe b/ExtractIcon/extracticon.exe new file mode 100644 index 0000000000000000000000000000000000000000..14dab83c128112e9a1cbed79426ab19cf05d7304 GIT binary patch literal 12288 zcmeHNYj7Labw0ZQ77LJq2$B-1hs8A|JCH0wBqdp-B3T3pP=W=LFhM;i%b`GENvsH9 zk=><4I+jg4aa(5`R~{#6<2)vE{>Yih#C6=nb=vVLNfW0@8aLA<g2wX;lvU zCXF;>Y4RGS7^344WZAng0=$0P6|*yas;Bb`8cR3ICG+#L3GoITm*3;P1-#Pr$z zulOSy!QZL)Bb!i?1z2fszZanjMlf%bHYKb?0*v@I?!60|v=G_wB-o~g)yNh`Lg7%P z3y7%!yxJTN?evF3=8Yg;4y$H2s|3RSa3F%AR9b`9EodmR73}u5Ced75^TyU-Yf~h| zy=SyJvX;?sQ`oO`M7+S)uh)3qM?An`6k{3`{Yc6IG?nH#z^!EEVj>B8kQ zySoj|icJz#80*us`DG&PK2f(B(k2 zR9vpL$idnPy;rxjt+lZFE8(?benfh}bR|LSeKoBe6x-u2l@1IU0!lZb6l(}LUoYc+ z0=#<>7)$`d{F2&wJz^n&6_(S!kk`aQ3kKIW-O{tZ`4-LZ+2vD1-u^Z@?N^0JiR^;H zPI>(fwc}(nWPRW9cbshU`})4#feyCy%5*1eW3U0OaWPU0jkXKU4Xh5mUsauH}60@KDr(4c+rkVL$I~J zsa3wZE(rVT!wgqjStI*f<$w}dgQ{)bR)GPDTV8@&+UOiSG|{1g%*4ao4uyGmag!uqdne5ze3*-@KXX# z`k1ptz_SEBw!C}DfF7WfSK})!cZdkkJ-oY3-S`+vp&W{ewIF< zFy0+x{3Aiee=gty0$vgDn||iMBH+7({`ZB2MGy1+D#M=${F4Gc7$`}SuqFv>5?vOY zHPDpk69VrR)(#0D5(57&+iAJvxd-q;z_i9={U}BK_o3DvoIy-I8GKNZ>0MPy@m!Sr z^usE3G58?~I|i;5dcStj^Mr(xNl=ezCHlC;tB+-mY3Jx^3Cp!gJra0Y!s4$|ry#>} z7u2I#Dfk%)M{<=q>c1l4^c2)%+6MzKNZnQHQSBN3UrO8RR6O{S#5)1E_oz1P`CDlx z4GZz_bWUSEVoMgXFZv#revx0FZwR|Hp;ivsbl^w*JW;2X0%`e< zI`zEoq@1Z!U-4a)j@7A`0%znCb?WZ}8|9OAYFwR@r|Z;+dPe56$MyP`zLI>pPW{j~ zL^E}2hw8|y)FZxk$g_3XAENbKo%)%)BI|YPqsm6vs8e5+-z}f5Q$K<)J~y)*>qqqhM{G>Dx@rU!-eao}Fecb5JfP@$l(aF2&|KCh4_ z(LA6`$B?Ues0fG|E$lxhEM$ZQOIW_Y0kVCG0(=BxrE=ZwH?_5hRwS(ERUNl=t}BaP zNHl}GGQEfvJ#-InFFgr;AN@NxwZ2@2zK0Chzm{`C*ndq}dq((hKcFQ1k?Ahj@sP`T zN$7l3SYU5u>_aT&T9UwJ`qKsr|AD%$oticF9MP`xWqMTL+hJ3tM})P90KHYup$>io z&_hT}z<&^$KKcTnLfj*NwMUH0RowOM{otToVfhN+wYb}(pNnyPx9H1lB33TEC^$Gi z(Qdn_`v-!o< z!m*X<_YkIOq$*)Rg*E^Nk#$U~Ez~FApnx#}lLFo$;Ix2Qz;!q`S^w<@Ca3g&g zP@(VA)7V=c0xki*92}zi>7Wt^|5e`+z)uCn0FMQx=%+LuxD)WJ!80UDj7!qLdFCh} zy@Qj4un(Vw^cCd=*#ANB>+th6&&!mQzU})S zd{`Iw893Y{74Zy8D$2EhEz}O!4qGa9(iXtYLaNbL;M*ty7@;1(ozxH5OZx!#&;h^$ z6ayThQNTDI1w2A&z?9HT3(YB^d6JHSf0|AKo}n4QylA%|+AWH9E23RTw7VqQy$9_g zhoKM1QEHf2V*sdKjDq`ZSxkEo3|)R}AY-k|Sm`oGSEc~&AZ~FTD=*r5zK@JP zvI}~#xVxWnW+~?|;>!BT=%>nJd7+<@srbxrDls1K-%DHf_3xttV`hG_sNW_e>B&SU zF?l=@OQny*XvEU9jy__RDp|*vE$Z=+Dho(lFX|3+GDi-NCTC((Q^bTTs%OSy!Lx%yMn>BM+!EK#i?jSWxC2y`rVawZ*{IwD#di)D@qWPD?0&G&v!fot#KlZ63=cScl`5 z96mOc$W-|a3}G*o%uK~6V<*zF$=Fz3DIT9Vo|r^DGBk-G3)@qZvGEMl65~^po=5^# z13#T%kCWr^#K|hFTG?gk<@`yqovh{5#Hr-?(W(kTI_*lQ>0GwBsL#w$C7yM%RI+oX zRWxR)5YJH|pCcoK_h>xJL^>YR=E0-)~ z=XE-)J8>&J?-GVv6lO~8w+z%D64~BdAflxYg-|WjK1uD8pjWWQeYn9o?EkT-XvF=U592_C)DA zESkoutS&$i(3pcK{MkjpoL!us=T5EiVEVXW^ZH%o#cW$I%@&uZ45yKOjSJV&wz7G> zl(iOCMN?S>(-D0-Z#D{QEpXJ1>y~Yp<<(j>A)+~F%r9ElUalvO>vqmED%T1}i`jX5 z)fDeeMo}1;)Qj0Afi=i$O{Fbtb2(?VVofk&R+cSezHnU;cE0lRYMsf&vSXC$JEBoE zoCawodk({b>6_AP*q8~{q+a3zZ=t`lM0rl5tg)-^N2}w(k}1=D zJ5*Dt*2FfzV>gEvMle38v5H37Crm&m?LfuKRtiSWuJt@_WarDKjpc9Qb>6fv8!#)^Y2PnpO$;Jg+gH zi&dIE;X{j02u$R}+#DlrUNkX#mWd3Kq?}VFiu#;`Ll8R+Wh&S?gh8y8vR%nqdfCC$ zT+kcoS_2~{wjCZA(VR;yj_c=LtWt4~m=4Y|hw@8xY`KO(%!hN_cCcvxB1b#9l?bZd}aZy^D5Cbu z=N3c+Cvl=;7cJXpTQBF9DZh+}R{%uDSziw#L?hWsHiwOt%*sr%tnt^_OsK_$pz5uE45;H$k-?m7!mP{4Swk3TaKpfFw)vLdyC7?!c47FZ|-U z(GP$2xW0u#8$1g^R5qzy-z~4Q*8iOGQ{2j!LPy>KQ*BC(2i$DD1rHGeS7(fDmH%=Y* zd*zO_v|d(Jj}mUd-Hv;ktZ;Y`vLN~7mewTqxrr2sBaA4upo3~i_T!BeMgU#&cA~$X zXt1+0K#JOlKXjZtohYH(+1Uw0oguXay$l3V4cJ&qYjdE6BC;N}v$I9zQbma|WD|mN zg=z~(2^BXZOo%6o)E3B~*b>G7dR4WxSye-t-$g_l;L9#jRoup93;r;+!X#IB8*Evuct_5*X;98MFB4{$AOgPk5f z%)gW{99t`3GuCZKvvlP2uz|!3FV=XLP*|eoVcl}H+sy(pe!M|tXA*7-$2%OplS#Ct zo;+%wds6G~?c1kaTWPyQ+j{e}yR*I7-kw3dU+>woXYSUX?C$=7o`JdSz`nk@z551d z=kR4tB7Yw~=HZWMOrp)v@x)X;uijZr*$(1^->uO;L@(4{7dAXyv~w-3F42TGJF(~1 z#PHt1{#$$Y4)iB__Q2Dg!O{3=Phwzja3CIw^$zYuyNJB#Jx74PUj4lYkHu!NUmU{S zO*EN_XIi(n%vD}dhTikN=2xP-qUX5Q$o|uD%dE_rOQ(^4@%d+;ZV1?E{L13AIeYdr z(sVs*>!%w=Hd@Kg!ovUkt9^L24^hUoRk{(Y{U*fa&5W2%Bo499O^aCXu_2FXObjc!mf^O#SNg z;s+UY9Pd$Q024F`EQw!eje|}Cj=I0rqCfNg_$u3XZ?5jzbicBR)3C~6nR~@iNU)kk z#xq*Y&XUM)=8*O9wT;-3DUlDAkw0;M##s^PMDF#JcZpxB!Y9s}I5(QVj-@G~(~FGE z{o6ycz=*zuK1g_5Y$8YE{E0I$WNEO*Srg}joO5vw#_}xWdC*1C4of%-N?K6P=+gsGmaJ zc~O%`6A!||V~!U8rti%>jh;jV`{4=a^4hP6$PQR zZJS4r`%;_PwJe=bw{eElVz=6-0Wk(?*fBBV_zA*khzMa7Hr7v8XAt4pgkyhgy*G}) z8{cr3sTl8rb9`juOpXxoEC;CUJ)^nmCfA;wUhn7l34X+9K_1(d4b@2W33{UE>qyOx;=;uT>9r zk1fY4c(}&1J7;s*N~L?3Yr(M=ZHM2l{F?sscZ+&x+tzc7cw4(%rNOcA5DGu_d>Zdi zkX_E}_OEGmcg?DXYt;@8|D4D_Q~Ei*s1+F=?9STB@;MW)ySlYSBbLLf6ohdOuQl{) zr-YGR*YT$2`mSqyKd`GF1SlNXRU0AqM-;0A|6A-g8-XGBw|YbFPhNhrwts8we|-f0 E3yMN-fdBvi literal 0 HcmV?d00001 diff --git a/app_paths.go b/app_paths.go index f26cc2e..ccffc7b 100644 --- a/app_paths.go +++ b/app_paths.go @@ -33,6 +33,7 @@ var appIconPath string var installationDirectory string var imageMagickPath string +var extractIconPath string var tempPngPaths map[string]string = map[string]string{} var deletePngPaths []string = []string{} @@ -131,6 +132,9 @@ func path_init() error { imageMagickPath = filepath.Join(installationDirectory, "ImageMagick-7.1.1-35-portable-Q16-x64", "magick.exe") runtime.LogDebugf(appContext, "ImageMagick path: %s", imageMagickPath) + extractIconPath = filepath.Join(installationDirectory, "ExtractIcon", "extracticon.exe") + runtime.LogDebugf(appContext, "ExtractIcon path: %s", extractIconPath) + // Copy all files in scriptsFolderEmbedded to scriptsFolder runtime.LogTrace(appContext, "Attempting to copy scripts from embedded folder to scripts folder") diff --git a/build/windows/installer/project.nsi b/build/windows/installer/project.nsi index e5aabfd..c8c9298 100644 --- a/build/windows/installer/project.nsi +++ b/build/windows/installer/project.nsi @@ -90,6 +90,9 @@ Section SetOutPath "$INSTDIR\ImageMagick-7.1.1-35-portable-Q16-x64" File /r "..\..\..\ImageMagick-7.1.1-35-portable-Q16-x64\*.*" + SetOutPath "$INSTDIR\ExtractIcon" + File /r "..\..\..\ExtractIcon\*.*" + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" diff --git a/dialog.go b/dialog.go index 8407555..e39f737 100644 --- a/dialog.go +++ b/dialog.go @@ -105,7 +105,7 @@ func (a *App) GetTempPng(id string) string { Filters: []runtime.FileFilter{ { DisplayName: "Image File", - Pattern: "*.png;*.jpg;*.jpeg;*.webp;*.svg;*.bmp;*.ico", + Pattern: "*.png;*.jpg;*.jpeg;*.webp;*.svg;*.bmp;*.ico;*.exe;*.lnk", }, }, }) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 8c5275b..af38132 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -59,15 +59,12 @@ import { GetIconFiles, GetIconPack, GetIconPackList, - GetTempPngPath, - IconLocation, ImportIconPack, Name, ReadLastTab, SetIconPackField, SetIconPackFiles, SetIconPackMetadata, - SetSelectImage, UUID, } from "@/wailsjs/go/main/App"; import { main } from "@/wailsjs/go/models"; diff --git a/iconpack.go b/iconpack.go index 17453ec..ebeb4b2 100644 --- a/iconpack.go +++ b/iconpack.go @@ -380,6 +380,9 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { fileInfo.Path = ConvertToGeneralPath(path) fileInfo.Extension = strings.ToLower(filepath.Ext(path)) + runtime.LogDebugf(appContext, "Pack id: %s", packId) + runtime.LogDebugf(appContext, "File extension for %s: %s", path, fileInfo.Extension) + if fileInfo.Extension == ".lnk" { link, err := lnk.File(path) if err != nil { @@ -398,7 +401,7 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { } fileInfo.Destination = ConvertToGeneralPath(fileInfo.Destination) - if packId != "" && strings.ToLower(filepath.Ext(link.StringData.IconLocation)) == ".ico" { + if packId != "" { runtime.LogDebugf(appContext, "Attempting to copy icon: %s", link.StringData.IconLocation) if packId == "temp" { @@ -414,7 +417,7 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { } } else { iconPath := filepath.Join(packsFolder, packId, "icons", fileInfo.Id+".png") - err = ConvertToPng(link.StringData.IconLocation, iconPath) + err = ConvertToPng(path, iconPath) if err != nil { runtime.LogError(appContext, err.Error()) } else { diff --git a/image.go b/image.go index 8d35205..6f543b8 100644 --- a/image.go +++ b/image.go @@ -8,10 +8,11 @@ import ( "strconv" "strings" - "github.com/google/uuid" + lnk "github.com/parsiya/golnk" + "github.com/wailsapp/wails/v2/pkg/runtime" ) -var allowedImageExtensionsPng = []string{".ico", ".png", ".jpg", ".jpeg", ".bmp", ".webp", ".svg"} +var allowedImageExtensionsPng = []string{".ico", ".png", ".jpg", ".jpeg", ".bmp", ".webp", ".svg", ".exe", ".lnk"} var allowedImageExtensionsIco = []string{".png", ".jpg", ".jpeg"} @@ -94,64 +95,50 @@ func ConvertToIco(path string, destination string, settings IconPackSettings) er func ConvertToPng(path string, destination string) error { extension := filepath.Ext(path) - if !contains(allowedImageExtensionsPng, extension) { + if !is_dir(path) && !contains(allowedImageExtensionsPng, extension) { return fmt.Errorf("invalid image extension: %s", extension) } - tempIconFolder := filepath.Join(tempFolder, "iconium-"+uuid.NewString()) - err := create_folder(tempIconFolder) - if err != nil { - return err - } - defer os.RemoveAll(tempIconFolder) - tempIconPath := filepath.Join(tempIconFolder, "icon.png") + iconDestination := "" + + if extension == ".lnk" { + link, err := lnk.File(path) + if err != nil { + return fmt.Errorf("failed to open .lnk file: %w", err) + } - if extension != ".ico" { - tempIconPath = destination + iconLocation := link.StringData.IconLocation + iconDestination = link.LinkInfo.LocalBasePath + + if strings.ToLower(filepath.Ext(iconLocation)) == ".ico" { + err = ConvertToPng(iconLocation, destination) + if err == nil { + return nil + } + } else { + err = ConvertToPng(iconDestination, destination) + if err == nil { + return nil + } + } } // Build magick command arguments - args := []string{imageMagickPath, path, "-alpha", "on", "-background", "none", tempIconPath} + args := []string{extractIconPath, path, destination} - if extension != ".ico" { - args = []string{imageMagickPath, path, "-alpha", "on", "-background", "none", "-resize", "256x256\\!", tempIconPath} + if extension != ".ico" && extension != ".exe" && extension != ".lnk" && !is_dir(path) { + args = []string{imageMagickPath, path, "-alpha", "on", "-background", "none", "-resize", "256x256\\!", destination} } + runtime.LogDebugf(appContext, "ConvertToPng: %s", strings.Join(args, " ")) + // Execute magick command silently - _, err = sendCommand(args...) + _, err := sendCommand(args...) if err != nil { - return fmt.Errorf("error converting image: %w", err) - } - - if extension == ".ico" { - files, err := os.ReadDir(tempIconFolder) - if err != nil { - return err - } - - largestFile := "icon.png" - - // Delete the icons other than the largest one - if len(files) > 1 { - largestWidth := -1 - for _, file := range files { - width, err := GetImageWidth(filepath.Join(tempIconFolder, file.Name())) - if err != nil { - return err - } - - if width > largestWidth { - largestWidth = width - largestFile = file.Name() - } - } - } - - // Move the largest icon to the destination - err = os.Rename(filepath.Join(tempIconFolder, largestFile), destination) - if err != nil { - return err + if extension == ".lnk" { + return ConvertToPng(iconDestination, destination) } + return fmt.Errorf("error converting image: %w", err) } return nil diff --git a/utils.go b/utils.go index 2ebc2c0..52f2d24 100644 --- a/utils.go +++ b/utils.go @@ -327,6 +327,14 @@ func exists(path string) bool { return !os.IsNotExist(err) } +func is_dir(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + return info.IsDir() +} + func (a *App) UUID() string { return uuid.NewString() } From 0497dea14eeabb461324a9dabc5f0d95670bd3c6 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 22 Aug 2024 03:47:18 +0300 Subject: [PATCH 156/221] select image bugfix --- iconpack.go | 21 ++++++++++++++++++++- image.go | 12 +++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/iconpack.go b/iconpack.go index ebeb4b2..7288848 100644 --- a/iconpack.go +++ b/iconpack.go @@ -408,7 +408,7 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { tempName := "iconium-" + uuid.NewString() iconPath := filepath.Join(tempFolder, tempName+".png") - err = ConvertToPng(link.StringData.IconLocation, iconPath) + err = ConvertToPng(path, iconPath) if err != nil { runtime.LogError(appContext, err.Error()) } else { @@ -565,6 +565,25 @@ func (a *App) GetFileInfoFromPaths(id string, path []string) ([]FileInfo, error) if err != nil { return nil, err } + + tempPngPath, ok := tempPngPaths[fileInfo.Id] + selectImage := SelectImage{ + Id: fileInfo.Id, + Path: "", + TempPath: "", + HasOriginal: false, + HasTemp: false, + IsRemoved: false, + } + if ok { + relativeTempPngPath, err := filepath.Rel(appFolder, tempPngPath) + if err == nil { + selectImage.TempPath = relativeTempPngPath + selectImage.HasTemp = true + selectImages.Set(fileInfo.Id, selectImage) + } + } + fileInfos = append(fileInfos, fileInfo) } return fileInfos, nil diff --git a/image.go b/image.go index 6f543b8..828301b 100644 --- a/image.go +++ b/image.go @@ -100,6 +100,7 @@ func ConvertToPng(path string, destination string) error { } iconDestination := "" + iconLocation := "" if extension == ".lnk" { link, err := lnk.File(path) @@ -115,11 +116,6 @@ func ConvertToPng(path string, destination string) error { if err == nil { return nil } - } else { - err = ConvertToPng(iconDestination, destination) - if err == nil { - return nil - } } } @@ -136,6 +132,12 @@ func ConvertToPng(path string, destination string) error { _, err := sendCommand(args...) if err != nil { if extension == ".lnk" { + if strings.ToLower(filepath.Ext(iconLocation)) != ".ico" { + err = ConvertToPng(iconLocation, destination) + if err == nil { + return nil + } + } return ConvertToPng(iconDestination, destination) } return fmt.Errorf("error converting image: %w", err) From f71ea2afec5082d2533c9a415f0918385d061421 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 22 Aug 2024 03:56:17 +0300 Subject: [PATCH 157/221] add directory support --- iconpack.go | 58 ++++++++++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/iconpack.go b/iconpack.go index 7288848..cb55fa8 100644 --- a/iconpack.go +++ b/iconpack.go @@ -48,7 +48,7 @@ type IconPackSettings struct { Opacity int `json:"opacity"` } -var allowedFileExtensions = []string{".lnk"} +var allowedFileExtensions = []string{".lnk", ".dir"} var iconPackCache map[string]IconPack = map[string]IconPack{} @@ -379,6 +379,9 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { fileInfo.Name = strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) fileInfo.Path = ConvertToGeneralPath(path) fileInfo.Extension = strings.ToLower(filepath.Ext(path)) + if is_dir(path) { + fileInfo.Extension = ".dir" + } runtime.LogDebugf(appContext, "Pack id: %s", packId) runtime.LogDebugf(appContext, "File extension for %s: %s", path, fileInfo.Extension) @@ -400,46 +403,29 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { fileInfo.Destination = link.LinkInfo.LocalBasePathUnicode } fileInfo.Destination = ConvertToGeneralPath(fileInfo.Destination) + } - if packId != "" { - runtime.LogDebugf(appContext, "Attempting to copy icon: %s", link.StringData.IconLocation) - - if packId == "temp" { - tempName := "iconium-" + uuid.NewString() - iconPath := filepath.Join(tempFolder, tempName+".png") + if packId != "" { + if packId == "temp" { + tempName := "iconium-" + uuid.NewString() + iconPath := filepath.Join(tempFolder, tempName+".png") - err = ConvertToPng(path, iconPath) - if err != nil { - runtime.LogError(appContext, err.Error()) - } else { - fileInfo.HasIcon = true - tempPngPaths[fileInfo.Id] = iconPath - } + err := ConvertToPng(path, iconPath) + if err != nil { + runtime.LogError(appContext, err.Error()) } else { - iconPath := filepath.Join(packsFolder, packId, "icons", fileInfo.Id+".png") - err = ConvertToPng(path, iconPath) - if err != nil { - runtime.LogError(appContext, err.Error()) - } else { - fileInfo.HasIcon = true - } + fileInfo.HasIcon = true + tempPngPaths[fileInfo.Id] = iconPath + } + } else { + iconPath := filepath.Join(packsFolder, packId, "icons", fileInfo.Id+".png") + err := ConvertToPng(path, iconPath) + if err != nil { + runtime.LogError(appContext, err.Error()) + } else { + fileInfo.HasIcon = true } } - - return fileInfo, nil - } - - file, err := os.Open(path) - if err != nil { - return FileInfo{}, err - } - defer file.Close() - fileStat, err := file.Stat() - if err != nil { - return FileInfo{}, err - } - if fileStat.IsDir() { - fileInfo.Extension = "dir" } if !contains(allowedFileExtensions, fileInfo.Extension) { From 38d0cbb62e64045a719e68d87f8425b0cfe144ab Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Thu, 22 Aug 2024 22:11:34 +0300 Subject: [PATCH 158/221] add directory apply support --- go.mod | 1 + go.sum | 2 + iconpack.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index da1e145..5604d92 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/orcaman/concurrent-map/v2 v2.0.1 github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff github.com/wailsapp/wails/v2 v2.9.1 + gopkg.in/ini.v1 v1.67.0 ) require ( diff --git a/go.sum b/go.sum index 9e38e11..90651f9 100644 --- a/go.sum +++ b/go.sum @@ -124,6 +124,8 @@ golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/iconpack.go b/iconpack.go index cb55fa8..feff08c 100644 --- a/iconpack.go +++ b/iconpack.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/wailsapp/wails/v2/pkg/runtime" + "gopkg.in/ini.v1" lnk "github.com/parsiya/golnk" ) @@ -305,6 +306,13 @@ func (a *App) SetIconPackFiles(packId string, files []FileInfo) { continue } delete(tempPngPaths, file.Id) + + // Delete apply.json + applyFile := filepath.Join(packsFolder, packId, "apply.json") + err = os.Remove(applyFile) + if err != nil { + runtime.LogWarning(appContext, fmt.Sprintf("Failed to remove apply.json icon: %s", err.Error())) + } } iconPath := filepath.Join(packsFolder, packId, "icons", file.Id+".png") @@ -436,14 +444,39 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { } func GetAppliedIcon(path string) (string, error) { - link, err := lnk.File(path) + ext := strings.ToLower(filepath.Ext(path)) - if err != nil { - return "", fmt.Errorf("failed to open .lnk file: %s", err.Error()) - } + if ext == ".lnk" { + link, err := lnk.File(path) + if err != nil { + return "", fmt.Errorf("failed to open .lnk file: %s", err.Error()) + } + + if link.StringData.IconLocation != "" { + return link.StringData.IconLocation, nil + } + } else if is_dir(path) { + iniPath := filepath.Join(path, "desktop.ini") + if !exists(iniPath) { + return "", nil + } + + iniContent, err := os.ReadFile(iniPath) + if err != nil { + return "", err + } + iniFile, err := ini.Load(iniContent) + if err != nil { + return "", err + } + section := iniFile.Section(".ShellClassInfo") + iconResource := section.Key("IconResource").String() + + iconResource = strings.Split(iconResource, ",")[0] - if link.StringData.IconLocation != "" { - return link.StringData.IconLocation, nil + runtime.LogDebugf(appContext, "Icon resource: %s", iconResource) + + return iconResource, nil } return "", errors.New("icon not found") @@ -820,7 +853,12 @@ func (fileInfo *FileInfo) MatchFile() string { } func SetIcon(path string, iconPath string) error { - switch filepath.Ext(path) { + ext := strings.ToLower(filepath.Ext(path)) + if is_dir(path) { + ext = ".dir" + } + + switch ext { case ".lnk": _, err := sendCommand("cscript.exe", setLnkIconScriptPath, filepath.Dir(path), filepath.Base(path), iconPath, "0") @@ -828,6 +866,65 @@ func SetIcon(path string, iconPath string) error { return err } + runtime.LogDebug(appContext, "Applied icon: "+iconPath) + return nil + case ".dir": + iniPath := filepath.Join(path, "desktop.ini") + iniPathTxt := filepath.Join(path, uuid.NewString()+"-desktop.txt") + + if exists(iniPath) { + iniContent, err := os.ReadFile(iniPath) + if err != nil { + return err + } + iniFile, err := ini.Load(iniContent) + if err != nil { + return err + } + section := iniFile.Section(".ShellClassInfo") + iconResource := section.Key("IconResource") + + iconResource.SetValue(iconPath + ",0") + + err = iniFile.SaveTo(iniPathTxt) + if err != nil { + return err + } + + err = os.Rename(iniPathTxt, iniPath) + if err != nil { + return err + } + + runtime.LogDebug(appContext, "Updated desktop.ini: "+iniPath) + } else { + // Create desktop.ini + file, err := os.Create(iniPathTxt) + if err != nil { + return err + } + + _, err = file.WriteString("[.ShellClassInfo]\nIconResource=" + iconPath + ",0") + if err != nil { + return err + } + + file.Close() + + err = os.Rename(iniPathTxt, iniPath) + if err != nil { + return err + } + + runtime.LogDebug(appContext, "Created desktop.ini: "+iniPath) + } + + sendCommand("attrib", "-s", "-h", iniPath) + sendCommand("attrib", "+s", "+h", iniPath) + + sendCommand("attrib", "-r", path) + sendCommand("attrib", "+r", path) + runtime.LogDebug(appContext, "Applied icon: "+iconPath) return nil } From 69c42bd0492a328512995d47e42ee734dfedb688 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 23 Aug 2024 14:03:25 +0300 Subject: [PATCH 159/221] enhance path pattern logic --- utils.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/utils.go b/utils.go index 52f2d24..814e471 100644 --- a/utils.go +++ b/utils.go @@ -165,6 +165,18 @@ func ConvertToFullPath(path string) string { paths = []string{path1, path2} } + if strings.Contains(path, `**`) { + newPaths := []string{} + + for _, path := range paths { + paths := generateCombinations(path) + + newPaths = append(newPaths, paths...) + } + + paths = newPaths + } + for _, path := range paths { matches, err := filepath.Glob(path) if err != nil { @@ -180,6 +192,43 @@ func ConvertToFullPath(path string) string { return "" } +func generateCombinations(path string) []string { + // Find the first occurrence of multiple consecutive asterisks pattern (e.g., **, ***) + index := strings.Index(path, "**") + if index == -1 { + return []string{filepath.Clean(path)} // No more '**' to replace, return cleaned path + } + + // Count consecutive asterisks to determine maximum depth + maxDepth := 0 + for i := index; i < len(path) && path[i] == '*'; i++ { + maxDepth++ + } + + runtime.LogDebugf(appContext, "Generating combinations with max depth: %d", maxDepth-1) + + var results []string + base := path[:index] + suffix := path[index+maxDepth:] // Adjust suffix to skip over the asterisks + + // Generate combinations based on current asterisk count (depth) + for i := 0; i < maxDepth; i++ { + // Combine path with varying depth of wildcards + if i == 0 { + // Add combination without extra depth + combinedPath := filepath.Clean(base + suffix) + results = append(results, generateCombinations(combinedPath)...) + } else { + // Add combinations with increasing depth + wildcards := strings.Repeat(`*\`, i) + combinedPath := filepath.Clean(base + wildcards + suffix) + results = append(results, generateCombinations(combinedPath)...) + } + } + + return results +} + func copy_file(src string, dst string) error { input, err := os.ReadFile(src) if err != nil { @@ -340,19 +389,16 @@ func (a *App) UUID() string { } func (a *App) Ext(path string) string { - path = ConvertToFullPath(path) - if path == "" { - return "" - } - return filepath.Ext(path) + return strings.ToLower(filepath.Ext(path)) } func (a *App) Name(path string) string { - path = ConvertToFullPath(path) - if path == "" { - return "" + fullPath := ConvertToFullPath(path) + if fullPath == "" { + base := filepath.Base(path) + return strings.TrimSuffix(base, filepath.Ext(base)) } - base := filepath.Base(path) + base := filepath.Base(fullPath) return strings.TrimSuffix(base, filepath.Ext(base)) } From c1e25d06be9ddddf87bca0a82547a2b1af309b21 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 23 Aug 2024 14:17:18 +0300 Subject: [PATCH 160/221] change env var order --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 814e471..a6d4667 100644 --- a/utils.go +++ b/utils.go @@ -123,8 +123,8 @@ func ConvertToGeneralPath(path string) string { "PROGRAMDATA", "USERPROFILE", "PUBLIC", - "WINDIR", "SYSTEMROOT", + "WINDIR", "HOMEDRIVE", "SYSTEMDRIVE", } From 9a55e013cc0b17fcdc0428a1228d0bc045b65691 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 23 Aug 2024 20:30:25 +0300 Subject: [PATCH 161/221] add url support --- config.go | 101 +++++++++--------- dialog.go | 14 +-- frontend/src/components/Packs.tsx | 40 +++---- ...g.tsx => MatchLnkByDestinationSetting.tsx} | 4 +- .../MatchURLByDestinationSetting.tsx | 14 +++ frontend/src/components/Settings.tsx | 6 +- frontend/src/wailsjs/go/models.ts | 6 +- iconpack.go | 60 ++++++++--- image.go | 17 ++- utils.go | 69 +++++++++--- 10 files changed, 225 insertions(+), 106 deletions(-) rename frontend/src/components/SettingItems/{MatchByDestinationSetting.tsx => MatchLnkByDestinationSetting.tsx} (78%) create mode 100644 frontend/src/components/SettingItems/MatchURLByDestinationSetting.tsx diff --git a/config.go b/config.go index 17c1d97..fb87a59 100644 --- a/config.go +++ b/config.go @@ -12,30 +12,31 @@ import ( ) type Config struct { - Theme *string `json:"theme"` // system, light, dark - UseSystemTitleBar *bool `json:"useSystemTitleBar"` // true, false - EnableLogging *bool `json:"enableLogging"` // true, false - EnableTrace *bool `json:"enableTrace"` // true, false - EnableDebug *bool `json:"enableDebug"` // true, false - EnableInfo *bool `json:"enableInfo"` // true, false - EnableWarn *bool `json:"enableWarn"` // true, false - EnableError *bool `json:"enableError"` // true, false - EnableFatal *bool `json:"enableFatal"` // true, false - MaxLogFiles *int `json:"maxLogFiles"` // int - Language *string `json:"language"` // en-US, tr-TR - SaveWindowStatus *bool `json:"saveWindowStatus"` // true, false - WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen - WindowStartPositionX *int `json:"windowStartPositionX"` // x - WindowStartPositionY *int `json:"windowStartPositionY"` // y - WindowStartSizeX *int `json:"windowStartSizeX"` // x - WindowStartSizeY *int `json:"windowStartSizeY"` // y - WindowScale *int `json:"windowScale"` // % - Opacity *int `json:"opacity"` // % - WindowEffect *int `json:"windowEffect"` // 0 = Auto, 1 = None, 2 = Mica, 3 = Acrylic, 4 = Tabbed - CheckForUpdates *bool `json:"checkForUpdates"` // true, false - LastUpdateCheck *int `json:"lastUpdateCheck"` // unix timestamp - MatchByDestination *bool `json:"matchByDestination"` // true, false - RenameMatchedFiles *bool `json:"renameMatchedFiles"` // true, false + Theme *string `json:"theme"` // system, light, dark + UseSystemTitleBar *bool `json:"useSystemTitleBar"` // true, false + EnableLogging *bool `json:"enableLogging"` // true, false + EnableTrace *bool `json:"enableTrace"` // true, false + EnableDebug *bool `json:"enableDebug"` // true, false + EnableInfo *bool `json:"enableInfo"` // true, false + EnableWarn *bool `json:"enableWarn"` // true, false + EnableError *bool `json:"enableError"` // true, false + EnableFatal *bool `json:"enableFatal"` // true, false + MaxLogFiles *int `json:"maxLogFiles"` // int + Language *string `json:"language"` // en-US, tr-TR + SaveWindowStatus *bool `json:"saveWindowStatus"` // true, false + WindowStartState *int `json:"windowStartState"` // 0 = Normal, 1 = Maximized, 2 = Minimized, 3 = Fullscreen + WindowStartPositionX *int `json:"windowStartPositionX"` // x + WindowStartPositionY *int `json:"windowStartPositionY"` // y + WindowStartSizeX *int `json:"windowStartSizeX"` // x + WindowStartSizeY *int `json:"windowStartSizeY"` // y + WindowScale *int `json:"windowScale"` // % + Opacity *int `json:"opacity"` // % + WindowEffect *int `json:"windowEffect"` // 0 = Auto, 1 = None, 2 = Mica, 3 = Acrylic, 4 = Tabbed + CheckForUpdates *bool `json:"checkForUpdates"` // true, false + LastUpdateCheck *int `json:"lastUpdateCheck"` // unix timestamp + MatchLnkByDestination *bool `json:"matchLnkByDestination"` // true, false + MatchURLByDestination *bool `json:"matchURLByDestination"` // true, false + RenameMatchedFiles *bool `json:"renameMatchedFiles"` // true, false } func GetDefaultConfig() Config { @@ -61,34 +62,36 @@ func GetDefaultConfig() Config { defaultWindowEffect := 0 defaultCheckForUpdates := true defaultLastUpdateCheck := 0 - defaultMatchByDestination := true + defaultMatchLnkByDestination := true + defaultMatchURLByDestination := true defaultRenameMatchedFiles := false return Config{ - Theme: &defaultTheme, - UseSystemTitleBar: &defaultUseSystemTitleBar, - EnableLogging: &defaultEnableLogging, - EnableTrace: &defaultEnableTrace, - EnableDebug: &defaultEnableDebug, - EnableInfo: &defaultEnableInfo, - EnableWarn: &defaultEnableWarn, - EnableError: &defaultEnableError, - EnableFatal: &defaultEnableFatal, - MaxLogFiles: &defaultMaxLogFiles, - Language: &defaultLanguage, - SaveWindowStatus: &defaultSaveWindowStatus, - WindowStartState: &defaultWindowStartState, - WindowStartPositionX: &defaultWindowStartPositionX, - WindowStartPositionY: &defaultWindowStartPositionY, - WindowStartSizeX: &defaultWindowStartSizeX, - WindowStartSizeY: &defaultWindowStartSizeY, - WindowScale: &defaultWindowScale, - Opacity: &defaultOpacity, - WindowEffect: &defaultWindowEffect, - CheckForUpdates: &defaultCheckForUpdates, - LastUpdateCheck: &defaultLastUpdateCheck, - MatchByDestination: &defaultMatchByDestination, - RenameMatchedFiles: &defaultRenameMatchedFiles, + Theme: &defaultTheme, + UseSystemTitleBar: &defaultUseSystemTitleBar, + EnableLogging: &defaultEnableLogging, + EnableTrace: &defaultEnableTrace, + EnableDebug: &defaultEnableDebug, + EnableInfo: &defaultEnableInfo, + EnableWarn: &defaultEnableWarn, + EnableError: &defaultEnableError, + EnableFatal: &defaultEnableFatal, + MaxLogFiles: &defaultMaxLogFiles, + Language: &defaultLanguage, + SaveWindowStatus: &defaultSaveWindowStatus, + WindowStartState: &defaultWindowStartState, + WindowStartPositionX: &defaultWindowStartPositionX, + WindowStartPositionY: &defaultWindowStartPositionY, + WindowStartSizeX: &defaultWindowStartSizeX, + WindowStartSizeY: &defaultWindowStartSizeY, + WindowScale: &defaultWindowScale, + Opacity: &defaultOpacity, + WindowEffect: &defaultWindowEffect, + CheckForUpdates: &defaultCheckForUpdates, + LastUpdateCheck: &defaultLastUpdateCheck, + MatchLnkByDestination: &defaultMatchLnkByDestination, + MatchURLByDestination: &defaultMatchURLByDestination, + RenameMatchedFiles: &defaultRenameMatchedFiles, } } diff --git a/dialog.go b/dialog.go index e39f737..91beb06 100644 --- a/dialog.go +++ b/dialog.go @@ -105,7 +105,7 @@ func (a *App) GetTempPng(id string) string { Filters: []runtime.FileFilter{ { DisplayName: "Image File", - Pattern: "*.png;*.jpg;*.jpeg;*.webp;*.svg;*.bmp;*.ico;*.exe;*.lnk", + Pattern: "*.png;*.jpg;*.jpeg;*.webp;*.svg;*.bmp;*.ico;*.exe;*.lnk;*.url", }, }, }) @@ -157,8 +157,8 @@ func (a *App) GetIconFile() string { CanCreateDirectories: true, Filters: []runtime.FileFilter{ { - DisplayName: "Windows Shortcut", - Pattern: "*.lnk", + DisplayName: "Shortcut", + Pattern: "*.lnk;*.url", }, }, }) @@ -177,8 +177,8 @@ func (a *App) GetIconFiles() []string { CanCreateDirectories: true, Filters: []runtime.FileFilter{ { - DisplayName: "Windows Shortcut", - Pattern: "*.lnk", + DisplayName: "Shortcut", + Pattern: "*.lnk;*.url", }, }, }) @@ -200,8 +200,8 @@ func (a *App) GetFilePath(generalPath string) string { ResolvesAliases: true, Filters: []runtime.FileFilter{ { - DisplayName: "Windows Shortcut", - Pattern: "*.lnk", + DisplayName: "Shortcut", + Pattern: "*.lnk;*.url", }, }, }) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index af38132..948afea 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -1083,14 +1083,16 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { className="justify-between w-full" />
- { - handleInputChange(index, "description", value); - }} - label={"Description"} - /> + {file.extension === ".lnk" && ( + { + handleInputChange(index, "description", value); + }} + label={"Description"} + /> + )} { + console.log("d: "+destinationPath); handleInputChange( index, "destinationPath", @@ -1124,16 +1127,17 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { label={"Path"} /> - {file.extension === ".lnk" && ( - { - handleInputChange(index, "destinationPath", value); - }} - label={"Destination Path"} - /> - )} + {file.extension === ".lnk" || + (file.extension === ".url" && ( + { + handleInputChange(index, "destinationPath", value); + }} + label={"Destination Path"} + /> + ))}
diff --git a/frontend/src/components/SettingItems/MatchByDestinationSetting.tsx b/frontend/src/components/SettingItems/MatchLnkByDestinationSetting.tsx similarity index 78% rename from frontend/src/components/SettingItems/MatchByDestinationSetting.tsx rename to frontend/src/components/SettingItems/MatchLnkByDestinationSetting.tsx index e75b2ff..8a56c6d 100644 --- a/frontend/src/components/SettingItems/MatchByDestinationSetting.tsx +++ b/frontend/src/components/SettingItems/MatchLnkByDestinationSetting.tsx @@ -1,12 +1,12 @@ import { useTranslation } from "react-i18next"; import { SwitchConfig } from "./Presets/SwitchConfig"; -export function MatchByDestinationSetting() { +export function MatchLnkByDestinationSetting() { const { t } = useTranslation(); return ( diff --git a/frontend/src/components/SettingItems/MatchURLByDestinationSetting.tsx b/frontend/src/components/SettingItems/MatchURLByDestinationSetting.tsx new file mode 100644 index 0000000..e520420 --- /dev/null +++ b/frontend/src/components/SettingItems/MatchURLByDestinationSetting.tsx @@ -0,0 +1,14 @@ +import { useTranslation } from "react-i18next"; +import { SwitchConfig } from "./Presets/SwitchConfig"; + +export function MatchURLByDestinationSetting() { + const { t } = useTranslation(); + + return ( + + ); +} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 2ecc6ca..9995b6b 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -16,8 +16,9 @@ import { CheckForUpdatesSetting } from "./SettingItems/CheckForUpdatesSetting"; import { UpdateSetting } from "./SettingItems/UpdateSetting"; import { useEffect, useState } from "react"; import { useStorage } from "@/contexts/storage-provider"; -import { MatchByDestinationSetting } from "./SettingItems/MatchByDestinationSetting"; +import { MatchLnkByDestinationSetting } from "./SettingItems/MatchLnkByDestinationSetting"; import { RenameMatchedFilesSetting } from "./SettingItems/RenameMatchedFilesSetting"; +import { MatchURLByDestinationSetting } from "./SettingItems/MatchURLByDestinationSetting"; export default function Settings() { const { t } = useTranslation(); @@ -97,7 +98,8 @@ export default function Settings() {
- + + diff --git a/frontend/src/wailsjs/go/models.ts b/frontend/src/wailsjs/go/models.ts index de818c7..cf42580 100644 --- a/frontend/src/wailsjs/go/models.ts +++ b/frontend/src/wailsjs/go/models.ts @@ -23,7 +23,8 @@ export namespace main { windowEffect?: number; checkForUpdates?: boolean; lastUpdateCheck?: number; - matchByDestination?: boolean; + matchLnkByDestination?: boolean; + matchURLByDestination?: boolean; renameMatchedFiles?: boolean; static createFrom(source: any = {}) { @@ -54,7 +55,8 @@ export namespace main { this.windowEffect = source["windowEffect"]; this.checkForUpdates = source["checkForUpdates"]; this.lastUpdateCheck = source["lastUpdateCheck"]; - this.matchByDestination = source["matchByDestination"]; + this.matchLnkByDestination = source["matchLnkByDestination"]; + this.matchURLByDestination = source["matchURLByDestination"]; this.renameMatchedFiles = source["renameMatchedFiles"]; } } diff --git a/iconpack.go b/iconpack.go index feff08c..39b66ff 100644 --- a/iconpack.go +++ b/iconpack.go @@ -49,7 +49,7 @@ type IconPackSettings struct { Opacity int `json:"opacity"` } -var allowedFileExtensions = []string{".lnk", ".dir"} +var allowedFileExtensions = []string{".lnk", ".dir", ".url"} var iconPackCache map[string]IconPack = map[string]IconPack{} @@ -306,13 +306,6 @@ func (a *App) SetIconPackFiles(packId string, files []FileInfo) { continue } delete(tempPngPaths, file.Id) - - // Delete apply.json - applyFile := filepath.Join(packsFolder, packId, "apply.json") - err = os.Remove(applyFile) - if err != nil { - runtime.LogWarning(appContext, fmt.Sprintf("Failed to remove apply.json icon: %s", err.Error())) - } } iconPath := filepath.Join(packsFolder, packId, "icons", file.Id+".png") @@ -324,6 +317,14 @@ func (a *App) SetIconPackFiles(packId string, files []FileInfo) { // Delete the unused icons a.DeleteDeletePngPaths() + // Delete apply.json + applyFile := filepath.Join(packsFolder, packId, "apply.json") + runtime.LogDebugf(appContext, "Attempting to remove apply.json: %s", applyFile) + err = os.Remove(applyFile) + if err != nil { + runtime.LogWarning(appContext, fmt.Sprintf("Failed to remove apply.json icon: %s", err.Error())) + } + // Update cache iconPack.Files = files a.SetIconPack(iconPack) @@ -411,6 +412,22 @@ func CreateFileInfo(packId string, path string) (FileInfo, error) { fileInfo.Destination = link.LinkInfo.LocalBasePathUnicode } fileInfo.Destination = ConvertToGeneralPath(fileInfo.Destination) + } else if fileInfo.Extension == ".url" { + iniContent, err := os.ReadFile(path) + if err != nil { + return FileInfo{}, err + } + + iniFile, err := ini.Load(iniContent) + if err != nil { + return FileInfo{}, err + } + + section := iniFile.Section("InternetShortcut") + + url := section.Key("URL").String() + + fileInfo.Destination = url } if packId != "" { @@ -455,6 +472,26 @@ func GetAppliedIcon(path string) (string, error) { if link.StringData.IconLocation != "" { return link.StringData.IconLocation, nil } + } else if ext == ".url" { + iniPath := filepath.Join(path) + if !exists(iniPath) { + return "", nil + } + iniContent, err := os.ReadFile(iniPath) + if err != nil { + return "", err + } + iniFile, err := ini.Load(iniContent) + if err != nil { + return "", err + } + section := iniFile.Section("InternetShortcut") + + if !section.HasKey("IconFile") { + return "", errors.New("icon not found") + } + + return section.Key("IconFile").String(), nil } else if is_dir(path) { iniPath := filepath.Join(path, "desktop.ini") if !exists(iniPath) { @@ -816,7 +853,7 @@ func (fileInfo *FileInfo) MatchFile() string { return path } - if *config.MatchByDestination { + if (*config.MatchLnkByDestination && fileInfo.Extension == ".lnk") || (*config.MatchURLByDestination && fileInfo.Extension == ".url") { pathDir := ConvertToFullPath(filepath.Dir(pathPattern)) files, err := os.ReadDir(pathDir) @@ -858,8 +895,7 @@ func SetIcon(path string, iconPath string) error { ext = ".dir" } - switch ext { - case ".lnk": + if ext == ".lnk" || ext == ".url" { _, err := sendCommand("cscript.exe", setLnkIconScriptPath, filepath.Dir(path), filepath.Base(path), iconPath, "0") if err != nil { @@ -868,7 +904,7 @@ func SetIcon(path string, iconPath string) error { runtime.LogDebug(appContext, "Applied icon: "+iconPath) return nil - case ".dir": + } else if ext == ".dir" { iniPath := filepath.Join(path, "desktop.ini") iniPathTxt := filepath.Join(path, uuid.NewString()+"-desktop.txt") diff --git a/image.go b/image.go index 828301b..86e86a0 100644 --- a/image.go +++ b/image.go @@ -10,9 +10,10 @@ import ( lnk "github.com/parsiya/golnk" "github.com/wailsapp/wails/v2/pkg/runtime" + "gopkg.in/ini.v1" ) -var allowedImageExtensionsPng = []string{".ico", ".png", ".jpg", ".jpeg", ".bmp", ".webp", ".svg", ".exe", ".lnk"} +var allowedImageExtensionsPng = []string{".ico", ".png", ".jpg", ".jpeg", ".bmp", ".webp", ".svg", ".exe", ".lnk", ".url"} var allowedImageExtensionsIco = []string{".png", ".jpg", ".jpeg"} @@ -117,6 +118,20 @@ func ConvertToPng(path string, destination string) error { return nil } } + } else if extension == ".url" { + iniContent, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to open .url file: %w", err) + } + iniFile, err := ini.Load(iniContent) + if err != nil { + return fmt.Errorf("failed to parse .url file: %w", err) + } + + section := iniFile.Section("InternetShortcut") + iconLocation = section.Key("IconFile").String() + + return ConvertToPng(iconLocation, destination) } // Build magick command arguments diff --git a/utils.go b/utils.go index a6d4667..85c4118 100644 --- a/utils.go +++ b/utils.go @@ -15,6 +15,7 @@ import ( "github.com/google/uuid" lnk "github.com/parsiya/golnk" "github.com/wailsapp/wails/v2/pkg/runtime" + "gopkg.in/ini.v1" ) func writeJSON(path string, data interface{}) error { @@ -420,18 +421,39 @@ func (a *App) Destination(path string) string { if path == "" { return "" } - link, err := lnk.File(path) - if err != nil { - return "" - } + + ext := a.Ext(path) var destination string - if link.LinkInfo.LocalBasePath != "" { - destination = link.LinkInfo.LocalBasePath - } - if link.LinkInfo.LocalBasePathUnicode != "" { - destination = link.LinkInfo.LocalBasePathUnicode + if ext == ".lnk" { + link, err := lnk.File(path) + if err != nil { + return "" + } + + if link.LinkInfo.LocalBasePath != "" { + destination = link.LinkInfo.LocalBasePath + } + if link.LinkInfo.LocalBasePathUnicode != "" { + destination = link.LinkInfo.LocalBasePathUnicode + } + } else if ext == ".url" { + iniPath := filepath.Join(path) + if !exists(iniPath) { + return "" + } + iniContent, err := os.ReadFile(iniPath) + if err != nil { + return "" + } + iniFile, err := ini.Load(iniContent) + if err != nil { + return "" + } + section := iniFile.Section("InternetShortcut") + + destination = section.Key("URL").String() } runtime.LogDebugf(appContext, "Destination: %s", destination) @@ -444,12 +466,33 @@ func (a *App) IconLocation(path string) string { if path == "" { return "" } - link, err := lnk.File(path) - if err != nil { - return "" + + if a.Ext(path) == ".lnk" { + link, err := lnk.File(path) + if err != nil { + return "" + } + + return link.StringData.IconLocation + } else if a.Ext(path) == ".url" { + iniPath := filepath.Join(path) + if !exists(iniPath) { + return "" + } + iniContent, err := os.ReadFile(iniPath) + if err != nil { + return "" + } + iniFile, err := ini.Load(iniContent) + if err != nil { + return "" + } + section := iniFile.Section("InternetShortcut") + + return section.Key("IconFile").String() } - return link.StringData.IconLocation + return "" } func (a *App) CreateLastTab(path string) { From a0f4f27277afa4bb3dac4e5557a80aa931c2fb4d Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 23 Aug 2024 21:50:05 +0300 Subject: [PATCH 162/221] update setIcon --- go.mod | 2 +- go.sum | 6 +- iconpack.go | 190 +++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 141 insertions(+), 57 deletions(-) diff --git a/go.mod b/go.mod index 5604d92..20ae0e2 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( aead.dev/minisign v0.2.0 // indirect github.com/bep/debounce v1.2.1 // indirect github.com/blang/semver v3.5.1+incompatible - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 90651f9..3fb9685 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4 h1:ygs9POGDQpQGLJPlq4+0LBUmMBNox1N4JSpw+OETcvI= github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4/go.mod h1:0W7dI87PvXJ1Sjs0QPvWXKcQmNERY77e8l7GFhZB/s4= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE= github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -101,7 +101,6 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -113,6 +112,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/iconpack.go b/iconpack.go index 39b66ff..2dc8103 100644 --- a/iconpack.go +++ b/iconpack.go @@ -10,7 +10,10 @@ import ( "strconv" "strings" "sync" + "syscall" + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" "github.com/google/uuid" "github.com/wailsapp/wails/v2/pkg/runtime" "gopkg.in/ini.v1" @@ -895,75 +898,156 @@ func SetIcon(path string, iconPath string) error { ext = ".dir" } - if ext == ".lnk" || ext == ".url" { - _, err := sendCommand("cscript.exe", setLnkIconScriptPath, filepath.Dir(path), filepath.Base(path), iconPath, "0") - + if ext == ".lnk" { + err := setLnkIcon(path, iconPath) if err != nil { - return err + return setIconScript(path, iconPath) + } + } else if ext == ".url" { + err := setIconScript(path, iconPath) + if err != nil { + return setUrlIcon(path, iconPath) } - - runtime.LogDebug(appContext, "Applied icon: "+iconPath) - return nil } else if ext == ".dir" { - iniPath := filepath.Join(path, "desktop.ini") - iniPathTxt := filepath.Join(path, uuid.NewString()+"-desktop.txt") + return setDirIcon(path, iconPath) + } - if exists(iniPath) { - iniContent, err := os.ReadFile(iniPath) - if err != nil { - return err - } - iniFile, err := ini.Load(iniContent) - if err != nil { - return err - } - section := iniFile.Section(".ShellClassInfo") - iconResource := section.Key("IconResource") + return errors.New("unsupported file type") +} - iconResource.SetValue(iconPath + ",0") +func setIconScript(path string, iconPath string) error { + _, err := sendCommand("cscript.exe", setLnkIconScriptPath, filepath.Dir(path), filepath.Base(path), iconPath, "0") + if err != nil { + return err + } + return nil +} - err = iniFile.SaveTo(iniPathTxt) - if err != nil { - return err - } +func setLnkIcon(linkPath string, iconPath string) error { + // Initialize COM + err := ole.CoInitialize(0) + if err != nil { + return fmt.Errorf("failed to initialize COM: %v", err) + } + defer ole.CoUninitialize() - err = os.Rename(iniPathTxt, iniPath) - if err != nil { - return err - } + // Create a WScript.Shell object + wshShell, err := oleutil.CreateObject("WScript.Shell") + if err != nil { + return fmt.Errorf("failed to create WScript.Shell object: %v", err) + } + defer wshShell.Release() - runtime.LogDebug(appContext, "Updated desktop.ini: "+iniPath) - } else { - // Create desktop.ini - file, err := os.Create(iniPathTxt) - if err != nil { - return err - } + // Get the IDispatch interface + wshShellDisp, err := wshShell.QueryInterface(ole.IID_IDispatch) + if err != nil { + return fmt.Errorf("failed to get IDispatch interface for WScript.Shell: %v", err) + } + defer wshShellDisp.Release() - _, err = file.WriteString("[.ShellClassInfo]\nIconResource=" + iconPath + ",0") - if err != nil { - return err - } + // Create a shortcut object + shortcut, err := oleutil.CallMethod(wshShellDisp, "CreateShortcut", linkPath) + if err != nil { + return fmt.Errorf("failed to create shortcut object: %v", err) + } + shortcutDisp := shortcut.ToIDispatch() + defer shortcutDisp.Release() - file.Close() + // Set the icon location + _, err = oleutil.PutProperty(shortcutDisp, "IconLocation", iconPath+",0") + if err != nil { + return fmt.Errorf("failed to set icon location: %v", err) + } - err = os.Rename(iniPathTxt, iniPath) - if err != nil { - return err - } + // Save the shortcut + _, err = oleutil.CallMethod(shortcutDisp, "Save") + if err != nil { + return fmt.Errorf("failed to save shortcut: %v", err) + } - runtime.LogDebug(appContext, "Created desktop.ini: "+iniPath) + return nil +} + +func setUrlIcon(urlPath string, iconPath string) error { + iniContent, err := os.ReadFile(urlPath) + if err != nil { + return err + } + iniFile, err := ini.Load(iniContent) + if err != nil { + return err + } + section := iniFile.Section("InternetShortcut") + section.Key("IconFile").SetValue(iconPath) + section.Key("IconIndex").SetValue("0") + err = iniFile.SaveTo(urlPath) + if err != nil { + return err + } + return nil +} + +func setDirIcon(dirPath string, iconPath string) error { + iniPath := filepath.Join(dirPath, "desktop.ini") + iniPathTxt := filepath.Join(dirPath, uuid.NewString()+"-desktop.txt") + + if exists(iniPath) { + iniContent, err := os.ReadFile(iniPath) + if err != nil { + return err + } + iniFile, err := ini.Load(iniContent) + if err != nil { + return err + } + section := iniFile.Section(".ShellClassInfo") + iconResource := section.Key("IconResource") + iconResource.SetValue(iconPath + ",0") + + err = iniFile.SaveTo(iniPathTxt) + if err != nil { + return err + } + + err = os.Rename(iniPathTxt, iniPath) + if err != nil { + return err + } + } else { + // Create desktop.ini + file, err := os.Create(iniPathTxt) + if err != nil { + return err } - sendCommand("attrib", "-s", "-h", iniPath) - sendCommand("attrib", "+s", "+h", iniPath) + _, err = file.WriteString("[.ShellClassInfo]\nIconResource=" + iconPath + ",0") + if err != nil { + return err + } - sendCommand("attrib", "-r", path) - sendCommand("attrib", "+r", path) + file.Close() + + err = os.Rename(iniPathTxt, iniPath) + if err != nil { + return err + } + } - runtime.LogDebug(appContext, "Applied icon: "+iconPath) - return nil + // Set attributes + if err := setFileAttributes(iniPath, syscall.FILE_ATTRIBUTE_HIDDEN|syscall.FILE_ATTRIBUTE_SYSTEM); err != nil { + return err + } + if err := setFileAttributes(dirPath, syscall.FILE_ATTRIBUTE_READONLY); err != nil { + return err } - return errors.New("unsupported file type") + return nil +} + +func setFileAttributes(path string, attributes uint32) error { + pathUTF16, err := syscall.UTF16PtrFromString(path) + if err != nil { + return err + } + return syscall.SetFileAttributes(pathUTF16, attributes) } From 03658c4c11bc29ae7c760bafaa78f766a8504745 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 23 Aug 2024 22:01:53 +0300 Subject: [PATCH 163/221] fix image selector updating bug for edit screen --- frontend/src/components/Packs.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 948afea..317fafd 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -854,7 +854,7 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { const [loading, setLoading] = useState(true); const [files, setFiles] = useState(); - const [updateArray, setUpdateArray] = useState([]); + const [updateArray, setUpdateArray] = useState(Array.from({ length: 4096 }, (_, i) => i)); const [addIconsFromDesktopRunning, setAddIconsFromDesktopRunning] = useState(false); @@ -864,9 +864,6 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { useEffect(() => { GetIconPack(iconPackId).then((pack) => { setFiles(pack.files); - for (let i = 0; i < pack.files.length; i++) { - setUpdateArray((prev) => [...prev, i]); - } setLoading(false); }); }, []); From c58efea4ff9a5dca937e4b9133d923d8c887204d Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 23 Aug 2024 22:53:05 +0300 Subject: [PATCH 164/221] fix empty icon image selector --- app_paths.go | 54 ++++++++++++++++++++++++++- frontend/src/components/Packs.tsx | 25 ++++++++++++- frontend/src/wailsjs/go/main/App.d.ts | 4 ++ frontend/src/wailsjs/go/main/App.js | 8 ++++ 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/app_paths.go b/app_paths.go index ccffc7b..e245216 100644 --- a/app_paths.go +++ b/app_paths.go @@ -252,7 +252,7 @@ func (a *App) GetTempPngPath(id string) string { func (a *App) AddTempPngPath(id string, path string) { if !contains(allowedImageExtensionsPng, filepath.Ext(path)) { - runtime.LogError(appContext, "Extension is not allowed: "+path) + runtime.LogError(appContext, "Extension is not allowed: "+path+" ,Full path: "+path) return } @@ -366,6 +366,25 @@ func (a *App) UploadSelectImage(id string) SelectImage { return selectImage } +func (a *App) SetTempImage(id string, path string) error { + selectImage, ok := selectImages.Get(id) + if !ok { + return errors.New("select image not found") + } + + if selectImage.HasTemp { + a.RemoveTempPng(id) + } + + selectImage.TempPath, _ = filepath.Rel(appFolder, path) + selectImage.HasTemp = true + selectImages.Set(id, selectImage) + + runtime.LogDebugf(appContext, "Set temp image: %s", path) + + return nil +} + func (a *App) SetSelectImage(id string, path string) { selectImage := a.GetSelectImage(id, path) @@ -433,3 +452,36 @@ func (a *App) ActionSelectImage(id string) SelectImage { func (a *App) ClearSelectImages() { selectImages.Clear() } + +func (a *App) SetImageIfAbsent(id string, path string) { + path = ConvertToFullPath(path) + + runtime.LogInfo(appContext, "SetImageIfAbsent path: "+path) + + if path == "" { + runtime.LogInfo(appContext, "SetImageIfAbsent: path is empty") + return + } + + selectImage := a.GetSelectImage(id, path) + if selectImage.Id == "" { + runtime.LogInfo(appContext, "SetImageIfAbsent: select image is empty") + return + } + + if !(selectImage.HasOriginal || selectImage.HasTemp) { + a.AddTempPngPath(id, path) + tempPngPath, ok := tempPngPaths[id] + + if ok { + err := a.SetTempImage(id, tempPngPath) + if err != nil { + runtime.LogError(appContext, "SetImageIfAbsent: error setting temp image: "+err.Error()) + } + } else { + runtime.LogInfo(appContext, "SetImageIfAbsent: tempPngPath is empty") + } + } else { + runtime.LogInfo(appContext, "SetImageIfAbsent: hasOriginal or hasTemp is true") + } +} diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 317fafd..13eaaa0 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -59,12 +59,14 @@ import { GetIconFiles, GetIconPack, GetIconPackList, + IconLocation, ImportIconPack, Name, ReadLastTab, SetIconPackField, SetIconPackFiles, SetIconPackMetadata, + SetImageIfAbsent, UUID, } from "@/wailsjs/go/main/App"; import { main } from "@/wailsjs/go/models"; @@ -854,7 +856,12 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { const [loading, setLoading] = useState(true); const [files, setFiles] = useState(); - const [updateArray, setUpdateArray] = useState(Array.from({ length: 4096 }, (_, i) => i)); + const [updateArray, setUpdateArray] = useState( + Array.from({ length: 4096 }, (_, i) => i) + ); + const [updateArray2, setUpdateArray2] = useState( + Array.from({ length: 4096 }, (_, i) => i) + ); const [addIconsFromDesktopRunning, setAddIconsFromDesktopRunning] = useState(false); @@ -1066,6 +1073,7 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { return newArray; }); }} + key={updateArray2[index]} editable />
@@ -1112,7 +1120,7 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { } if (file.destinationPath === "") { Destination(value).then((destinationPath) => { - console.log("d: "+destinationPath); + console.log("d: " + destinationPath); handleInputChange( index, "destinationPath", @@ -1120,6 +1128,19 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { ); }); } + + SetImageIfAbsent(file.id, value).then(() => { + setUpdateArray((prevUpdateArray) => { + const newArray = [...prevUpdateArray]; + newArray[index] = prevUpdateArray[index] + 1; + return newArray; + }); + setUpdateArray2((prevUpdateArray) => { + const newArray = [...prevUpdateArray]; + newArray[index] = prevUpdateArray[index] + 1; + return newArray; + }); + }); }} label={"Path"} /> diff --git a/frontend/src/wailsjs/go/main/App.d.ts b/frontend/src/wailsjs/go/main/App.d.ts index 7e40930..a7e881a 100644 --- a/frontend/src/wailsjs/go/main/App.d.ts +++ b/frontend/src/wailsjs/go/main/App.d.ts @@ -110,8 +110,12 @@ export function SetIconPackFiles(arg1:string,arg2:Array):Promise< export function SetIconPackMetadata(arg1:string,arg2:main.Metadata):Promise; +export function SetImageIfAbsent(arg1:string,arg2:string):Promise; + export function SetSelectImage(arg1:string,arg2:string):Promise; +export function SetTempImage(arg1:string,arg2:string):Promise; + export function UUID():Promise; export function Update(arg1:string):Promise; diff --git a/frontend/src/wailsjs/go/main/App.js b/frontend/src/wailsjs/go/main/App.js index d0b7b0e..357f301 100644 --- a/frontend/src/wailsjs/go/main/App.js +++ b/frontend/src/wailsjs/go/main/App.js @@ -218,10 +218,18 @@ export function SetIconPackMetadata(arg1, arg2) { return window['go']['main']['App']['SetIconPackMetadata'](arg1, arg2); } +export function SetImageIfAbsent(arg1, arg2) { + return window['go']['main']['App']['SetImageIfAbsent'](arg1, arg2); +} + export function SetSelectImage(arg1, arg2) { return window['go']['main']['App']['SetSelectImage'](arg1, arg2); } +export function SetTempImage(arg1, arg2) { + return window['go']['main']['App']['SetTempImage'](arg1, arg2); +} + export function UUID() { return window['go']['main']['App']['UUID'](); } From 7a72a17dd5764a1f6fdce03f4a1bcfef4719ba81 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 23 Aug 2024 22:55:56 +0300 Subject: [PATCH 165/221] remove unused import --- frontend/src/components/Packs.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 13eaaa0..0698304 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -59,7 +59,6 @@ import { GetIconFiles, GetIconPack, GetIconPackList, - IconLocation, ImportIconPack, Name, ReadLastTab, From 9f813744b6a38ade331d65338e3f2f29bb852932 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Fri, 23 Aug 2024 23:39:29 +0300 Subject: [PATCH 166/221] reset corrupted configs --- config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index fb87a59..8a2c48d 100644 --- a/config.go +++ b/config.go @@ -104,11 +104,11 @@ func config_init() error { } err = ReadConfig(configPath) if err != nil { - return errors.New("failed to read config file") + config = GetDefaultConfig() + } else { + merge_defaults() } - merge_defaults() - return nil } From 73579d96de0cdaa4886880bfdb954054219a9c6d Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 27 Aug 2024 13:18:20 +0300 Subject: [PATCH 167/221] add folder button --- frontend/src/components/Packs.tsx | 77 ++++++++++++++++++++++++++++++- iconpack.go | 2 +- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 0698304..140cab7 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -11,6 +11,7 @@ import { Switch } from "@/components/ui/switch"; import { Download, Edit, + Folder, FolderOpen, Loader2, LucideTrash, @@ -40,6 +41,7 @@ import { AddDeletePngRelativePath, AddFilesToIconPackFromDesktop, AddFilesToIconPackFromPath, + AddFileToIconPackFromPath, AddIconPack, ApplyIconPack, ClearDeletePngPaths, @@ -57,6 +59,7 @@ import { GetFileInfoFromPaths, GetFilePath, GetIconFiles, + GetIconFolder, GetIconPack, GetIconPackList, ImportIconPack, @@ -412,7 +415,12 @@ function PackContent({ const [addIconsFromDesktopRunning, setAddIconsFromDesktopRunning] = useState(false); const [addIconsRunning, setAddIconsRunning] = useState(false); - const running = applyRunning || addIconsFromDesktopRunning || addIconsRunning; + const [addFolderRunning, setAddFolderRunning] = useState(false); + const running = + applyRunning || + addIconsFromDesktopRunning || + addIconsRunning || + addFolderRunning; useEffect(() => { GetIconPack(iconPackId).then((pack) => { @@ -555,6 +563,24 @@ function PackContent({ }); }; + const handleAddFolder = () => { + GetIconFolder().then((folder) => { + if (folder) { + setAddFolderRunning(true); + + AddFileToIconPackFromPath(iconPackId, folder, true).then(() => { + GetIconPack(iconPackId) + .then((pack) => { + setIconPack(pack); + }) + .finally(() => { + setAddFolderRunning(false); + }); + }); + } + }); + }; + const openDialog = useCallback(() => { if (dialogRef.current) { setDeleteGeneratedIcons(false); @@ -740,6 +766,20 @@ function PackContent({ )} {t("my_packs.card.pack_actions.add_icons")} + +
@@ -865,7 +905,9 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { const [addIconsFromDesktopRunning, setAddIconsFromDesktopRunning] = useState(false); const [addIconsRunning, setAddIconsRunning] = useState(false); - const running = addIconsFromDesktopRunning || addIconsRunning; + const [addFolderRunning, setAddFolderRunning] = useState(false); + const running = + addIconsFromDesktopRunning || addIconsRunning || addFolderRunning; useEffect(() => { GetIconPack(iconPackId).then((pack) => { @@ -906,6 +948,24 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { }); }; + const handleAddFolder = () => { + GetIconFolder().then((folder) => { + if (folder) { + setAddFolderRunning(true); + + GetFileInfoFromPaths("temp", [folder]) + .then((fileInfos) => { + const oldFiles = files || []; + oldFiles.push(...fileInfos); + setFiles(oldFiles); + }) + .finally(() => { + setAddFolderRunning(false); + }); + } + }); + }; + const handleAddEmptyIcon = () => { setAddIconsRunning(true); @@ -1010,6 +1070,19 @@ function PackEdit({ iconPackId, setEditingIconPack }: PackEditProps) { )} {t("my_packs.card.pack_actions.add_icons")} + - - - )} + ) : null}
@@ -1308,11 +1292,20 @@ function PathInput({ } interface CreatePackFormProps { - loadPackInfo: () => void; - dialogCloseRef: React.RefObject; + reloadIconPacks?: () => void; + dialogCloseRef?: React.RefObject; + handleSave?: (metadata: main.Metadata) => void; + handleCancel?: () => void; + defaultValues?: main.Metadata; } -function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { +function CreatePackForm({ + reloadIconPacks, + dialogCloseRef, + handleSave, + handleCancel, + defaultValues, +}: CreatePackFormProps) { const { t } = useTranslation(); const formSchema = z.object({ @@ -1352,17 +1345,21 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { resolver: zodResolver(formSchema), defaultValues: { icon: "", - name: "", - version: "v1.0.0", - author: "", + name: defaultValues?.name || "", + version: defaultValues?.version || "v1.0.0", + author: defaultValues?.author || "", }, }); function onSubmit(data: z.infer) { - AddIconPack(data.name, data.version, data.author).then(() => { - loadPackInfo(); - dialogCloseRef.current?.click(); - }); + if (reloadIconPacks) { + AddIconPack(data.name, data.version, data.author).then(() => { + reloadIconPacks(); + dialogCloseRef?.current?.click(); + }); + } else { + handleSave?.(main.Metadata.createFrom(data)); + } } useEffect(() => { @@ -1377,29 +1374,32 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { - ( - - - {t("my_packs.card.pack_information.information.icon.label")} - - - - - )} - /> + {!defaultValues && ( + ( + + + {t("my_packs.card.pack_information.information.icon.label")} + + + + + )} + /> + )} ( - + - {" "} {t("my_packs.card.pack_information.information.name.label")} @@ -1419,7 +1419,7 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { control={form.control} name="version" render={({ field }) => ( - + {t("my_packs.card.pack_information.information.version.label")} @@ -1440,7 +1440,7 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { control={form.control} name="author" render={({ field }) => ( - + {t("my_packs.card.pack_information.information.author.label")} @@ -1457,9 +1457,23 @@ function CreatePackForm({ loadPackInfo, dialogCloseRef }: CreatePackFormProps) { )} /> - + {!defaultValues ? ( + + ) : ( +
+ + +
+ )} ); From d90f75327a0000d107484ebdf4fa2b7623899f98 Mon Sep 17 00:00:00 2001 From: Bedirhan Yenilmez Date: Tue, 27 Aug 2024 15:02:16 +0300 Subject: [PATCH 169/221] fix message layout --- frontend/src/components/Packs.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Packs.tsx b/frontend/src/components/Packs.tsx index 66a21fa..626fc5b 100644 --- a/frontend/src/components/Packs.tsx +++ b/frontend/src/components/Packs.tsx @@ -419,7 +419,8 @@ function PackContent({ applyRunning || addIconsFromDesktopRunning || addIconsRunning || - addFolderRunning || editingMetadata; + addFolderRunning || + editingMetadata; useEffect(() => { GetIconPack(iconPackId).then((pack) => { @@ -602,7 +603,9 @@ function PackContent({ editingMetadata ? "" : "gap-6" }`} > -
+
{editingMetadata && (
-
-
- {editingMetadata && ( - - )} - -
+
+
+
+ {editingMetadata && ( + + )} + +
-
- {!editingMetadata && - fields.map((field) => ( -
-
- {t( - "my_packs.card.pack_information.information." + - field + - ".label" - )} -
-
{iconPack.metadata[field]}
+ {!editingMetadata && ( +
+
+ {fields.map((field) => ( +
+
+ {t( + "my_packs.card.pack_information.information." + + field + + ".label" + )} +
+
+ {iconPack.metadata[field]} +
+
+ ))}
- ))} + {iconPack.metadata.description && ( +
+
+ {t( + "my_packs.card.pack_information.information.description.label" + )} +
+
+ {iconPack.metadata.description} +
+
+ )} +
+ )} {editingMetadata && (
-
- {!editingMetadata ? ( - <> - - - -
- - setDeleteGeneratedIcons(!deleteGeneratedIcons) - } - /> - -
-
- - ) : null} -
+ {!editingMetadata && ( +
+ + + +
+ + setDeleteGeneratedIcons(!deleteGeneratedIcons) + } + /> + +
+
+
+ )}
@@ -1342,6 +1357,11 @@ function CreatePackForm({ "my_packs.card.pack_information.information.author.message.author_max" ), }), + description: z.string().max(256, { + message: t( + "my_packs.card.pack_information.information.description.message.description_max" + ), + }), }); const form = useForm>({ @@ -1351,12 +1371,13 @@ function CreatePackForm({ name: defaultValues?.name || "", version: defaultValues?.version || "v1.0.0", author: defaultValues?.author || "", + description: defaultValues?.description || "", }, }); function onSubmit(data: z.infer) { if (reloadIconPacks) { - AddIconPack(data.name, data.version, data.author).then(() => { + AddIconPack(data.name, data.version, data.author, data.description).then(() => { reloadIconPacks(); dialogCloseRef?.current?.click(); }); @@ -1377,9 +1398,12 @@ function CreatePackForm({
{!defaultValues && ( @@ -1397,85 +1421,127 @@ function CreatePackForm({ )} /> )} - ( - - - {t("my_packs.card.pack_information.information.name.label")} - - - - - - - )} - /> - ( - - - {t("my_packs.card.pack_information.information.version.label")} - - - - - - - )} - /> - ( - - - {t("my_packs.card.pack_information.information.author.label")} - - - - - - - )} - /> - {!defaultValues ? ( +
+
+ ( + + + {t("my_packs.card.pack_information.information.name.label")} + + + + + + + )} + /> + ( + + + {t( + "my_packs.card.pack_information.information.version.label" + )} + + + + + + + )} + /> + ( + + + {t( + "my_packs.card.pack_information.information.author.label" + )} + + + + + + + )} + /> +
+
+ ( + + + {t( + "my_packs.card.pack_information.information.description.label" + )} + + +