Compare commits

..

100 Commits

Author SHA1 Message Date
Huakun Shen
3542eec277 feat: add GitHub Actions workflow for desktop test builds 2025-04-03 16:46:40 -04:00
Huakun
de3886d416
feat: implement clipboard data cleaning for data older than 10 days (#267)
* Update drizzle-orm to version 0.41.0 and implement clipboard cleanup functionality

- Updated drizzle-orm dependency in package.json and pnpm-lock.yaml to version 0.41.0.
- Added a new utility function `cleanClipboard` to remove clipboard entries older than 10 days.
- Integrated clipboard cleanup into the initialization process, logging success or failure.
- Refactored drizzle exports to include `proxyDB` for better access to the database proxy.
- Minor cleanup in the proxy.ts file to remove commented-out debug logs.

* Refactor clipboard cleanup logic to use configurable days parameter

- Introduced a variable `nDays` to allow dynamic adjustment of the clipboard cleanup threshold.
- Updated logging to reflect the configurable number of days for clipboard entry deletion instead of a hardcoded value.

* Enhance clipboard and database management in initialization process

- Added logging to `cleanClipboard` to indicate the number of clipboard entries older than a specified number of days.
- Introduced a new utility function `vacuumSqlite` for database maintenance, which is now called during app initialization.
- Updated the `init` function to await the completion of `cleanClipboard` and `vacuumSqlite` for better error handling and flow control.
- Ensured that the console attachment in `onMount` is awaited for proper synchronization.

* Update version in package.json from 0.1.37-beta.1 to 0.1.37

* Adds C11 standard flag for builds

Try to fix windows build beta CI
Adds the C11 standard flag to the build environment. This ensures
that the code is compiled using the C11 standard, potentially
improving compatibility and avoiding compiler-specific behavior.

* Remove CFLAGS for C11 standard from beta build workflow

This change eliminates the CFLAGS environment variable previously set for C11 standard compliance in the beta build process, streamlining the build configuration.
2025-04-03 12:42:27 -04:00
Huakun
bb9a46935c
Feature: add drizzle (#264)
* feat: add drizzle orm

* feat: update drizzle configuration and schema management

- Added a check for DB_FILE_NAME in drizzle.config.ts to ensure it's set.
- Updated package.json to change the package name to @kksh/drizzle and added exports for schema and relations.
- Enhanced README.md with instructions for using the schema generation.
- Refactored schema.ts for improved readability and organization of imports.

* add tauri-plugin-sql

* feat: add database select and execute commands

- Introduced `select` and `execute` functions in the database module to facilitate querying and executing SQL commands.
- Updated the Tauri plugin to expose these commands, allowing for database interactions from the frontend.
- Added corresponding permissions for the new commands in the permissions configuration.
- Enhanced the database library with JSON value handling for query parameters.

* fix: sqlite select command

* drizzle ORM verified working

* refactor: clean up database module by removing unused SelectQueryResult type and disabling eslint for explicit any usage

* pnpm lock update

* Update enum definition for type safety

- Changed enum to use 'as const' for better type inference
- Ensured more robust handling of extension publish sources

* reimplemented most db command functions with ORM (migrate from tauri command invoke

* fixed searchExtensionData orm function

* Refactor ORM commands and searchExtensionData function for improved readability and consistency

- Reformatted import statements for better organization.
- Cleaned up whitespace and indentation in searchExtensionData function.
- Enhanced readability of SQL conditions and query building logic.
- Disabled eslint for explicit any usage in the troubleshooters page.

* Fix test assertions in database module to use array indexing for results

format rust code

* update deno lock

* move drizzle from desktop to drizzle package

* update pnpm version and lock

* refactor: migrate db tauri commands to drizzle

* refactor: remove unused extension and command CRUD operations from db module
2025-04-01 06:15:10 -04:00
Huakun Shen
bf51fdadbc
Update version to 0.1.37-beta.1 and add loading animation translations for multiple languages 2025-03-28 08:54:18 -04:00
Huakun
9cf06b1835
Feature: custom transition animation (#266)
* Add loading animation to general settings

* Update dependencies and integrate @tauri-store/svelte

- Added `bon` and `bon-macros` packages to Cargo.lock.
- Upgraded `tauri-plugin-svelte`, `tauri-store`, and related packages to their latest versions.
- Updated `@tauri-store/svelte` integration in the desktop app, including changes to app configuration and layout handling.
- Adjusted pnpm-lock.yaml to reflect updated package versions and added new dependencies.
- Introduced a new app configuration file for development.

* Enhance loading animation handling in FullScreenLoading component

- Integrated conditional rendering for loading animations based on app configuration.
- Updated default loading animation to "kunkun-dancing" in app configuration.
- Adjusted general settings to ensure proper type handling for language labels.
- Modified ui-iframe component to manage full-screen loading state more effectively.

* remove a mis-placed config file

* Refactor window handling to ensure focus after showing

- Updated various components to use promise chaining with `show()` and `setFocus()` for better window management.
- Introduced `data.win` in multiple places to streamline access to the current webview window.
- Enhanced splashscreen and app layout handling to improve user experience by ensuring the window is focused after being shown.

* Refactor window handling to improve safety and consistency

- Introduced optional chaining for `data.win` to prevent potential runtime errors when accessing window methods.
- Updated various components to ensure proper handling of window focus and visibility.
- Enhanced the layout and extension pages to utilize the current webview window more effectively, improving overall user experience.
2025-03-28 07:45:25 -04:00
Huakun
48e2e47f96
Remove supabase (#263)
* remove supabase package

* upgrade valibot

* removed supabase package

Migration not complete yet

* update submodule

* fixed some supabase errors

* Add new fields to extension models

- Added `id` field to `ExtPublish`
- Expanded `DBExtension` with multiple new properties:
  - `api_version`, `author_id`, `created_at`,
  - `downloads`, `icon`, `identifier`,
  - `long_description`, `name`,
  - `readme`, `short_description`,
  - and `tarball_size`

* Refactor: clean up unused Supabase imports

- Removed commented-out Supabase imports from various files to streamline the codebase.
- Updated `created_at` type in `ExtPublish` model from `date` to `string` for consistency.

* update icon enum to union

* fix type errors after removing supabase

* format

* more types fixed

* feat: enhance command handling and update SDK version
2025-03-26 08:50:55 -04:00
Huakun
9fe51f6260
Feat: gitea mirror (#262)
* Update component props and add GitLab link

- Made `ref` prop optional in TauriLink component
- Added GitLab mirror URL to GitHubProvenanceCard
- Included a link to the GitLab mirror in the card layout
- Adjusted layout for StoreExtDetail component for better responsiveness
- Imported Tooltip component for potential future use

* chore: add parse-github-url dependency and update GitHub parsing logic

- Added `parse-github-url` package as a dependency in `package.json`.
- Updated `parseGitHubRepoFromUri` function to utilize `parse-github-url` for improved URI parsing.
- Introduced `getGitHubRepoMetadata` function to fetch repository metadata using Octokit.
- Updated validation data structure to include optional `repoId`.
- Enhanced tests to cover new functionality and error handling for invalid URIs.

* fix typo

* refactor: update validation data structure and improve function documentation

- Removed optional `repoId` from `ExtensionPublishValidationData` and adjusted related function to reflect this change.
- Added a note in the `validateJsrPackageAsKunkunExtension` function documentation to clarify frontend/backend verification logic.
- Updated `ExtPublishMetadata` to rename `repoId` to `repoNodeId` for clarity.

* refactor: remove GitLab mirror link from GitHubProvenanceCard

- Removed the GitLab mirror URL and its associated link from the GitHubProvenanceCard component.
- Commented out the layout for the GitLab mirror instead of deleting it, preserving the structure for potential future use.

* refactor: simplify GitHub repository URI parsing

- Removed dependency on `parse-github-url` and implemented a regex-based approach for parsing GitHub repository URIs in the `parseGitHubRepoFromUri` function.
- Enhanced error handling for invalid URIs while maintaining the function's output structure.

* feat: add Gitea mirror link to GitHubProvenanceCard

- Introduced a new link to the Gitea mirror repository in the GitHubProvenanceCard component.
- Updated the layout to reflect the new mirror link while removing the commented-out GitLab mirror section.

* refactor: enhance Globe component's location handling

- Updated the Globe component to conditionally render markers based on the provided locations prop.
- Simplified the destructuring of props for better readability.
- Retained default marker locations for cases where no locations are provided.

* pnpm lock
2025-03-26 01:08:16 -04:00
Huakun Shen
7759e615dd
Merge remote-tracking branch 'origin/develop' into develop 2025-03-23 10:26:22 -04:00
Huakun Shen
11226ee2ef
fix: jsr API for cloudflare worker env
Without this header will get html format instead of json in cf worker
2025-03-23 10:26:18 -04:00
Huakun
c39e98258c
Fix: kkrpc serialization backward compatibility (#256)
* update deno lock

* chore: update kkrpc and tauri-api-adapter versions, enhance serialization handling

- Bump kkrpc version to 0.2.2 in multiple packages including desktop and api.
- Update tauri-api-adapter version to 0.3.27.
- Introduce a new utility function to determine kkrpc serialization based on API version.
- Refactor RPC channel initialization to include serialization version in desktop extension handling.
- Increment desktop package version to 0.1.36 and api package version to 0.1.7.

* chore: update dependencies in pnpm-lock and package.json

- Upgrade postcss version for autoprefixer to 8.5.3 in pnpm-lock.yaml.
- Add semver package with version 7.7.1 in package.json.
- Update CHANGELOG.md to reflect recent kkrpc upgrades and changes.
2025-03-19 03:01:40 -04:00
Huakun Shen
d27731d0e6
hotfix: hard coded debug logic in shell spawn API 2025-03-18 21:09:44 -04:00
Huakun
0bca6739a7
[feat] New sysinfo api, update dep (#251)
* update tauri-plugin-system-info submodule to latest commit cb32fe8

* Update dependencies to latest versions, including valibot to 1.0.0-rc.4 and kkrpc to 0.2.1 across multiple packages. Bump api package version to 0.1.6.

* Update desktop and API package versions; change development server port and URL
2025-03-18 08:42:39 -04:00
Joel Stüdle
993e276e72
add translations for german (de) (#249) 2025-03-17 01:14:12 -04:00
Huakun
310969e597
UI Updates (#246)
* minor ui updates to shiki

* feat: add markdown renderer

* feat(ui): add scroll area component and expand markdown renderer

* feat(ui): expand markdown syntax highlighting with additional language support

* feat(ui): add markdown language support to syntax highlighting

* feat(ui): update markdown syntax highlighting theme to GitHub Dark Default

* feat(ui): add bash language support to markdown syntax highlighting

* feat: add globe component

* Change RetroGrid bg color

* feat: add headless command list to store detail component

* feat: update markdown renderer

Replace svelte-markdown with svelte-exmarkdown, with custom tauri link renderer and code highlight support

* format and fix eslint
2025-03-13 21:30:52 -04:00
Huakun
cd7301255b
perf: remove deno and ffmpeg instruction from onboard; use tauri-plugin-svelte store (#245) 2025-03-13 21:06:33 -04:00
Huakun
b4afcaac6c
UI updates/fixes (#244)
* fix: change icon in manifest

* refactor(ui): move BorderBeam component to ui package and update imports

* feat(ui): add new animation components and keyframes utility

* chore(deps): remove svelte-motion and related dependencies

* chore(deps): add svelte-motion and related dependencies

* fix(ui): eslint

* fix: extension store demo image display

* fix(ui): go to settings item in dropdown menu

* format
2025-03-10 13:59:19 -04:00
Huakun
234f245a9c
Improve: add global loading screen (#237)
* refactor(desktop): move ext loading code in store from +page.ts to +page.svelte

try to solve blank screen on slow network

* Revert "refactor(desktop): move ext loading code in store from +page.ts to +page.svelte"

This reverts commit 4a0a695ce615cee695849c64746ba569680ff8c4.

* feat(desktop): add full-screen loading state and border beam animation

- Implement full-screen loading component with BorderBeam animation
- Add fullScreenLoading flag to appState store
- Update extension store pages to use full-screen loading
- Add border beam animation to Tailwind config
- Enhance page loading experience with visual feedback

* feat(desktop): add dance animation to loading screen and update imports

- Add Dance component to FullScreenLoading with subtle background effect
- Remove unused fade transition import from layout
- Update lz-string import in utils to use default import
- Clean up compress test imports

* feat(desktop): add back button to full-screen loading component

- Import ArrowLeftIcon and Constants from @kksh/ui
- Add back button with absolute positioning
- Remove "Go Home" text button
- Enhance loading screen with improved navigation

* refactor(desktop): update BorderBeam component to use Svelte 5 runes
2025-03-07 13:00:15 -05:00
Huakun Shen
cc7cea7fe9
fix(desktop): add extension store item selection handler
fix issue https://github.com/kunkunsh/kunkun/issues/233
2025-03-03 21:31:24 -05:00
Huakun
90ba943fb6
fix: windows app detect (#231)
* fix: applications-rs upgrade submodule

* chore(desktop): bump package version to 0.1.33
2025-03-03 08:41:05 -05:00
Huakun
6ffc6f1543
fix(api): update matchPathAndScope (#229)
Translate windows style back slash to posix style slash in order for minimatch to work.
https://www.npmjs.com/package/minimatch#windows
2025-03-03 05:22:20 -05:00
Huakun
2cbe45f6d1
feat(desktop): improve app icon handling for cross-platform support (#230)
- Add platform-specific icon path selection for Windows
- Enhance app command item rendering with dynamic icon resolution
- Modify icon loading to use applications-rs for Windows icon extraction
- Update Rust icon loading utility to provide more robust error handling
2025-03-03 05:22:08 -05:00
Huakun
a42d4d97eb
Fix: windows powershell window (#228)
* chore: bump desktop package version to 0.1.30

* chore: increase Node.js memory limit for desktop build process

* chore: configure Node.js memory limit for desktop build

* update tauri-plugin-shellx, hide powershell window in whereIsCommand
2025-03-03 05:21:42 -05:00
Huakun
5fc99ca26c
Fix: listview filter (#225)
* chore: bump desktop package version to 0.1.30

* chore: increase Node.js memory limit for desktop build process

* chore: configure Node.js memory limit for desktop build

* fix(desktop): list view filter mode
2025-03-02 12:51:53 -05:00
Huakun
41302a29ff
fix: list view item's action panel and listview undefined error (#224)
* fix: list view item's action panel and listview undefined error

* chore: increase Node.js memory limit for build processes

* chore: configure Node.js memory limit for Tauri build process

* refactor: delete unecessary ui component code
2025-03-01 21:43:23 -05:00
Huakun
8751fbeff4
feat: add custom configurable app search paths (#221)
* feat: add custom configurable app search paths

* feat(i18n): add English and Chinese translation for app search path settings

* format
2025-03-01 12:36:37 -05:00
Huakun Shen
6df1c9865a
chore: upgrade applications-rs submodule 2025-03-01 08:12:06 -05:00
Anshul Raj Verma
f09b2832e9
fix: show action icon in action panel (#219) 2025-03-01 05:57:21 -05:00
Huakun Shen
6555ebcfcb
chore: upgrade applications-rs submodule 2025-03-01 03:47:19 -05:00
Luca Giannini
9e52ea331e
exit and clear search term after onSelect (#217)
* clean path and exit after onSelect

* format

* do not clean linux app path, and clear search term

---------

Co-authored-by: Huakun Shen <huakun.shen@huakunshen.com>
2025-03-01 01:59:09 -05:00
Huakun
70f7d4131e
[feat] Improve list view with fuse search and virtual list (#215)
* chore: comment out auto install for on boarding, its behavior and speed is unpredictable

* fix: clear action when ui template exits

* fix: update extension command search store references

* feat(ui): implement virtual list with advanced search and section handling

- Add @tanstack/svelte-virtual for efficient list rendering
- Integrate Fuse.js for advanced search across list items and sections
- Create dynamic virtual list with support for section headers
- Enhance list view with flexible search and filtering capabilities
- Add new types and components for virtual list management

* chore(desktop): bump package version to 0.1.29
2025-02-28 07:47:09 -05:00
Huakun
97cd20906f
Feature: add extension api (hide and paste) (#210)
* feat: add paste API to extension API

* feat(desktop): enhance clipboard and hotkey utilities

- Add `hideAndPaste` utility function to simplify window hiding and clipboard pasting
- Adjust key press and sleep timings for more reliable input simulation
- Implement window focus listener in clipboard extension
- Bind input element reference for automatic focus management

* feat(permissions): enhance clipboard permission handling

- Update clipboard permission schema to include paste permission
- Modify clipboard API to check for paste permission before executing
- Refactor permission map and schema for more flexible permission management

* feat(desktop): refactor extension command search and rendering

- Add `svelte-inspect-value` for debugging
- Implement new `ExtCmds` component to replace `ExtCmdsGroup`
- Enhance extension command search with separate Fuse.js instances for installed and dev extensions
- Simplify extension command filtering and rendering logic
- Add derived stores for extension commands with improved type safety

* feat(desktop): improve extension command search filtering

* bump @kksh/api version

* feat(permissions): add clipboard paste permission description
2025-02-26 04:47:43 -05:00
Huakun
a92c266d32
Feature: fine grain kill API for extension (#201)
* upgrade tauri-plugin-shellx

* feat(shell): add killPid method to shell API with new permission

* fix: extension new window

* feat(shell): enhance process management and logging in extensions

- Add debug logging for extension process events
- Implement process tracking in UI worker
- Update shell API to support custom process recording
- Modify extension template to demonstrate process spawning
- Refactor shell command handling with improved error handling

* Add killPid extension API to @kksh/api

* chore(deps): update tauri-plugin-shellx-api to version 2.0.15

* pnpm lock

* chore(deps): update dependencies and lock file

- Upgrade ESLint to version 9.21.0
- Update @types/bun to version 1.2.3
- Bump various development dependencies
- Reorganize package.json dependencies in ui package

* chore(deps): update SvelteKit template dependencies

- Add @eslint/js version 9.21.0
- Update package.json dependency order
- Sync pnpm-lock.yaml with package.json changes

* chore: add eslint to desktop

* chore(deps): remove local tauri-plugin-shellx and use published version 2.0.15

* bump desktop to 0.1.28
2025-02-26 02:06:06 -05:00
Huakun
66135624b9
feat(desktop) Improve search (#202)
* feat: add lockHideOnBlur to prevent app hiding during dialogs

* feat: add Fuse.js for advanced search filtering across app sections

* chore: update .prettierignore and clean up imports in AddDevExtForm
2025-02-25 10:21:20 -05:00
Luca Giannini
8940d25245
fix: only show apps with name (#194)
* only show apps with name

* format

* update pnpm lock

---------

Co-authored-by: Huakun Shen <huakun.shen@huakunshen.com>
2025-02-22 09:54:06 -05:00
Luca Giannini
8d49f50495
fix: handle failing icon loading linux gracefully (#193) 2025-02-22 09:12:47 -05:00
Huakun
441abbcdae
Fix: clipboard paste timing (#191)
* fix(desktop): improve hotkey and paste functionality with refined key press timing

* fix(clipboard): streamline clipboard handling for macOS and add TODO for Windows/Linux support

* removed a comment
2025-02-22 07:21:15 -05:00
Huakun
330678cb45
Fix: linux app launch (#190)
* fix(desktop): update version to 0.1.26 and enhance app execution handling for Linux

* fix(desktop): remove redundant app path display in AppsCmds component
2025-02-22 05:36:53 -05:00
Huakun
52919b8d2f
fix(desktop): set macOS accessory activation policy to Accessory (#189) 2025-02-22 05:14:04 -05:00
Huakun
ed20f9a142
Fix: linux extension loading (#188)
* fix: change escape key behavior to navigate home instead of back

* feat: load custom ui extension with http server on Linux

Linux now uses the same loading approach as Windows due to a bug https://github.com/tauri-apps/tauri/issues/12767

* feat: add tauri-plugin-system-info-api dependency to deno.lock
2025-02-22 04:36:44 -05:00
Huakun
b5ea128aca
feat(shell): add hidden window style to PowerShell script execution (#182)
Modify PowerShell script execution to run with hidden window style in both TypeScript API and Rust plugin to prevent visible command windows
2025-02-22 03:00:00 -05:00
Luca Giannini
872b601338
crosslink issue on linux, and cleanup entire dir (#177)
* crosslink issue on linux, and cleanup entire dir

* moved copy_dir_all to rust

* using dircpy instead of diy due to complications

* refactor: move copy_dir_all to jarvis plugin

All commands are in jarvis plugin, this is more organized.
And this API will be exposed to extensions.

---------

Co-authored-by: Huakun Shen <huakun.shen@huakunshen.com>
2025-02-22 02:58:41 -05:00
Huakun
a0bd2d8573
Feature: enable clipboard paste for windows and linux (#185)
* feat(shell): add hidden window style to PowerShell script execution

Modify PowerShell script execution to run with hidden window style in both TypeScript API and Rust plugin to prevent visible command windows

* feat(desktop): implement cross-platform paste functionality in clipboard extension

* feat(desktop): enhance hotkey functionality with registration and update methods

* fix(desktop): remove unnecessary border from command root styling

* feat(desktop): add cross-platform paste support for clipboard extension

- Import Tauri OS plugin to detect platform
- Implement platform-specific paste methods for macOS, Windows, and Linux
- Add error handling for unsupported platforms
- Center windows in Tauri configuration

* feat(desktop): extend global key handler to support Linux control key for settings navigation
2025-02-22 00:41:35 -05:00
Luca Giannini
ec951bfc80
add toggle cli command to show/hide kunkun (#176)
* add toggle cli command

* cleaner

* keep unused imports
2025-02-21 06:28:08 -05:00
Huakun
4d90e2cf29
Feature: watch manifest and reload for dev extension (#181)
* feat(desktop): add file system watch support for extensions

- Enable file system watching for package.json and test files
- Update Cargo.toml to include file system watch feature
- Add console logging for file system events

* feat(desktop): add extension auto reload mechanism for dev extensions

- Implement `reloadExtension` method in extensions store
- Add Tauri event for reloading a single extension
- Update UI worker to watch package.json and trigger extension reload
- Add Linux dependencies for building from source in CONTRIBUTING.md
2025-02-21 05:59:25 -05:00
Huakun
8a9f6bcb09
UI Update (#179)
* feat: add publisher link to extension detail

* fix: improve IconMultiplexer and StoreExtDetail component rendering

* feat: add published date to extension details view

* chore: add moment.js and clean up imports in StoreExtDetail

* fix: support cloudflare worker

Otherwise cloudflare worker gets html instead of json

* refactor: move AppsCmds component to desktop app

* bump: version to 0.1.1

* fix: package.json fetching cors error

* fix: improve files field validation in verify command

* feat: make dropdown width in main search input dynamic

* feat(ui): add install button for web extension store

* update submodules

* format

* feat(desktop): disable clipboard auto paste for windows and linux

They are not implemented yet, current code only works for mac
2025-02-21 05:59:09 -05:00
Huakun
07c62e236c
feat(desktop): enhance clipboard extension with auto-paste functionality (#171)
- Add writeToClipboard utility function to handle different clipboard content types
- Implement paste() method to simulate keyboard paste action
- Update onItemSelected to hide app and auto-paste selected clipboard item
- Add core:app:allow-app-hide capability to support app hiding
2025-02-20 04:02:50 -05:00
Huakun Shen
ac6e2c3f78
chore(desktop): bump version to 0.1.24 2025-02-19 09:00:19 -05:00
Huakun Shen
b986121708
feat(desktop): improve app config loading,
Merge loaded config with default config
2025-02-19 08:58:29 -05:00
Huakun
369a9719fd
feat(desktop): implement quick install hotkey for store (#164) 2025-02-19 08:47:43 -05:00
Huakun
9cfb59e7e4
fix: system command not triggerred (#165) 2025-02-19 07:56:36 -05:00
Huakun Shen
d3215d386d
docs(readme): refine extension request discussion link 2025-02-19 03:57:31 -05:00
Huakun Shen
0bd65db3e5
docs(readme): improve extension request section with direct submission link 2025-02-19 03:43:51 -05:00
Huakun Shen
dfd84db783
docs(readme): update extension request discussion link and formatting 2025-02-19 03:36:18 -05:00
Huakun Shen
60f442dafb
feat: add cache table to supabase 2025-02-19 03:06:07 -05:00
Huakun Shen
742cf3af09
docs(readme): add extension request section to README 2025-02-18 07:38:43 -05:00
Huakun
ba36b6226a
feat(desktop): expand file system access permissions to all paths, to be able to read from other drives (#138) 2025-02-18 03:56:48 -05:00
Huakun
eeeeaf1822
feat(desktop): add localized drag and drop strike separator text (#139)
* feat(desktop): add localized drag and drop strike separator text

* chore(desktop): format code and remove trailing whitespaces
2025-02-18 03:17:19 -05:00
Huakun Shen
71b88e0a22
feat(desktop): add autostart (#137)
* feat(desktop): add Tauri autostart plugin and update launch at login settings

* chore(ci): update beta build workflow defaults and matrix generation
2025-02-17 21:51:44 -05:00
Huakun Shen
513cf16d72
Improve CI (enable beta build for all PR) (#134)
* chore(desktop): configure publish workflow and update Tauri configuration

* chore(ci): enable beta build on develop pull requests

* chore(ci): add develop branch to CI workflow triggers

* chore(ci): enable default platform builds for beta workflow
2025-02-17 19:10:09 -05:00
Huakun Shen
41f864e996
chore(github): add "searched" checkbox to issue templates 2025-02-16 14:32:17 -05:00
Ivan Kachalkin
b119c4e8aa
chore(docs): add homebrew link (#130) 2025-02-16 13:35:25 -05:00
Huakun Shen
bbee92fa9f
chore(desktop): set minimum macOS system version to 10.15 (#125) 2025-02-16 04:18:51 -05:00
Nhan. Le Cong
162a8dd685
feat(i18n ): add vietnamese (#124) 2025-02-16 02:44:27 -05:00
Huakun Shen
b866967dda
Update Readme Layout (#118)
* docs(readme): improve project documentation layout and warning section

* docs(readme): refactor layout with flexbox for improved readability

* docs(readme): simplify resource links and add platform section

* docs(readme): restructure platforms section

* docs(readme): replace flexbox with table for resource and platform sections
2025-02-14 20:49:00 -05:00
Huakun Shen
a359c8b739
docs(readme): update project overview and add repository insights 2025-02-14 10:25:45 -05:00
Huakun Shen
b5848ecea5
docs(readme): add star history chart 2025-02-14 06:23:56 -05:00
Huakun Shen
63403b6118
fix(apps): windows app loading (#114)
upgrade applications submodule
2025-02-14 01:59:47 -05:00
Huakun Shen
03450e8a12 feat: add Português to settings 2025-02-14 00:28:38 -05:00
Nicolas Vyčas Nery
e36237facd
Feature: i18n - Added Portuguese translations (#108) 2025-02-14 00:20:38 -05:00
Huakun Shen
4221b574c9
fix(desktop): global hot key register during init must be after appConfig.init (#113) 2025-02-13 23:13:35 -05:00
Huakun Shen
f1cace38d4
docs(readme): add development warning for Linux compatibility 2025-02-13 11:29:53 -05:00
Huakun Shen
c8112b43bc
feat(extension): add drag region to markdown view 2025-02-13 05:18:54 -05:00
Huakun Shen
e9dfaf519e
chore(deno): update deno lock file 2025-02-13 03:23:36 -05:00
Huakun Shen
474b0f59b3
revert: PR #103
@kksh/svelte5@0.1.16 seems to cause errors
2025-02-13 03:18:34 -05:00
Huakun Shen
6bd7d71df6
Fix: svelte template (#103)
* chore: bump @kksh/svelte5 to 0.1.16 and update dependencies

* chore: bump create-kunkun package version to 0.1.46

* chore: update pnpm lock
2025-02-12 09:26:11 -05:00
Huakun Shen
b28573713a
docs(readme): update demo video link and add YouTube thumbnail 2025-02-12 08:12:02 -05:00
Huakun Shen
bdf99ee196
perf(splashscreen): make splashscreen prerenderred, slightly faster (#98) 2025-02-11 08:29:47 -05:00
Huakun Shen
183af3fb84
fix(desktop): update deeplink route for extension store (#100)
* fix(desktop): update deeplink route for extension store

* chore(desktop): bump package version to 0.1.21
2025-02-11 08:29:36 -05:00
Huakun Shen
5573923a76
perf(desktop): reduce bundle size by ~8.5MB from shiki (#97)
* perf(desktop): reduce bundle size by ~8.5MB from shiki

Use fine grained shiki bundle

* ci: update GitHub Actions workflow build step description
2025-02-07 14:51:28 -05:00
Huakun Shen
0eacf01de2
update deno.lock 2025-02-07 05:11:29 -05:00
Huakun Shen
839bad6751
Feature: fix extension delete (#96)
* chore: improve database and extension handling

- Remove debug console log in extension uninstall
- Add ON DELETE CASCADE to extension-related foreign keys
- Enable foreign key constraints in database connection
- Update database file extensions from .db to .sqlite
- Modify command value generation for better identification

* fix: add small delay to onboarding page navigation

Without delay the page switch won't be triggered when window first loads
2025-02-07 04:43:24 -05:00
Huakun Shen
27fdff03d9
feat: expose helper API in headless and UI modules
- Add helper API to headless module exports
- Update UI custom module to include helper API
- Bump package version to 0.1.3
2025-02-07 02:45:57 -05:00
Huakun Shen
7b6c0934ab
Feature: add helper api (#95)
* feat: add helper API for installation guides and update UI components

- Implement helperAPI with methods to navigate to installation guides for Deno, FFmpeg, and Homebrew
- Update extension and help page components to use new helper API
- Modify command filtering in builtin commands
- Adjust page navigation in help pages to use goHome instead of goBack
- Remove unused imports and clean up components

* chore: bump @kksh/api to 0.1.2 and update dependent packages
2025-02-07 02:41:50 -05:00
Huakun Shen
490368428e
UI (#94)
* feat: add publisher link to extension detail

* fix: improve IconMultiplexer and StoreExtDetail component rendering

* feat: add published date to extension details view

* chore: add moment.js and clean up imports in StoreExtDetail

* fix: support cloudflare worker

Otherwise cloudflare worker gets html instead of json

* refactor: move AppsCmds component to desktop app

* bump: version to 0.1.1

* fix: package.json fetching cors error

* fix: improve files field validation in verify command
2025-02-07 01:26:56 -05:00
Huakun Shen
e49c0f5da5
update: deno.lock 2025-02-06 22:08:43 -05:00
Huakun Shen
f37605f9a2
Refactor: rename api subpackage (#93)
* refactor(api): rename ui subpackage name

* refactor(api): update import paths for template UI schemas

* chore: update dependencies and bump package versions

* chore(api): bump package version to 0.1.1

* refactor(api): rename IUiIframe to IUiCustom and related types

* format
2025-02-06 21:54:35 -05:00
Huakun Shen
872bcfdfd1
Feature: app launcher (#92)
* feat: implement app loader (has performance problem)

* feat: enhance command filtering and search functionality

- Implement command score filtering for various command types
- Add filtered stores for quick links, system commands, and extensions
- Update command components to use new filtering mechanism
- Improve search experience by dynamically filtering results
- Refactor command value handling to use direct name matching
2025-02-06 20:29:56 -05:00
Huakun Shen
f895594b62
chore: update vitest and deno.lock dependencies 2025-02-05 12:51:12 -05:00
Huakun Shen
46d6872614
refactor: Command class rename (#90)
* refactor: rename WorkerExtension to TemplateUiCommand, HeadlessWorkerExtension to HeadlessCommand

* ci: update npm publish workflow to include refactor branch

* ci: add push trigger for JSR publish workflow and bump API package version

* ci: add pnpm setup to npm publish workflow

* chore: add repository field to package.json for @kksh/api
2025-02-05 12:16:33 -05:00
dependabot[bot]
b4b7851366
chore(deps-dev): bump vitest from 2.1.5 to 2.1.9 (#89)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 2.1.5 to 2.1.9.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.9/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 15:43:31 -05:00
Huakun Shen
4862484857
test: remove build commad and its tests from cli app 2025-02-04 10:06:51 -05:00
Huakun Shen
4626acf534
chore: update jsr.json exports to add headless module 2025-02-02 11:24:11 -05:00
Huakun Shen
2fd119f842
chore: remove supabase exports from jsr.json 2025-02-02 11:21:20 -05:00
Huakun Shen
892c31ae31
chore: update deno.lock 2025-02-02 11:18:57 -05:00
Huakun Shen
e260264797
refactor: alias WorkerExtension to TempalteUiExtension 2025-02-02 11:12:58 -05:00
Huakun Shen
798cc773e5
Feature: upgrade kkrpc (#84)
* feat: upgrade kkrpc and tauri-api-adapter

* feat: update package.json exports for @kksh/api

* chore: bump @kksh/api package version to 0.0.56

* chore: bump @kksh/api version to 0.0.56
2025-01-31 05:07:04 -05:00
Huakun Shen
33e4451be2
Fix: action panel (#83)
* fix: change action trigger hotkey on win and linux from control + k to alt + k

* chore: Update macOS build matrix to use macos-13 instead of macos-12

* feat: improve action panel, disable actions vimBindings
2025-01-29 18:56:16 -05:00
Huakun Shen
0bb59e4f66
Fix: ext window loading (#82)
* fix: extension new window loading with localStorage

* fix: extension loading in new window

* upgrade: @kksh/svelte5

* refactor: update SideBar import to Sidebar across desktop app

* fix: safely remove test directories with existsSync check

* feat: add open preference command with platform-specific shortcut

* chore: clean up vite config trailing comma

* fix: iframe custom ext loading

* fix: fix template extension loading

* feat: add progress bar to extension form and list views

* feat: add optional description to form view template
2025-01-28 09:44:46 -05:00
Huakun Shen
c93ebd895e
Fix: ext window loading (#81)
* fix: extension new window loading with localStorage

* fix: extension loading in new window

* upgrade: @kksh/svelte5

* refactor: update SideBar import to Sidebar across desktop app

* fix: safely remove test directories with existsSync check

* feat: add open preference command with platform-specific shortcut

* chore: clean up vite config trailing comma
2025-01-28 04:58:54 -05:00
Huakun Shen
63bc401d8e
fix: system command filtering 2025-01-27 23:10:05 -05:00
347 changed files with 17888 additions and 9984 deletions

View File

@ -11,11 +11,9 @@
"jarvis",
"form-view",
"@kksh/desktop",
"@kksh/supabase",
"@kksh/utils",
"@kksh/extension",
"@kksh/schema",
"@kksh/supabase",
"@kksh/ui"
]
}

View File

@ -15,7 +15,7 @@ body:
placeholder: Bug description
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
@ -40,4 +40,11 @@ body:
label: Contributes
options:
- label: I am willing to submit a PR to fix this issue
- label: I am willing to submit a PR with failing tests
- label: I am willing to submit a PR with failing tests
- type: checkboxes
id: searched
attributes:
label: Searched
options:
- label: I have searched for similar issues and found none

View File

@ -2,7 +2,7 @@ blank_issues_enabled: false
contact_links:
- name: GitHub Discussions
url: https://github.com/kunkunsh/kunkun/discussions
about: Please ask and answer questions here.
about: Discussions and questions here
- name: 💬 Discord
url: https://discord.gg/7dzw3TYeTU
about: Please ask and answer questions here.
about: Please ask and answer questions here

View File

@ -41,4 +41,9 @@ body:
options:
- label: I am willing to submit a PR to implement this feature
- type: checkboxes
id: searched
attributes:
label: Searched
options:
- label: I have searched for similar issues and found none

View File

@ -1,20 +1,23 @@
name: Build Beta Package
name: Build Beta
on:
schedule:
- cron: "22 22 * * *"
pull_request:
branches:
- develop
workflow_dispatch:
inputs:
updater:
description: "Enable updater?"
required: true
type: boolean
default: true
default: false
platform_windows:
description: "windows"
required: true
type: boolean
default: true
default: false
platform_linux:
description: "linux"
required: true
@ -90,8 +93,8 @@ jobs:
id: setting
run: |
matrix=""
if [ "${{ github.event_name }}" == "schedule" ]; then
matrix="\"windows-latest\",\"ubuntu-22.04\",\"macos-14\",\"macos-12\""
if [ "${{ github.event_name }}" == "schedule" ] || [ "${{ github.event_name }}" == "pull_request" ]; then
matrix="\"windows-latest\",\"ubuntu-22.04\",\"macos-14\",\"macos-13\""
build_mode=""
build_path="release"
retention_days='1'
@ -106,7 +109,7 @@ jobs:
matrix="${matrix}\"macos-14\","
fi
if [ "${{ inputs.platform_macos_x86_64 }}" == "true" ]; then
matrix="${matrix}\"macos-12\","
matrix="${matrix}\"macos-13\","
fi
if [ -z "${matrix}" ]; then
matrix="\"windows-latest\","
@ -137,8 +140,6 @@ jobs:
RETENTION_DAYS: ${{ needs.preprocess.outputs.retention_days }}
FILE_PREFIX: ${{ needs.preprocess.outputs.file_prefix }}
# BUILD_TIME: ${{ needs.preprocess.outputs.build_time }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
NO_STRIP: true
steps:
- name: Checkout repository
@ -204,12 +205,15 @@ jobs:
run: pnpm prepare
- name: Build Packages
env:
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm build
- name: Build the app (Windows)
- name: Build the App
working-directory: apps/desktop
env:
CI: false
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm tauri build ${{ env.BUILD_MODE}} ${{ matrix.os == 'windows-latest' && '-b nsis' || '' }}
- name: Rename macos-aarch64
@ -217,7 +221,7 @@ jobs:
run: mv target/${{ env.BUILD_PATH }}/bundle/dmg/*.dmg target/${{ env.BUILD_PATH }}/bundle/dmg/${{ env.FILE_PREFIX }}-aarch64.dmg
- name: Rename macos-x86_64
if: matrix.os == 'macos-12'
if: matrix.os == 'macos-13'
run: mv target/${{ env.BUILD_PATH }}/bundle/dmg/*.dmg target/${{ env.BUILD_PATH }}/bundle/dmg/${{ env.FILE_PREFIX }}-amd64.dmg
- name: Rename windows

View File

@ -5,6 +5,7 @@ on:
pull_request:
branches:
- main
- develop
jobs:
build-test:
@ -51,6 +52,8 @@ jobs:
- name: Setup
run: pnpm prepare
- name: Build
env:
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm build
- name: JS Test
if: matrix.os == 'ubuntu-24.04'

View File

@ -15,16 +15,16 @@ jobs:
matrix:
settings:
- platform: "macos-14" # for Arm based macs (M1 and above).
args: "--target aarch64-apple-darwin --verbose"
args: "--target aarch64-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
- platform: "macos-13" # for Intel based macs.
args: "--target x86_64-apple-darwin --verbose"
args: "--target x86_64-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
# Universal Build no longer supported after adding openssl, which is not cross-compilable.
- platform: "macos-14" # for Both Arm and Intel based macs.
args: "--target universal-apple-darwin --verbose"
args: "--target universal-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
- platform: "ubuntu-22.04" # for Tauri v1 you could replace this with ubuntu-20.04.
args: "--verbose"
args: "--verbose --config src-tauri/tauri.conf.publish.json"
- platform: "windows-latest"
args: "--verbose"
args: "--verbose --config src-tauri/tauri.conf.publish.json"
runs-on: ${{ matrix.settings.platform }}
steps:
@ -87,6 +87,8 @@ jobs:
# pnpm --filter=@kksh/ci run ci-env-check
bun packages/ci/scripts/ci-env-check.ts
- name: Build Packages
env:
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm build
- name: Get App Version
if: matrix.settings.platform == 'windows-latest'
@ -96,7 +98,9 @@ jobs:
- uses: tauri-apps/tauri-action@v0
env:
CI: false
KUNKUN_PUBLISH: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}

View File

@ -1,5 +1,7 @@
name: JSR Publish
on:
push:
branches: [develop, main]
workflow_dispatch:
jobs:

39
.github/workflows/npm-publish.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: NPM Package Publish
on:
push:
branches: [develop, main]
release:
types: [created]
workflow_dispatch:
jobs:
publish-npm:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "22.x"
registry-url: "https://registry.npmjs.org"
- uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm build
working-directory: packages/api
- name: Check if version is already published
working-directory: packages/api
run: |
PACKAGE_VERSION=$(node -p "require('./package.json').version")
npm view @kksh/api@$PACKAGE_VERSION
continue-on-error: true
id: check_version
- name: Publish
working-directory: packages/api
if: steps.check_version.outcome != 'success'
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

121
.github/workflows/test-build.yml vendored Normal file
View File

@ -0,0 +1,121 @@
name: "Desktop Test build"
on:
push:
branches:
- "test-build"
tags:
- "v*"
workflow_dispatch:
jobs:
publish-tauri:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
settings:
- platform: "macos-14" # for Arm based macs (M1 and above).
args: "--target aarch64-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
- platform: "macos-13" # for Intel based macs.
args: "--target x86_64-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
# Universal Build no longer supported after adding openssl, which is not cross-compilable.
- platform: "macos-14" # for Both Arm and Intel based macs.
args: "--target universal-apple-darwin --verbose --config src-tauri/tauri.conf.publish.json"
- platform: "ubuntu-22.04" # for Tauri v1 you could replace this with ubuntu-20.04.
args: "--verbose --config src-tauri/tauri.conf.publish.json"
- platform: "windows-latest"
args: "--verbose --config src-tauri/tauri.conf.publish.json"
runs-on: ${{ matrix.settings.platform }}
steps:
- uses: actions/checkout@v4
with:
submodules: "true"
- name: Install Dependencies (ubuntu only)
if: matrix.settings.platform == 'ubuntu-22.04' # This must match the platform value defined above.
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libxdo-dev
# You can remove the one that doesn't apply to your app to speed up the workflow a bit.
- name: Install protobuf (Mac)
if: startsWith(matrix.settings.platform, 'macos')
run: |
brew install protobuf
brew install openssl
- name: Install Protobuf (Ubuntu)
if: matrix.settings.platform == 'ubuntu-22.04'
run: |
sudo apt install -y protobuf-compiler
- uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: "pnpm" # Set this to npm, yarn or pnpm.
cache-dependency-path: ./pnpm-lock.yaml
- name: Install protoc and openssl for windows
if: matrix.settings.platform == 'windows-latest'
run: |
choco install protoc
choco install openssl
echo OPENSSL_DIR='C:\Program Files\OpenSSL' >> $env:GITHUB_ENV
echo OPENSSL_INCLUDE_DIR='C:\Program Files\OpenSSL\include' >> $env:GITHUB_ENV
echo OPENSSL_LIB_DIR='C:\Program Files\OpenSSL\lib\VC\x64\MDd' >> $env:GITHUB_ENV
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
targets: ${{ matrix.settings.platform == 'macos-14' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Add rust target (macos only)
if: matrix.settings.platform == 'macos-14'
run: |
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
- name: Rust Cache
uses: swatinem/rust-cache@v2
with:
workspaces: ". -> target"
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install Dependencies
run: pnpm install
- name: Environment Check
run: |
# pnpm --filter=@kksh/ci run ci-env-check
bun packages/ci/scripts/ci-env-check.ts
- name: Build Packages
env:
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm build
- name: Get App Version
if: matrix.settings.platform == 'windows-latest'
id: appversion
run: |
echo "version=$(node -p "require('./apps/desktop/package.json').version")" >> $env:GITHUB_OUTPUT
- uses: tauri-apps/tauri-action@v0
env:
CI: false
KUNKUN_PUBLISH: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: Kunkun-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
releaseName: "Kunkun v__VERSION__"
releaseBody: "See the assets to download this version and install."
releaseDraft: false
prerelease: false
args: ${{ matrix.settings.args }} ${{ contains(steps.appversion.outputs.version, 'beta') && matrix.settings.platform == 'windows-latest' && '-b nsis' || '' }}
projectPath: "./apps/desktop"

2
.gitmodules vendored
View File

@ -12,4 +12,4 @@
url = https://github.com/kunkunsh/tauri-plugin-user-input.git
[submodule "vendors/tauri-plugin-keyring"]
path = vendors/tauri-plugin-keyring
url = https://github.com/HuakunShen/tauri-plugin-keyring.git
url = https://github.com/HuakunShen/tauri-plugin-keyring.git

View File

@ -1,4 +1,5 @@
.svelte-kit/
target/
vendors/**
vendors
.nuxt/

View File

@ -10,5 +10,10 @@
"titleBar.activeForeground": "#FFFBFC"
},
"svelte.enable-ts-plugin": true,
"deno.enable": false
"deno.enable": false,
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.code-search": true
}
}

View File

@ -26,13 +26,15 @@ If you are interested in contributing to the project, please read the following
- [cmake](https://cmake.org/)
- MacOS: `brew install cmake`
- Linux: `sudo apt install -y cmake`
- Other Linux Dependencies
- `sudo apt-get install -y protobuf-compiler libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libxdo-dev`
### Setup
```bash
git clone https://github.com/kunkunsh/kunkun.git --recursive
pnpm install
pnpm prepare
pnpm build # build submodules
```
### Run Desktop App
@ -44,6 +46,15 @@ cd apps/desktop
pnpm tauri dev
```
### Build from Source
If you have problem running the app, consider building from source to see if it works.
```bash
cd apps/desktop
pnpm tauri build
```
## i188n
If you are willing to help with the translation, please use translations in json files in `apps/desktop/messages`.

1078
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,8 @@ tokio-util = "0.7.12"
mdns-sd = "0.11.1"
tauri-plugin-network = { path = "./vendors/tauri-plugin-network" }
tauri-plugin-keyring = { path = "./vendors/tauri-plugin-keyring" }
tauri-plugin-clipboard = "2.1.8"
tauri-plugin-shellx = { version = "2.0.16" }
tauri-plugin-clipboard = "2.1.11"
mac-security-rs = { path = "./packages/mac-security-rs" }
log = "0.4.22"
strum = "0.26"

120
README.md
View File

@ -1,36 +1,75 @@
# Kunkun
![kunkun](https://socialify.git.ci/kunkunsh/kunkun/image?description=1&forks=1&issues=1&logo=https%3A%2F%2Fstorage.huakun.tech%2F2024%2F9%2F12%2F4MjHiKK.png&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto)
> Kunkun is a cross-platform extensible app launcher like Raycast or Alfred.
> All extensions run in a sandboxed environment by default to ensure security.
## Demo Video and Instructions
- https://docs.kunkun.sh/guides/demo/
- Download extension from https://kunkun.sh/download
> [!WARNING]
> 🚧 Work in Progress 🚧
> This project is still in its early stages.
>
> We know its not perfect yet. The author is pouring heart, soul, and a few sleepless nights into fixing the issues. Your patience means everything.
>
> Got feedback or found a bug? Open an issue—it helps more than you know.
![GitHub last commit](https://img.shields.io/github/last-commit/kunkunsh/kunkun)
[![YouTube badge][]][YouTube link]
<a href="https://discord.gg/7dzw3TYeTU" style="display: flex; align-items: center; background-color: #444; width: fit-content; padding: 0.2em 0.5em; border-radius: 10px; ">
<img src="https://api.iconify.design/skill-icons/discord.svg" />
<span style="margin-left: 0.2em; color: white; font-family: monospace;">Discord</span>
</a>
- Website: https://kunkun.sh/
- Documentation: https://docs.kunkun.sh/
[![](https://dcbadge.limes.pink/api/server/7dzw3TYeTU)](https://discord.gg/7dzw3TYeTU)
[YouTube badge]: https://img.shields.io/youtube/channel/subscribers/UC1gJeFbvRcQXDC_C8nKetdA?style=social
[YouTube link]: https://www.youtube.com/@huakun
## Download
<table>
<tr>
<th>Demo Video and Instructions</th>
<th>Download</th>
<th>Platforms</th>
</tr>
<tr>
<td>
<ul>
<li><a href="https://youtu.be/HfQb38s8VjY">Introduction Video</a></li>
<li><a href="https://kunkun.sh/">Visit Website</a></li>
<li><a href="https://docs.kunkun.sh/">Documentation</a></li>
</ul>
</td>
<td>
<ul>
<li><a href="https://kunkun.sh/download/">From Website</a></li>
<li><a href="https://github.com/kunkunsh/kunkun/releases">From GitHub Releases</a></li>
<li><a href="https://formulae.brew.sh/cask/kunkun">Via Homebrew</a></li>
</ul>
</td>
<td>
<ul>
<li>MacOS</li>
<li>Linux</li>
<li>Windows</li>
</ul>
</td>
</tr>
</table>
- From the Website: https://kunkun.sh/download/
- From GitHub Releases: https://github.com/kunkunsh/kunkun/releases
<table>
<tr>
<th>Extension Request</th>
</tr>
<tr>
<td>
You can <a href="https://github.com/kunkunsh/kunkun/discussions/new?category=extension-requests&body=%3E%20%5B!IMPORTANT%5D%0A%3E%20Upvote%20if%20you%20want%20this">Submit Extension Request</a>
request in the
<a href="https://github.com/kunkunsh/kunkun/discussions/categories/extension-requests?discussions_q=is%3Aopen+sort%3Atop+category%3A%22Extension+Requests%22">Extension Requests discussion</a>
section to gauge interest in your request.
<br/>
If there is significant demand, the extension may be considered for implementation.
</td>
</tr>
## Platforms
</table>
- [x] MacOS
- [x] Linux
- [x] Windows
<a href="https://star-history.com/#kunkunsh/kunkun&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kunkunsh/kunkun&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kunkunsh/kunkun&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kunkunsh/kunkun&type=Date" />
</picture>
</a>
## Sample Extensions
@ -110,3 +149,40 @@
##### Clipboard History
![](https://i.imgur.com/uw1hJmG.png)
## Stats
![Alt](https://repobeats.axiom.co/api/embed/7105c01eb031bd6a88897d79c8713aa4251842e9.svg "Repobeats analytics image")
<!-- Copy-paste in your Readme.md file -->
<a href="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats?repo_id=882158748" target="_blank" style="display: block" align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=882158748&image_size=auto&color_scheme=dark" width="655" height="auto">
<img alt="Performance Stats of kunkunsh/kunkun - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=882158748&image_size=auto&color_scheme=light" width="655" height="auto">
</picture>
</a>
<!-- Made with [OSS Insight](https://ossinsight.io/) -->
<!-- Copy-paste in your Readme.md file -->
<a href="https://next.ossinsight.io/widgets/official/compose-org-activity-growth-total?activity=stars&period=past_28_days&owner_id=176965503" target="_blank" style="display: block" align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-org-activity-growth-total/thumbnail.png?activity=stars&period=past_28_days&owner_id=176965503&image_size=4x7&color_scheme=dark" width="657" height="auto">
<img alt="Stars trends of kunkunsh" src="https://next.ossinsight.io/widgets/official/compose-org-activity-growth-total/thumbnail.png?activity=stars&period=past_28_days&owner_id=176965503&image_size=4x7&color_scheme=light" width="657" height="auto">
</picture>
</a>
<!-- Made with [OSS Insight](https://ossinsight.io/) -->
<!-- Copy-paste in your Readme.md file -->
<a href="https://next.ossinsight.io/widgets/official/compose-org-overview-stars?period=past_28_days&owner_id=176965503" target="_blank" style="display: block" align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-org-overview-stars/thumbnail.png?period=past_28_days&owner_id=176965503&image_size=2x6&color_scheme=dark" width="561" height="auto">
<img alt="Overview of Stars earned of kunkunsh" src="https://next.ossinsight.io/widgets/official/compose-org-overview-stars/thumbnail.png?period=past_28_days&owner_id=176965503&image_size=2x6&color_scheme=light" width="561" height="auto">
</picture>
</a>
<!-- Made with [OSS Insight](https://ossinsight.io/) -->

View File

@ -1,5 +1,40 @@
# kksh
## 0.1.3
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.5
## 0.1.2
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.4
## 0.1.1
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.2
## 0.0.32
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.1
## 0.0.31
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.0
## 0.0.30
### Patch Changes

View File

@ -1,78 +0,0 @@
import os from "os"
import path from "path"
import { getRootDir } from "@/constants"
import type { BuildResult } from "@/types"
import { buildWithDockerAndValidate } from "@/utils"
import { $ } from "bun"
import { afterAll, expect, test } from "bun:test"
import fs from "fs-extra"
import { verifyCmd } from "../src/commands/verify"
const rootDir = getRootDir()
const createKKDir = path.join(rootDir, "../create-kunkun")
const createKKDistDir = path.join(createKKDir, "dist")
const createKKIndexjsPath = path.join(createKKDistDir, "index.mjs")
const testDir = path.join(os.tmpdir(), "kunkun-cli-test")
console.log("Test Dir: ", testDir)
const templateNames = ["react", "vue", "nuxt", "svelte", "sveltekit", "next", "template"]
fs.rmdirSync(testDir, { recursive: true })
fs.mkdirpSync(testDir)
const testTemplateDirs: string[] = []
for (const templateName of templateNames) {
const folderName = `${templateName}-ext`
await $`node ${createKKIndexjsPath} --outdir ${testDir} --name ${folderName} --template ${templateName}`
const templateDir = path.join(testDir, folderName)
console.log("templateDir", templateDir)
await $`pnpm install`.cwd(templateDir).quiet()
await $`pnpm build`.cwd(templateDir).quiet()
testTemplateDirs.push(templateDir)
}
test("Build And Verify", async () => {
for (const templateDir of testTemplateDirs) {
expect(verifyCmd(templateDir, false)).toBeTrue()
}
})
const testDirDocker = path.join(os.tmpdir(), "kunkun-cli-test-docker")
fs.rmdirSync(testDirDocker, { recursive: true })
fs.mkdirpSync(testDirDocker)
const templateData: Record<string, { dir: string; buildResult: BuildResult }> = {}
await Promise.all(
templateNames.map(async (templateName) => {
const folderName = `${templateName}-ext`
await $`node ${createKKIndexjsPath} --outdir ${testDirDocker} --name ${folderName} --template ${templateName}`
const templateDir = path.join(testDirDocker, folderName)
console.log("templateDir:", templateDir)
const buildResult = await buildWithDockerAndValidate(templateDir)
templateData[templateName] = {
dir: templateDir,
buildResult
}
})
)
console.log(templateData)
test("Template Exist", () => {
Object.entries(templateData).forEach(async ([templateName, { dir }]) => {
console.log("Expect dir exist: ", dir)
expect(fs.existsSync(dir)).toBeTrue()
})
})
test("Build Result Tarball Exist", () => {
Object.entries(templateData).forEach(async ([templateName, { buildResult, dir }]) => {
const expectedTarballPath = path.join(dir, buildResult.tarballFilename)
expect(fs.existsSync(expectedTarballPath)).toBeTrue()
})
})
afterAll(() => {
fs.rmdirSync(testDir, { recursive: true })
fs.rmdirSync(testDirDocker, { recursive: true })
})

View File

@ -1,8 +1,8 @@
#!/usr/bin/env node
import fs from "fs"
import path from "path"
import { buildCmd, verifyCmd } from "@/commands"
import { getDockerFolder, NODE_ENV } from "@/constants"
import { verifyCmd } from "@/commands"
import { NODE_ENV } from "@/constants"
import logger from "@/logger"
import { program } from "commander"
import { version } from "./package.json"
@ -39,13 +39,4 @@ program
}
})
program
.command("build [project_path]")
.option("--entrypoint [path]", "Use custom entrypoint.sh (for debug purpose)")
.description("Build extension with docker and validate (You must have docker installed)")
.action((projectPath: string | undefined, opts: { entrypoint?: string }) => {
logger.info("cwd:", cwd)
buildCmd(computeProjectDir(projectPath), opts.entrypoint)
})
program.parse()

View File

@ -1,9 +1,6 @@
export { buildWithDocker, buildWithDockerAndValidate } from "@/utils"
export type { BuildResult } from "@/types"
export {
verifyCustomUiCommand,
verifyTemplateUiCommand,
verifySingleProject,
verifyCmd
} from "@/commands/verify"
export { buildCmd } from "@/commands/build"

View File

@ -1,7 +1,7 @@
{
"name": "kksh",
"module": "dist/cli.js",
"version": "0.0.30",
"version": "0.1.3",
"type": "module",
"bin": {
"kksh": "./dist/cli.js",
@ -31,7 +31,7 @@
"debug": "^4.4.0",
"fs-extra": "^11.2.0",
"inquirer": "^10.1.2",
"valibot": "^1.0.0-beta.10"
"valibot": "^1.0.0"
},
"files": [
"dist"

View File

@ -1,20 +0,0 @@
import fs from "fs"
import path from "path"
import { getRootDir } from "@/constants"
import { buildWithDockerAndValidate } from "@/utils"
export async function buildCmd(projectPath: string, entrypoint?: string) {
const rootDir = getRootDir()
const entrypointPath = entrypoint
? fs.existsSync(entrypoint)
? entrypoint
: path.join(rootDir, entrypoint)
: undefined
const buildResult = await buildWithDockerAndValidate(
projectPath,
entrypointPath && fs.existsSync(entrypointPath) ? entrypointPath : undefined
)
console.log(buildResult)
}
export default buildCmd

View File

@ -1,2 +1 @@
export { default as verifyCmd } from "./verify"
export { default as buildCmd } from "./build"

View File

@ -52,7 +52,8 @@ export function verifySingleProject(projectPath: string): boolean {
logger.info(`name`, pkg.name)
logger.info(`version`, pkg.version)
logger.info(`identifier`, pkg.kunkun.identifier)
if (pkg.files.length === 0) {
if ((pkg.files?.length ?? 0) === 0) {
logger.warn(
`"files" field is empty, it is recommended to include only the necessary files, e.g. dist`
)

View File

@ -1,185 +0,0 @@
import { exec, spawn } from "child_process"
import crypto from "crypto"
import path from "path"
import { ExtPackageJson } from "@kksh/api/models"
import fs from "fs-extra"
import * as v from "valibot"
import { getDockerEntrypoint } from "./constants"
import logger from "./logger"
import type { BuildResult } from "./types"
/**
* Package Name can be scoped or not
* Use regex to extract package name
* @param packageName
* @param version
*/
export function computeTarballName(packageName: string, version: string): string {
const scoped = packageName.startsWith("@")
if (scoped) {
const [scope, name] = packageName.split("/")
return `${scope.substring(1)}-${name}-${version}.tgz`
} else {
return `${packageName}-${version}.tgz`
}
}
export function computeFileHash(filePath: string, algorithm: string): Promise<string> {
return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm)
const stream = fs.createReadStream(filePath)
stream.on("data", (data) => {
// @ts-ignore
hash.update(data)
})
stream.on("end", () => {
const shasum = hash.digest("hex")
resolve(shasum)
})
stream.on("error", (err) => {
reject(err)
})
})
}
export function computeFileSha1(filePath: string): Promise<string> {
return computeFileHash(filePath, "sha1")
}
export function computeFileSha512(filePath: string): Promise<string> {
return computeFileHash(filePath, "sha512")
}
export function computeHash(buffer: Buffer, algorithm: "sha1" | "sha256" | "sha512") {
const hash = crypto.createHash(algorithm)
// @ts-ignore
hash.update(buffer)
return hash.digest("hex")
}
/**
* Docker is used to build each individual extension for safety
* Packages could potentially modify other extensions if they share environment.
* There is also a possibility of leaking environment variables.
* docker run -v $(pwd)/scripts/docker/entrypoint.sh:/entrypoint.sh \
* -v $(pwd)/extensions/$ext:/workspace \
* -w /workspace --rm \
* --platform=linux/amd64 \
* node:20 /entrypoint.sh
* @param extPath
* @returns shasum of the tarball parsed from stderr output
*/
export function buildWithDocker(
extPath: string,
entrypoint?: string
): Promise<{
stderrShasum: string
stderrTarballFilename: string
pkg: ExtPackageJson
}> {
logger.info(`Building ${extPath}`)
return new Promise((resolve, reject) => {
const pkg = v.parse(ExtPackageJson, fs.readJsonSync(path.join(extPath, "package.json")))
const dockerEntrypoint = entrypoint ? entrypoint : getDockerEntrypoint()
logger.info("Docker Entrypoint", dockerEntrypoint)
const dockerCmd = `
run -v ${dockerEntrypoint}:/entrypoint.sh -v ${extPath}:/workspace -w /workspace --rm huakunshen/kunkun-ext-builder:latest /entrypoint.sh`
logger.info("dockerCmd", dockerCmd)
const args = dockerCmd
.split(" ")
.filter((arg) => arg.length > 0)
.filter((arg) => arg !== "\n")
const subprocess = spawn("docker", args)
let stderrShasum = ""
let stderrTarballFilename = ""
subprocess.stdout.on("data", (data) => {
console.log(`stdout: ${data}`)
})
subprocess.stderr.on("data", (data) => {
const dataStr = data.toString()
console.error(`stderr: ${dataStr}`)
// if (data instanceof String) {
if (dataStr.includes("npm notice shasum")) {
console.log("shasum found")
const shasumMatch = dataStr.match(/npm notice shasum:\s+([a-f0-9]+)/)
if (shasumMatch) {
stderrShasum = shasumMatch[1]
console.log("Parsed shasum:", stderrShasum)
}
}
if (dataStr.includes("npm notice filename:")) {
const tarballFilename = dataStr.match(/npm notice filename:\s+([^\s]+)/)
if (tarballFilename) {
stderrTarballFilename = tarballFilename[1]
console.log("Parsed tarball:", stderrTarballFilename)
}
} else if (dataStr.includes("filename:")) {
const tarballFilename = dataStr.match(/filename:\s+([^\s]+)/)
if (tarballFilename) {
stderrTarballFilename = tarballFilename[1]
console.log("Parsed tarball:", stderrTarballFilename)
}
}
// } else {
// console.error("data is not string");
// }
})
subprocess.on("close", (code) => {
console.log(`child process exited with code ${code}`)
if (stderrShasum.trim().length === 0 || stderrTarballFilename.trim().length === 0) {
return reject("shasum or tarball filename not found")
}
if (code !== 0) {
return reject(`child process exited with code ${code}`)
} else {
return resolve({ stderrShasum, stderrTarballFilename, pkg })
}
})
})
}
/**
* Use this function to build an extension with docker and validate the tarball
* If this passes, the tarball is ready to be inserted into the database
* @param extPath Extension Path
* @returns
*/
export function buildWithDockerAndValidate(
extPath: string,
entrypoint?: string
): Promise<BuildResult> {
return buildWithDocker(extPath, entrypoint)
.then((res) => {
const parsedTarballPath = path.join(extPath, res.stderrTarballFilename)
if (!fs.existsSync(parsedTarballPath)) {
console.error(`Tarball not found: ${parsedTarballPath}`)
process.exit(1)
}
return computeFileSha1(parsedTarballPath).then((computedShasum) => {
if (computedShasum !== res.stderrShasum) {
console.error(
`Shasum mismatch: Computed(${computedShasum}) !== Output from docker(${res.stderrShasum})`
)
process.exit(1)
} else {
console.log("Shasum matches")
}
return {
shasum: computedShasum,
tarballFilename: res.stderrTarballFilename,
tarballPath: parsedTarballPath,
extPath: extPath,
pkg: res.pkg
}
})
})
.catch((err) => {
console.error(err)
process.exit(1)
})
}

View File

@ -1,5 +1,40 @@
# create-kunkun
## 0.1.49
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.5
## 0.1.48
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.4
## 0.1.45
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.2
## 0.1.44
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.1
## 0.1.43
### Patch Changes
- Updated dependencies
- @kksh/api@0.1.0
## 0.1.42
### Patch Changes

View File

@ -15,7 +15,9 @@ const distDir = path.join(getRootDir(), "dist")
const indexjsPath = path.join(distDir, "index.mjs")
const templateNames = ["template", "react", "vue", "nuxt", "svelte", "sveltekit"]
fs.rmdirSync(testDir, { recursive: true })
if (fs.existsSync(testDir)) {
fs.rmdirSync(testDir, { recursive: true })
}
fs.mkdirpSync(testDir)
for (const templateName of templateNames) {
const folderName = `${templateName}-ext`

View File

@ -94,7 +94,7 @@ async function copyTemplate(templateTgz: string, targetFolderName: string): Prom
message: "Select an Extension Template",
choices: [
{
name: "Preset Template (Web Worker)",
name: "Template UI (Web Worker)",
value: "template",
description:
"Write regular logic in TypeScript in OOP manner to render extension UI based on predefined template."

View File

@ -1,7 +1,7 @@
{
"name": "create-kunkun",
"type": "module",
"version": "0.1.42",
"version": "0.1.49",
"bin": {
"create-kunkun": "dist/index.mjs"
},
@ -15,7 +15,7 @@
"@types/fs-extra": "^11.0.4",
"get-folder-size": "^5.0.0",
"tar": "^7.4.3",
"vitest": "^2.0.0"
"vitest": "^2.1.9"
},
"peerDependencies": {
"typescript": "^5.0.0"
@ -27,7 +27,7 @@
"commander": "^12.1.0",
"fs-extra": "^11.2.0",
"handlebars": "^4.7.8",
"valibot": "^1.0.0-beta.10"
"valibot": "^1.0.0"
},
"files": [
"dist"

13
apps/desktop/dev.ts Normal file
View File

@ -0,0 +1,13 @@
import { IconType } from "@kksh/api/models"
import { getExtensionsLatestPublishByIdentifier } from "@kksh/sdk"
const latestPublish = await getExtensionsLatestPublishByIdentifier({
path: {
identifier: "RAG1"
}
})
console.log(latestPublish)
// latestPublish
// console.log(typeof IconEnum.Iconify)
console.log(IconType.options)

View File

@ -0,0 +1,91 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"app_name": "KunKun",
"secondary_app_name": "KunKun",
"common_edit": "Bearbeiten",
"common_clear": "Löschen",
"common_check": "Prüfen",
"common_install": "Installieren",
"home_command_input_placeholder": "Suchen…",
"home_command_input_dropdown_quit": "Beenden",
"home_command_input_dropdown_developer_title": "Entwickler",
"home_command_input_dropdown_close_window": "Fenster schließen",
"home_command_input_dropdown_toggle_devtools": "Entwicklertools umschalten",
"home_command_input_dropdown_reload_window": "Fenster neu laden",
"home_command_input_dropdown_open_preference": "Einstellungen öffnen",
"home_command_input_dropdown_toggle_dev_extension_hmr": "Entwicklererweiterungen HMR umschalten",
"command_group_heading_dev_ext": "Entwicklererweiterungen",
"command_group_heading_ext": "Erweiterungen",
"command_group_heading_quick_links": "Quick Links",
"settings_menu_settings": "Einstellungen",
"settings_menu_general": "Allgemein",
"settings_menu_app_search_paths": "Verzeichnisse für Programme",
"settings_menu_developer": "Entwickler",
"settings_menu_extensions": "Erweiterungen",
"settings_menu_set_dev_ext": "Dev-Erweiterung festlegen",
"settings_menu_add_dev_ext": "Dev-Erweiterung hinzufügen",
"settings_menu_about": "Über",
"settings_general_launch_at_login": "Beim Systemstart öffnen",
"settings_general_hotkey": "Tastenkombination",
"settings_general_menu_bar_icon": "Menüleiste-Symbol",
"settings_general_hide_on_blur": "Automatisch ausblenden",
"settings_general_extension_auto_upgrade": "Erweiterungen automatisch aktualisieren",
"settings_general_dev_extension_hmr": "Entwicklererweiterungen HMR",
"settings_general_join_beta_updates": "Beta-Updates nutzen",
"settings_general_developer_mode": "Entwickler-Modus",
"settings_general_language": "Sprache",
"settings_general_loading_animation": "Ladeanimation",
"settings_app_search_paths_title": "Zusätzliche Verzeichnisse für die Programm-Suche",
"settings_app_search_paths_add_app_search_path": "Verzeichnis für Programm-Suche hinzufügen",
"settings_app_search_paths_table_col_search_path": "Suchpfad",
"settings_app_search_paths_table_col_depth": "Tiefe",
"settings_app_search_paths_table_col_actions": "Aktionen",
"settings_about_version": "Version",
"settings_about_author": "Autor",
"settings_about_source_code": "Quellcode",
"settings_about_extensions_source_code": "Quellcode für Erweiterungen",
"settings_about_check_for_updates": "Nach Updates suchen",
"settings_set_dev_ext_title": "Verzeichnis der Entwicklererweiterungen",
"settings_set_dev_ext_description": "Hier werden Entwicklererweiterungen installiert.",
"settings_set_dev_ext_enter_path": "Verzeichnis eingeben",
"settings_extensions_title": "Deine Erweiterungen",
"settings_extensions_table_col_name": "Name",
"settings_extensions_table_col_identifier": "Identifikator",
"settings_extensions_table_col_type": "Typ",
"settings_extensions_table_col_version": "Version",
"settings_extensions_table_col_uninstall": "Deinstallieren",
"settings_add_dev_ext_title": "Entwicklererweiterung hinzufügen",
"settings_add_dev_ext_description": "Es gibt vier Möglichkeiten, eine Erweiterung als Entwicklererweiterung zu installieren. Tarball-Archiv, lokales Verzeichnis, URL zu Tarball-Archiv oder NPM-Paketnamen.",
"settings_add_dev_ext_install_from_ext_folders": "Verzeichnis",
"settings_add_dev_ext_install_from_ext_files": "Tarball-Archiv",
"settings_add_dev_ext_drag_and_drop": "Drag and Drop",
"settings_add_dev_ext_drag_and_drop_strike": "Drag and Drop",
"settings_add_dev_ext_drag_and_drop2": "Verzeichnis oder Tarball-Archiv",
"settings_add_dev_ext_install_tarball_from_url": "Tarball-Archiv aus URL installieren",
"troubleshooters_sidebar_title": "Fehlerbehebung",
"troubleshooters_sidebar_extension_loading_title": "Ladevorgang",
"troubleshooters_sidebar_extension_window_title": "Darstellung",
"troubleshooters_sidebar_mdns_debugger_title": "MDNS-Debugger",
"troubleshooters_extension_window_title": "Fehlerbehebung für die Darstellung von Erweiterungen",
"troubleshooters_extension_window_refresh_every_second": "Jede Sekunde neu laden",
"troubleshooters_extension_window_refresh": "Neu laden",
"troubleshooters_extension_window_refreshed": "{count}x neu geladen",
"troubleshooters_extension_loading_title": "Fehlerbehebung für den Ladevorgang von Erweiterungen",
"troubleshooters_extension_loading_table_col_identifier": "Identifikator",
"troubleshooters_extension_loading_table_col_path": "Verzeichnis",
"troubleshooters_extension_loading_table_col_error": "Fehler"
}

View File

@ -15,6 +15,7 @@
"home_command_input_dropdown_close_window": "Close Window",
"home_command_input_dropdown_toggle_devtools": "Toggle Devtools",
"home_command_input_dropdown_reload_window": "Reload Window",
"home_command_input_dropdown_open_preference": "Open Preference",
"home_command_input_dropdown_toggle_dev_extension_hmr": "Toggle Dev Extension HMR",
"command_group_heading_dev_ext": "Dev Extensions",
@ -23,6 +24,7 @@
"settings_menu_settings": "Settings",
"settings_menu_general": "General",
"settings_menu_app_search_paths": "App Search Paths",
"settings_menu_developer": "Developer",
"settings_menu_extensions": "Extensions",
"settings_menu_set_dev_ext": "Set Dev Extension",
@ -38,6 +40,13 @@
"settings_general_join_beta_updates": "Join Beta Updates",
"settings_general_developer_mode": "Developer Mode",
"settings_general_language": "Language",
"settings_general_loading_animation": "Loading Animation",
"settings_app_search_paths_title": "Extra App Search Paths",
"settings_app_search_paths_add_app_search_path": "Add App Search Path",
"settings_app_search_paths_table_col_search_path": "Search Path",
"settings_app_search_paths_table_col_depth": "Depth",
"settings_app_search_paths_table_col_actions": "Actions",
"settings_about_version": "Version",
"settings_about_author": "Author",
@ -61,6 +70,7 @@
"settings_add_dev_ext_install_from_ext_folders": "Install from Extension Folders",
"settings_add_dev_ext_install_from_ext_files": "Install from Extension Tarball File",
"settings_add_dev_ext_drag_and_drop": "Drag and Drop",
"settings_add_dev_ext_drag_and_drop_strike": "Drag and Drop",
"settings_add_dev_ext_drag_and_drop2": "Extension Folder or Tarball",
"settings_add_dev_ext_install_tarball_from_url": "Install Tarball From URL",

View File

@ -0,0 +1,84 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"app_name": "KunKun",
"secondary_app_name": "KunKun",
"common_edit": "Editar",
"common_clear": "Limpar",
"common_check": "Verificar",
"common_install": "Instalar",
"home_command_input_placeholder": "Digite \"\/\" para buscar...",
"home_command_input_dropdown_quit": "Sair",
"home_command_input_dropdown_developer_title": "Desenvolvedor",
"home_command_input_dropdown_close_window": "Fechar Janela",
"home_command_input_dropdown_toggle_devtools": "Alternar Ferramentas de Desenvolvedor",
"home_command_input_dropdown_reload_window": "Recarregar Janela",
"home_command_input_dropdown_open_preference": "Abrir Preferências",
"home_command_input_dropdown_toggle_dev_extension_hmr": "Alternar HMR de Extensão de Desenvolvedor",
"command_group_heading_dev_ext": "Extensões de Desenvolvedor",
"command_group_heading_ext": "Extensões",
"command_group_heading_quick_links": "Links Rápidos",
"settings_menu_settings": "Configurações",
"settings_menu_general": "Geral",
"settings_menu_developer": "Desenvolvedor",
"settings_menu_extensions": "Extensões",
"settings_menu_set_dev_ext": "Definir Extensão de Desenvolvedor",
"settings_menu_add_dev_ext": "Adicionar Extensão de Desenvolvedor",
"settings_menu_about": "Sobre",
"settings_general_launch_at_login": "Iniciar ao Fazer Login",
"settings_general_hotkey": "Tecla de Atalho",
"settings_general_menu_bar_icon": "Ícone na Barra de Menu",
"settings_general_hide_on_blur": "Ocultar ao Perder Foco",
"settings_general_extension_auto_upgrade": "Atualização Automática de Extensões",
"settings_general_dev_extension_hmr": "HMR de Extensão de Desenvolvedor",
"settings_general_join_beta_updates": "Participar das Atualizações Beta",
"settings_general_developer_mode": "Modo Desenvolvedor",
"settings_general_language": "Idioma",
"settings_general_loading_animation": "Animação de Carregamento",
"settings_about_version": "Versão",
"settings_about_author": "Autor",
"settings_about_source_code": "Código Fonte",
"settings_about_extensions_source_code": "Código Fonte das Extensões",
"settings_about_check_for_updates": "Verificar Atualizações",
"settings_set_dev_ext_title": "Definir Caminho da Extensão de Desenvolvedor",
"settings_set_dev_ext_description": "Aqui é onde suas extensões serão instaladas.",
"settings_set_dev_ext_enter_path": "Digite o Caminho",
"settings_extensions_title": "Suas Extensões",
"settings_extensions_table_col_name": "Nome",
"settings_extensions_table_col_identifier": "Identificador",
"settings_extensions_table_col_type": "Tipo",
"settings_extensions_table_col_version": "Versão",
"settings_extensions_table_col_uninstall": "Desinstalar",
"settings_add_dev_ext_title": "Adicionar Extensão de Desenvolvedor",
"settings_add_dev_ext_description": "Existem 4 opções para instalar uma extensão em modo desenvolvedor. Você pode carregá-la de um arquivo tarball local, pasta local, URL remota de tarball ou nome de pacote npm.",
"settings_add_dev_ext_install_from_ext_folders": "Instalar de Pastas de Extensão",
"settings_add_dev_ext_install_from_ext_files": "Instalar de Arquivo Tarball de Extensão",
"settings_add_dev_ext_drag_and_drop": "Arraste e Solte",
"settings_add_dev_ext_drag_and_drop_strike": "Arraste e Solte",
"settings_add_dev_ext_drag_and_drop2": "Pasta ou Tarball da Extensão",
"settings_add_dev_ext_install_tarball_from_url": "Instalar Tarball da URL",
"troubleshooters_sidebar_title": "Solucionadores de Problemas",
"troubleshooters_sidebar_extension_loading_title": "Carregamento de Extensão",
"troubleshooters_sidebar_extension_window_title": "Janela da Extensão",
"troubleshooters_sidebar_mdns_debugger_title": "Depurador MDNS",
"troubleshooters_extension_window_title": "Solucionador de Problemas da Janela de Extensão",
"troubleshooters_extension_window_refresh_every_second": "Atualizar a Cada Segundo",
"troubleshooters_extension_window_refresh": "Atualizar",
"troubleshooters_extension_window_refreshed": "Atualizado {count} vezes",
"troubleshooters_extension_loading_title": "Solucionador de Problemas do Carregamento de Extensão",
"troubleshooters_extension_loading_table_col_identifier": "Identificador",
"troubleshooters_extension_loading_table_col_path": "Caminho",
"troubleshooters_extension_loading_table_col_error": "Erro"
}

View File

@ -15,6 +15,7 @@
"home_command_input_dropdown_close_window": "Закрыть окно",
"home_command_input_dropdown_toggle_devtools": "Вкл/выкл инструменты разработчика",
"home_command_input_dropdown_reload_window": "Перезагрузить окно",
"home_command_input_dropdown_open_preference": "Открыть настройки",
"home_command_input_dropdown_toggle_dev_extension_hmr": "Вкл/выкл горячую замену (HMR) у dev-расширений",
"command_group_heading_dev_ext": "Dev-расширения",
@ -38,6 +39,7 @@
"settings_general_join_beta_updates": "Получать бета-обновления",
"settings_general_developer_mode": "Режим разработчика",
"settings_general_language": "Язык",
"settings_general_loading_animation": "Анимация загрузки",
"settings_about_version": "Версия",
"settings_about_author": "Автор",
@ -60,8 +62,10 @@
"settings_add_dev_ext_description": "В режиме разработчика у вас есть четыре способа установки dev-расширений: из локального tar-архива, из локальной директории, по URL-адресу на tar-архив либо по названию пакета npm.",
"settings_add_dev_ext_install_from_ext_folders": "Установить из директори",
"settings_add_dev_ext_install_from_ext_files": "Установить из tar-архива",
"settings_add_dev_ext_drag_and_drop": "Локальные расширения",
"settings_add_dev_ext_drag_and_drop2": "в виде директории или tar-архива могут быть перемещены сюда",
"settings_add_dev_ext_drag_and_drop_strike": "Локальные расширения",
"settings_add_dev_ext_drag_and_drop": "Можете перетащить сюда расширения",
"settings_add_dev_ext_drag_and_drop2": "в виде директории или tar-архива",
"settings_add_dev_ext_install_tarball_from_url": "Установить tar-архив по URL-адресу",
"troubleshooters_sidebar_title": "Диагностика",

View File

@ -0,0 +1,84 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"app_name": "KunKun",
"secondary_app_name": "KunKun",
"common_edit": "Sửa",
"common_clear": "Xoá",
"common_check": "Kiểm tra",
"common_install": "Cài đặt",
"home_command_input_placeholder": "Gõ \"\/\" để tìm kiếm...",
"home_command_input_dropdown_quit": "Thoát",
"home_command_input_dropdown_developer_title": "Nhà phát triển",
"home_command_input_dropdown_close_window": "Đóng cửa sổ",
"home_command_input_dropdown_toggle_devtools": "Bật\/Tắt DevTools",
"home_command_input_dropdown_reload_window": "Khởi động lại",
"home_command_input_dropdown_open_preference": "Mở cài đặt",
"home_command_input_dropdown_toggle_dev_extension_hmr": "Bật\/tắt HMR cho tiện ích đang phát triển",
"command_group_heading_dev_ext": "Tiện Ích Mở Rộng Đang Phát Triển",
"command_group_heading_ext": "Tiện Ích Mở Rộng",
"command_group_heading_quick_links": "Liên kết nhanh",
"settings_menu_settings": "Cài đặt",
"settings_menu_general": "Chung",
"settings_menu_developer": "Nhà phát triển",
"settings_menu_extensions": "Tiện ích mở rộng",
"settings_menu_set_dev_ext": "Đường dẫn tiện ích",
"settings_menu_add_dev_ext": "Thêm tiện ích",
"settings_menu_about": "Thông tin",
"settings_general_launch_at_login": "Khởi động khi đăng nhập",
"settings_general_hotkey": "Phím tắt",
"settings_general_menu_bar_icon": "Biểu tượng thanh menu",
"settings_general_hide_on_blur": "Ẩn khi chuyển sang ứng dụng khác",
"settings_general_extension_auto_upgrade": "Tự động nâng cấp tiện ích mở rộng",
"settings_general_dev_extension_hmr": "HMR cho tiện ích mở rộng đang phát triển",
"settings_general_join_beta_updates": "Cài đặt cập nhật thử nghiệm (beta)",
"settings_general_developer_mode": "Chế độ nhà phát triển",
"settings_general_language": "Ngôn ngữ",
"settings_general_loading_animation": "Hình ảnh tải",
"settings_about_version": "Phiên bản",
"settings_about_author": "Tác giả",
"settings_about_source_code": "Mã nguồn",
"settings_about_extensions_source_code": "Mã nguồn tiện ích mở rộng",
"settings_about_check_for_updates": "Kiểm tra cập nhật",
"settings_set_dev_ext_title": "Đặt đường dẫn tiện ích mở rộng đang phát triển",
"settings_set_dev_ext_description": "Đây là đường dẫn cài đặt các tiện ích mở rộng của bạn.",
"settings_set_dev_ext_enter_path": "Nhập đường dẫn",
"settings_extensions_title": "Tiện ích mở rộng của bạn",
"settings_extensions_table_col_name": "Tên",
"settings_extensions_table_col_identifier": "Định danh",
"settings_extensions_table_col_type": "Loại",
"settings_extensions_table_col_version": "Phiên bản",
"settings_extensions_table_col_uninstall": "Gỡ cài đặt",
"settings_add_dev_ext_title": "Thêm tiện ích đang phát triển",
"settings_add_dev_ext_description": "Có 4 cách để cài đặt một tiện ích mở rộng trong chế độ nhà phát triển. Nạp nó từ file tarball, thư mục từ máy tính, file tarball từ liên kết ngoài, hoặc tên gói npm.",
"settings_add_dev_ext_install_from_ext_folders": "Cài Đặt Từ Thư Mục Tiện Ích",
"settings_add_dev_ext_install_from_ext_files": "Cài Đặt Từ File Tarball",
"settings_add_dev_ext_drag_and_drop": "Kéo và Thả",
"settings_add_dev_ext_drag_and_drop_strike": "Kéo và Thả",
"settings_add_dev_ext_drag_and_drop2": "Thư Mục Tiện Ích hoặc File Tarball",
"settings_add_dev_ext_install_tarball_from_url": "Cài Đặt File Tarball Từ Liên kết",
"troubleshooters_sidebar_title": "Khắc Phục Sự Cố",
"troubleshooters_sidebar_extension_loading_title": "Tải Tiện ích Mở Rộng",
"troubleshooters_sidebar_extension_window_title": "Cửa sổ Tiện ích Mở Rộng",
"troubleshooters_sidebar_mdns_debugger_title": "Trình Gỡ Lỗi MDNS",
"troubleshooters_extension_window_title": "Khắc Phục Sự Cố Cửa sổ Tiện ích Mở Rộng",
"troubleshooters_extension_window_refresh_every_second": "Làm mới mỗi giây",
"troubleshooters_extension_window_refresh": "Làm mới",
"troubleshooters_extension_window_refreshed": "Đã làm mới {count} lần",
"troubleshooters_extension_loading_title": "Khắc Phục Sự Cố Tải Tiện ích Mở Rộng",
"troubleshooters_extension_loading_table_col_identifier": "Định danh",
"troubleshooters_extension_loading_table_col_path": "Đường dẫn",
"troubleshooters_extension_loading_table_col_error": "Lỗi"
}

View File

@ -15,6 +15,7 @@
"home_command_input_dropdown_close_window": "关闭窗口",
"home_command_input_dropdown_toggle_devtools": "切换开发者工具",
"home_command_input_dropdown_reload_window": "重新加载窗口",
"home_command_input_dropdown_open_preference": "打开设置",
"home_command_input_dropdown_toggle_dev_extension_hmr": "切换开发插件 HMR",
"command_group_heading_dev_ext": "开发插件",
@ -23,6 +24,7 @@
"settings_menu_settings": "设置",
"settings_menu_general": "通用",
"settings_menu_app_search_paths": "应用搜索路径",
"settings_menu_developer": "开发者",
"settings_menu_extensions": "插件",
"settings_menu_set_dev_ext": "设置开发插件",
@ -38,6 +40,13 @@
"settings_general_join_beta_updates": "加入 Beta 更新",
"settings_general_developer_mode": "开发者模式",
"settings_general_language": "语言",
"settings_general_loading_animation": "加载动画",
"settings_app_search_paths_title": "额外应用搜索路径",
"settings_app_search_paths_add_app_search_path": "添加应用搜索路径",
"settings_app_search_paths_table_col_search_path": "搜索路径",
"settings_app_search_paths_table_col_depth": "深度",
"settings_app_search_paths_table_col_actions": "操作",
"settings_about_version": "版本",
"settings_about_author": "作者",
@ -61,6 +70,7 @@
"settings_add_dev_ext_install_from_ext_folders": "从插件文件夹安装",
"settings_add_dev_ext_install_from_ext_files": "从插件 tarball 文件安装",
"settings_add_dev_ext_drag_and_drop": "拖放",
"settings_add_dev_ext_drag_and_drop_strike": "拖放",
"settings_add_dev_ext_drag_and_drop2": "插件文件夹或 tarball",
"settings_add_dev_ext_install_tarball_from_url": "从 tarball URL 安装",

View File

@ -1,6 +1,6 @@
{
"name": "@kksh/desktop",
"version": "0.1.19",
"version": "0.1.37",
"description": "",
"type": "module",
"scripts": {
@ -16,61 +16,71 @@
"license": "MIT",
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@inlang/paraglide-sveltekit": "0.15.5",
"@inlang/paraglide-sveltekit": "0.16.0",
"@kksh/drizzle": "workspace:*",
"@kksh/extension": "workspace:*",
"@kksh/supabase": "workspace:*",
"@kksh/svelte5": "^0.1.15",
"@kksh/ui": "workspace:*",
"@kksh/utils": "workspace:*",
"@std/semver": "npm:@jsr/std__semver@^1.0.3",
"@supabase/supabase-js": "^2.48.0",
"@tanstack/table-core": "^8.20.5",
"@tauri-apps/api": "^2.1.1",
"@std/semver": "npm:@jsr/std__semver@^1.0.4",
"@supabase/supabase-js": "^2.49.1",
"@tanstack/table-core": "^8.21.2",
"@tauri-apps/api": "^2.3.0",
"@tauri-apps/plugin-autostart": "^2.2.0",
"@tauri-apps/plugin-shell": "^2.2.0",
"@tauri-apps/plugin-sql": "^2.2.0",
"@tauri-apps/plugin-stronghold": "^2.2.0",
"dompurify": "^3.2.3",
"gsap": "^3.12.5",
"kkrpc": "^0.0.13",
"@tauri-store/svelte": "^2.1.1",
"dompurify": "^3.2.4",
"drizzle-orm": "^0.41.0",
"eslint": "^9.21.0",
"fuse.js": "^7.1.0",
"gsap": "^3.12.7",
"kkrpc": "^0.2.2",
"lz-string": "^1.5.0",
"pretty-bytes": "^6.1.1",
"semver": "^7.6.3",
"semver": "^7.7.1",
"svelte-inspect-value": "^0.5.0",
"svelte-sonner": "^0.3.28",
"sveltekit-superforms": "^2.22.1",
"sveltekit-superforms": "^2.23.1",
"tauri-plugin-clipboard-api": "^2.1.11",
"tauri-plugin-shellx-api": "^2.0.16",
"tauri-plugin-svelte": "1.2.1",
"tauri-plugin-user-input-api": "workspace:*",
"uuid": "^11.0.3"
"uuid": "^11.1.0"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
"@eslint/js": "^9.21.0",
"@inlang/paraglide-js": "1.11.8",
"@kksh/types": "workspace:*",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.12.1",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.17.3",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tauri-apps/cli": "^2.1.0",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"@tauri-apps/cli": "^2.3.1",
"@types/bun": "latest",
"@types/semver": "^7.5.8",
"@typescript-eslint/eslint-plugin": "^8.20.0",
"@typescript-eslint/parser": "^8.20.0",
"@typescript-eslint/eslint-plugin": "^8.25.0",
"@typescript-eslint/parser": "^8.25.0",
"autoprefixer": "^10.4.20",
"bits-ui": "1.0.0-next.72",
"bits-ui": "1.0.0-next.86",
"clsx": "^2.1.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.14.0",
"lucide-svelte": "^0.469.0",
"prettier": "^3.4.2",
"lucide-svelte": "^0.474.0",
"prettier": "^3.5.2",
"svelte-radix": "^2.0.1",
"tailwind-merge": "^2.5.5",
"tailwind-variants": "^0.3.0",
"tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.20.0",
"vite": "^6.0.3"
"typescript-eslint": "^8.25.0",
"vite": "^6.2.0"
}
}

View File

@ -1,7 +1,7 @@
{
"$schema": "https://inlang.com/schema/project-settings",
"sourceLanguageTag": "en",
"languageTags": ["en", "zh", "ru"],
"languageTags": ["en", "zh", "ru", "pt", "vi", "de"],
"modules": [
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",

View File

@ -14,10 +14,10 @@ name = "kunkun_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.0.3", features = [] }
tauri-build = { version = "2.0.5", features = [] }
[dependencies]
tauri = { version = "2.1.1", features = [
tauri = { version = "2.2.5", features = [
"macos-private-api",
"image-png",
"image-ico",
@ -34,12 +34,12 @@ chrono = { workspace = true }
log = { workspace = true }
urlencoding = "2.1.3"
tauri-plugin-process = "2.2.0"
tauri-plugin-shellx = "2.0.12"
tauri-plugin-fs = "2.2.0"
tauri-plugin-shellx = { workspace = true }
tauri-plugin-fs = { version = "2.2.0", features = ["watch"] }
tauri-plugin-dialog = "2.2.0"
tauri-plugin-notification = "2.2.0"
tauri-plugin-notification = "2.2.1"
tauri-plugin-os = "2.2.0"
tauri-plugin-http = "2.2.0"
tauri-plugin-http = "2.3.0"
tauri-plugin-upload = { workspace = true }
# tauri-plugin-upload = "2.2.1"
tauri-plugin-jarvis = { workspace = true }
@ -50,23 +50,26 @@ tauri-plugin-user-input = { workspace = true }
tauri-plugin-clipboard = { workspace = true }
tauri-plugin-store = "2.2.0"
tauri-plugin-deep-link = "2.2.0"
tauri-plugin-log = { version = "2.2.0", features = ["colored"] }
tauri-plugin-log = { version = "2.2.1", features = ["colored"] }
crypto = { workspace = true }
zip = "2.2.2"
uuid = "1.11.0"
uuid = "1.14.0"
# tauri-plugin-devtools = "2.0.0"
obfstr = { workspace = true }
base64 = { workspace = true }
tauri-plugin-stronghold = "2.2.0"
tauri-plugin-sql = "2"
[target."cfg(target_os = \"macos\")".dependencies]
cocoa = "0.24.1"
mac-security-rs = { workspace = true }
objc = "0.2.7"
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
tauri-plugin-autostart = "2"
tauri-plugin-cli = "2"
tauri-plugin-global-shortcut = "2.0.1"
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
tauri-plugin-updater = "2.0.2"
tauri-plugin-svelte = "2.1.1"

View File

@ -24,6 +24,7 @@
"core:event:default",
"core:window:default",
"core:window:allow-set-size",
"core:window:allow-set-enabled",
"core:window:allow-start-dragging",
"core:window:allow-set-focus",
"core:window:allow-toggle-maximize",
@ -39,6 +40,7 @@
"core:webview:allow-create-webview",
"core:webview:allow-create-webview-window",
"core:app:default",
"core:app:allow-app-hide",
"core:resources:default",
"core:menu:default",
"core:tray:default",
@ -59,6 +61,7 @@
"shellx:allow-execute",
"shellx:allow-open",
"shellx:allow-kill",
"shellx:allow-kill-pid",
"shellx:allow-spawn",
"shellx:allow-stdin-write",
"shellx:allow-fix-path-env",
@ -111,28 +114,7 @@
"identifier": "fs:scope",
"allow": [
{
"path": "$DESKTOP"
},
{
"path": "$DESKTOP/**"
},
{
"path": "$DOWNLOAD"
},
{
"path": "$DOWNLOAD/**"
},
{
"path": "$DOCUMENT"
},
{
"path": "$DOCUMENT/**"
},
{
"path": "$TEMP/**"
},
{
"path": "$TEMP"
"path": "**/*"
}
]
},
@ -164,6 +146,9 @@
}
]
},
"deep-link:default"
"deep-link:default",
"autostart:allow-enable",
"autostart:allow-disable",
"autostart:allow-is-enabled"
]
}

View File

@ -0,0 +1,5 @@
{
"identifier": "svelte",
"windows": ["*"],
"permissions": ["svelte:default", "core:event:default"]
}

View File

@ -7,8 +7,10 @@ use log;
#[cfg(target_os = "macos")]
use tauri::ActivationPolicy;
use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_jarvis::{
constants::KUNKUN_PUBLISH,
db::JarvisDB,
server::Protocol,
utils::{
@ -25,7 +27,7 @@ use utils::server::tauri_file_server;
pub fn run() {
let context = tauri::generate_context!();
let mut builder = tauri::Builder::default();
// let app_data_path = tauri::path::PathResolver::app_data_dir().unwrap();
// let db_key = if cfg!(debug_assertions) {
// None
// } else {
@ -63,14 +65,29 @@ pub fn run() {
// .build(),
// );
// }
if KUNKUN_PUBLISH == "true" {
// only include updater with KUNKUN_PUBLISH = true, this is used to avoid using updater in beta build CI, which requires secrets for the updater endpoint if updater is included
println!("KUNKUN_PUBLISH: {}", KUNKUN_PUBLISH);
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
}
let shell_unlocked = true;
builder = builder
.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
let _ = app
.get_webview_window("main")
.expect("no main window")
.set_focus();
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
let window = app.get_webview_window("main").expect("no main window");
// if toggle is passed, we want to show/hide the main window
if args.get(1).map_or(false, |arg| arg == "toggle") {
if window.is_visible().unwrap_or(false) {
log::info!("hiding main window");
window.hide().unwrap();
} else {
log::info!("showing main window");
window.show().unwrap();
window.set_focus().unwrap();
};
} else {
let _ = window.set_focus();
}
}))
.plugin(
tauri_plugin_log::Builder::new()
@ -91,13 +108,22 @@ pub fn run() {
.build(),
)
.plugin(tauri_plugin_cli::init())
.plugin(
tauri_plugin_sql::Builder::default()
// .add_migrations("sqlite:mydatabase.db", migrations)
.build(),
)
.plugin(tauri_plugin_user_input::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_svelte::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
Some(vec![]),
))
.plugin(tauri_plugin_upload::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_store::Builder::default().build())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
@ -111,7 +137,7 @@ pub fn run() {
.plugin(tauri_plugin_network::init())
.plugin(tauri_plugin_system_info::init())
.invoke_handler(tauri::generate_handler![
commands::keyring::get_stronghold_key
commands::keyring::get_stronghold_key,
]);
let app = builder
@ -123,7 +149,7 @@ pub fn run() {
})
.register_uri_scheme_protocol("ext", |app, request| {
let app_handle = app.app_handle();
// app_handle.
let win_label = app.webview_label();
let jarvis_state = app_handle.state::<tauri_plugin_jarvis::JarvisState>();
let window_ext_map = jarvis_state.window_label_ext_map.lock().unwrap();
@ -210,7 +236,8 @@ pub fn run() {
}
// setup::deeplink::setup_deeplink(app);
// #[cfg(all(target_os = "macos", debug_assertions))]
// app.set_activation_policy(ActivationPolicy::Accessory);
#[cfg(target_os = "macos")]
app.set_activation_policy(ActivationPolicy::Accessory);
// let mut store = StoreBuilder::new("appConfig.bin").build(app.handle().clone());
// let store = app.handle().store_builder("appConfig.json").build()?;

View File

@ -5,7 +5,7 @@
"identifier": "sh.kunkun.desktop",
"build": {
"beforeDevCommand": "pnpm dev",
"devUrl": "http://localhost:1420",
"devUrl": "http://localhost:1566",
"beforeBuildCommand": "pnpm build",
"frontendDist": "../build"
},
@ -20,19 +20,24 @@
"url": "/app",
"title": "Kunkun",
"width": 800,
"label": "main",
"visible": false,
"height": 600,
"decorations": true
"decorations": true,
"center": true
},
{
"url": "/splashscreen",
"visible": false,
"label": "splashscreen"
"visible": true,
"label": "splashscreen",
"center": true
}
]
},
"bundle": {
"createUpdaterArtifacts": true,
"macOS": {
"minimumSystemVersion": "10.15"
},
"fileAssociations": [
{
"ext": ["kunkun"],
@ -55,10 +60,6 @@
"fs": {
"requireLiteralLeadingDot": false
},
"updater": {
"endpoints": ["https://updater.kunkun.sh"],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDc1NENCRjZFM0JBOEQ0ODMKUldTRDFLZzdicjlNZFhHS0ZKYk13WkdZUTFUM01LNjkvVW5Bb2x1SnB1R0crbFRuMnlRSlJ0STgK"
},
"deep-link": {
"desktop": {
"schemes": ["kunkun"]

View File

@ -0,0 +1,11 @@
{
"bundle": {
"createUpdaterArtifacts": true
},
"plugins": {
"updater": {
"endpoints": ["https://updater.kunkun.sh"],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDc1NENCRjZFM0JBOEQ0ODMKUldTRDFLZzdicjlNZFhHS0ZKYk13WkdZUTFUM01LNjkvVW5Bb2x1SnB1R0crbFRuMnlRSlJ0STgK"
}
}
}

View File

@ -4,12 +4,14 @@ import { checkUpdateAndInstall } from "@/utils/updater"
import { setTransparentTitlebar } from "@kksh/api/commands"
import { IconEnum } from "@kksh/api/models"
import type { BuiltinCmd } from "@kksh/ui/types"
import { commandScore } from "@kksh/ui/utils"
import { getVersion } from "@tauri-apps/api/app"
import { appDataDir } from "@tauri-apps/api/path"
import { WebviewWindow } from "@tauri-apps/api/webviewWindow"
import { exit } from "@tauri-apps/plugin-process"
import { dev } from "$app/environment"
import { goto } from "$app/navigation"
import Fuse from "fuse.js"
import { toast } from "svelte-sonner"
import { derived } from "svelte/store"
import * as clipboard from "tauri-plugin-clipboard-api"
@ -240,6 +242,23 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
},
keywords: ["extension", "troubleshooter"]
},
{
name: "ORM Troubleshooter",
icon: {
type: IconEnum.Iconify,
value: "material-symbols:database"
},
description: "",
flags: {
developer: true,
dev: true
},
function: async () => {
appState.clearSearchTerm()
goto(i18n.resolveRoute("/app/troubleshooters/orm"))
},
keywords: ["extension", "troubleshooter", "database", "orm"]
},
{
name: "Create Quicklink",
icon: {
@ -409,7 +428,7 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
visible: false
})
setTimeout(() => {
window.show()
window.show().then(() => window.setFocus())
}, 2_000)
}
},
@ -474,10 +493,19 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
}
].map((cmd) => ({ ...cmd, id: uuidv4() }))
export const builtinCmds = derived(appConfig, ($appConfig) => {
return rawBuiltinCmds.filter((cmd) => {
const passDeveloper = cmd.flags?.developer ? $appConfig.developerMode : true
const passDev = cmd.flags?.dev ? dev : true
return passDeveloper && passDev
})
export const fuse = new Fuse<BuiltinCmd>(rawBuiltinCmds, {
includeScore: true,
threshold: 0.2,
keys: ["name"]
})
export const builtinCmds = derived([appConfig, appState], ([$appConfig, $appState]) => {
return $appState.searchTerm
? fuse
.search($appState.searchTerm)
.map((result) => result.item)
.filter(
(cmd) => (!cmd.flags?.developer || $appConfig.developerMode) && (!cmd.flags?.dev || dev)
)
: rawBuiltinCmds
})

View File

@ -1,20 +1,41 @@
import { i18n } from "@/i18n"
import { appState } from "@/stores"
import { winExtMap } from "@/stores/winExtMap"
import { helperAPI } from "@/utils/helper"
import { paste } from "@/utils/hotkey"
import { decideKkrpcSerialization } from "@/utils/kkrpc"
import { sleep } from "@/utils/time"
import { trimSlash } from "@/utils/url"
import { constructExtensionSupportDir } from "@kksh/api"
import { db, spawnExtensionFileServer } from "@kksh/api/commands"
import { HeadlessWorkerExtension } from "@kksh/api/headless"
import { spawnExtensionFileServer } from "@kksh/api/commands"
import type { HeadlessCommand } from "@kksh/api/headless"
import { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models"
import { constructJarvisServerAPIWithPermissions, type IApp } from "@kksh/api/ui"
import { db } from "@kksh/drizzle"
import { launchNewExtWindow, loadExtensionManifestFromDisk } from "@kksh/extension"
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
import { convertFileSrc } from "@tauri-apps/api/core"
import * as path from "@tauri-apps/api/path"
import { getCurrentWindow } from "@tauri-apps/api/window"
import * as fs from "@tauri-apps/plugin-fs"
import { info } from "@tauri-apps/plugin-log"
import { platform } from "@tauri-apps/plugin-os"
import { goto } from "$app/navigation"
import { RPCChannel, WorkerParentIO } from "kkrpc/browser"
import * as v from "valibot"
export const KunkunIframeExtParams = v.object({
url: v.string(),
cmdName: v.optional(v.string()),
extPath: v.string()
})
export type KunkunIframeExtParams = v.InferOutput<typeof KunkunIframeExtParams>
export const KunkunTemplateExtParams = v.object({
url: v.optional(v.string()),
extPath: v.string(),
cmdName: v.string()
})
export type KunkunTemplateExtParams = v.InferOutput<typeof KunkunTemplateExtParams>
export async function createExtSupportDir(extPath: string) {
const extSupportDir = await constructExtensionSupportDir(extPath)
@ -23,16 +44,29 @@ export async function createExtSupportDir(extPath: string) {
}
}
function setTemplateExtParams(extPath: string, cmdName: string, url?: string) {
localStorage.setItem(
"kunkun-template-ext-params",
JSON.stringify({ extPath, cmdName, url } satisfies KunkunTemplateExtParams)
)
}
export async function onTemplateUiCmdSelect(
ext: ExtPackageJsonExtra,
cmd: TemplateUiCmd,
{ isDev, hmr }: { isDev: boolean; hmr: boolean }
) {
await createExtSupportDir(ext.extPath)
// console.log("onTemplateUiCmdSelect", ext, cmd, isDev, hmr)
const url = `/app/extension/ui-worker?extPath=${encodeURIComponent(ext.extPath)}&cmdName=${encodeURIComponent(cmd.name)}`
setTemplateExtParams(ext.extPath, cmd.name, url)
if (cmd.window) {
const winLabel = await winExtMap.registerExtensionWithWindow({ extPath: ext.extPath })
const paramsStr = JSON.stringify({
url,
extPath: ext.extPath,
cmdName: cmd.name
} satisfies KunkunIframeExtParams)
localStorage.setItem("kunkun-template-ext-params", paramsStr)
const window = launchNewExtWindow(winLabel, url, cmd.window)
window.onCloseRequested(async (event) => {
await winExtMap.unregisterExtensionFromWindow(winLabel)
@ -54,6 +88,7 @@ export async function onHeadlessCmdSelect(
const loadedExt = await loadExtensionManifestFromDisk(
await path.join(ext.extPath, "package.json")
)
const scriptPath = await path.join(loadedExt.extPath, cmd.main)
const workerScript = await fs.readTextFile(scriptPath)
const blob = new Blob([workerScript], { type: "application/javascript" })
@ -65,11 +100,26 @@ export async function onHeadlessCmdSelect(
}
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
loadedExt.kunkun.permissions,
loadedExt.extPath
loadedExt.extPath,
{
recordSpawnedProcess: async (pid: number) => {
console.log("recordSpawnedProcess pid", pid)
},
getSpawnedProcesses: async () => {
console.log("getSpawnedProcesses")
return []
},
paste: async () => {
await getCurrentWindow().hide()
await sleep(200)
return paste()
}
}
)
const serverAPI2 = {
...serverAPI,
iframeUi: undefined,
helper: helperAPI,
workerUi: undefined,
db: new db.JarvisExtDB(extInfoInDB.extId),
kv: new db.KV(extInfoInDB.extId),
@ -78,13 +128,27 @@ export async function onHeadlessCmdSelect(
} satisfies IApp
}
const io = new WorkerParentIO(worker)
const rpc = new RPCChannel<typeof serverAPI2, HeadlessWorkerExtension>(io, {
expose: serverAPI2
const kkrpcSerialization = decideKkrpcSerialization(loadedExt)
info(
`Establishing kkrpc connection for ${loadedExt.kunkun.identifier} with serialization: ${kkrpcSerialization}`
)
const rpc = new RPCChannel<typeof serverAPI2, HeadlessCommand>(io, {
expose: serverAPI2,
serialization: {
version: kkrpcSerialization
}
})
const workerAPI = rpc.getAPI()
await workerAPI.load()
}
function setIframeExtParams(extPath: string, url: string) {
localStorage.setItem(
"kunkun-iframe-ext-params",
JSON.stringify({ url, extPath } satisfies KunkunIframeExtParams)
)
}
export async function onCustomUiCmdSelect(
ext: ExtPackageJsonExtra,
cmd: CustomUiCmd,
@ -102,6 +166,9 @@ export async function onCustomUiCmdSelect(
: decodeURIComponent(convertFileSrc(`${trimSlash(cmd.main)}`, "ext"))
}
let url2 = `/app/extension/ui-iframe?url=${encodeURIComponent(url)}&extPath=${encodeURIComponent(ext.extPath)}`
// url2 = `/dev?url=${encodeURIComponent(url)}&extPath=${encodeURIComponent(ext.extPath)}`
setIframeExtParams(ext.extPath, url)
if (cmd.window) {
const winLabel = await winExtMap.registerExtensionWithWindow({
extPath: ext.extPath,
@ -111,7 +178,12 @@ export async function onCustomUiCmdSelect(
const addr = await spawnExtensionFileServer(winLabel)
const newUrl = `http://${addr}`
url2 = `/app/extension/ui-iframe?url=${encodeURIComponent(newUrl)}&extPath=${encodeURIComponent(ext.extPath)}`
setIframeExtParams(ext.extPath, newUrl)
}
localStorage.setItem(
"kunkun-iframe-ext-params",
JSON.stringify({ url, extPath: ext.extPath } satisfies KunkunIframeExtParams)
)
const window = launchNewExtWindow(winLabel, url2, cmd.window)
window.onCloseRequested(async (event) => {
await winExtMap.unregisterExtensionFromWindow(winLabel)
@ -123,11 +195,13 @@ export async function onCustomUiCmdSelect(
extPath: ext.extPath,
dist: cmd.dist
})
if (platform() === "windows" && !useDevMain) {
const _platform = platform()
if ((_platform === "windows" || _platform === "linux") && !useDevMain) {
const addr = await spawnExtensionFileServer(winLabel) // addr has format "127.0.0.1:<port>"
console.log("Extension file server address: ", addr)
const newUrl = `http://${addr}`
url2 = `/app/extension/ui-iframe?url=${encodeURIComponent(newUrl)}&extPath=${encodeURIComponent(ext.extPath)}`
setIframeExtParams(ext.extPath, newUrl)
}
goto(i18n.resolveRoute(url2))
}

View File

@ -12,7 +12,7 @@ import { onQuickLinkSelect } from "./quick-links"
const onExtCmdSelect: OnExtCmdSelect = (
ext: ExtPackageJsonExtra,
cmd: CustomUiCmd | TemplateUiCmd,
cmd: CustomUiCmd | TemplateUiCmd | HeadlessCmd,
{ isDev, hmr }: { isDev: boolean; hmr: boolean }
) => {
switch (cmd.type) {

View File

@ -1,4 +1,27 @@
import { getSystemCommands } from "@kksh/api/commands"
import type { SysCommand } from "@kksh/api/models"
import { commandScore } from "@kksh/ui/utils"
import Fuse from "fuse.js"
import { derived, readable } from "svelte/store"
import { appState } from "../stores/appState"
export const systemCommands: SysCommand[] = getSystemCommands()
export const systemCommands = getSystemCommands()
export const fuse = new Fuse<SysCommand>(systemCommands, {
includeScore: true,
threshold: 0.2,
keys: ["name"]
})
export const systemCommandsFiltered = derived(appState, ($appState) => {
return $appState.searchTerm
? fuse.search($appState.searchTerm).map((result) => result.item)
: systemCommands
})
// export const systemCommandsFiltered = derived(
// [systemCommands, appState],
// ([$systemCommands, $appState]) => {
// return $systemCommands.filter((cmd) => commandScore(cmd.name, $appState.searchTerm) > 0.5)
// }
// )

View File

@ -0,0 +1,37 @@
<script lang="ts">
import { appConfig, appState } from "@/stores"
import { cn } from "@/utils"
import { Button } from "@kksh/svelte5"
import { BorderBeam, Constants, Layouts, TauriLink } from "@kksh/ui"
import { goto } from "$app/navigation"
import { ArrowLeftIcon, LoaderCircleIcon } from "lucide-svelte"
import Dance from "../dance/dance.svelte"
let { class: className }: { class?: string } = $props()
function goHome() {
appState.setFullScreenLoading(false)
goto("/app")
}
</script>
<Layouts.Center class={cn("flex h-screen flex-col items-center justify-center", className)}>
<Button
variant="outline"
size="icon"
onclick={goHome}
class={cn(Constants.CLASSNAMES.BACK_BUTTON, "absolute left-4 top-4")}
data-flip-id={Constants.CLASSNAMES.BACK_BUTTON}
>
<ArrowLeftIcon class="size-4" />
</Button>
{#if $appConfig.loadingAnimation === "kunkun-dancing"}
<!-- <DanceTransition delay={300} autoHide={false} show={!uiControl.iframeLoaded} /> -->
<Dance class="absolute z-50 h-screen opacity-20" />
{:else}
<!-- <LoadingAnimation delay={300} autoHide={false} show={!uiControl.iframeLoaded} /> -->
<LoaderCircleIcon class="h-24 w-24 animate-spin" />
<span class="font-mono">Loading</span>
{/if}
<BorderBeam size={150} duration={12} />
</Layouts.Center>

View File

@ -75,7 +75,7 @@
<div class={cn("flex items-center gap-2", className)}>
<Shiki class={cn("w-full overflow-x-scroll rounded-md p-1 px-2")} {code} {lang} />
<Button class="" size="sm" variant="secondary" onclick={copy}>Copy</Button>
<Button class="" size="sm" variant="secondary" onclick={autoInstall} disabled={!autoInstallable}>
<!-- <Button class="" size="sm" variant="secondary" onclick={autoInstall} disabled={!autoInstallable}>
Auto Install
</Button>
</Button> -->
</div>

View File

@ -1,15 +1,15 @@
<script lang="ts">
import { goHome } from "@/utils/route"
import { Button, SideBar } from "@kksh/svelte5"
import { Button, Sidebar } from "@kksh/svelte5"
import { Constants } from "@kksh/ui"
import { ArrowLeftIcon } from "lucide-svelte"
const { useSidebar } = SideBar
const { useSidebar } = Sidebar
const sidebar = useSidebar()
</script>
<div class="fixed flex h-10 w-full items-center gap-2 pl-1 pt-1" data-tauri-drag-region>
<SideBar.Trigger class="z-50" />
<Sidebar.Trigger class="z-50" />
{#if sidebar.state === "collapsed"}
<Button
variant="outline"

View File

@ -1,8 +1,8 @@
<script lang="ts">
import { setAppConfigContext } from "@/context"
import { setAppStateContext } from "@/context/appState"
import type { AppConfig, AppState } from "@kksh/types"
import type { Snippet } from "svelte"
import type { AppConfigState, AppState } from "@kksh/types"
import { type Snippet } from "svelte"
import type { Writable } from "svelte/store"
const {
@ -10,7 +10,7 @@
appState,
children
}: {
appConfig: Writable<AppConfig>
appConfig: Writable<AppConfigState>
appState: Writable<AppState>
children: Snippet<[]>
} = $props()

View File

@ -0,0 +1,63 @@
<script lang="ts">
import { appState } from "@/stores"
import { IconEnum, type AppInfo } from "@kksh/api/models"
import { Command } from "@kksh/svelte5"
import { IconMultiplexer } from "@kksh/ui"
import { DraggableCommandGroup } from "@kksh/ui/custom"
import { convertFileSrc } from "@tauri-apps/api/core"
import { getCurrentWindow } from "@tauri-apps/api/window"
import * as os from "@tauri-apps/plugin-os"
import { toast } from "svelte-sonner"
import { executeBashScript, open } from "tauri-plugin-shellx-api"
const platform = os.platform()
let { apps }: { apps: AppInfo[] } = $props()
</script>
<DraggableCommandGroup heading="Apps">
{#each apps.filter((app) => app.name) as app, idx}
{@const iconPath = platform === "windows" ? (app.icon_path ?? app.app_path_exe) : app.icon_path}
<Command.Item
class="flex justify-between"
onSelect={async () => {
if (platform === "windows") {
if (app.app_path_exe) {
open(app.app_path_exe)
} else {
toast.error("No executable path found for this app")
}
} else if (platform === "macos") {
open(app.app_desktop_path)
} else if (platform === "linux") {
if (app.app_path_exe) {
executeBashScript(app.app_path_exe)
} else {
toast.error("No executable path found for this app")
}
} else {
toast.error("Unsupported platform")
}
await getCurrentWindow().hide()
appState.clearSearchTerm()
}}
value={`app:${idx}:${app.app_desktop_path}`}
>
<span class="flex gap-2">
<IconMultiplexer
icon={iconPath
? {
type: IconEnum.RemoteUrl,
value: convertFileSrc(iconPath, "appicon")
}
: {
type: IconEnum.Iconify,
value: "mdi:application"
}}
class="!h-5 !w-5 shrink-0"
/>
<span>{app.name}</span>
<!-- <span>{app.app_path_exe}</span> -->
</span>
</Command.Item>
{/each}
</DraggableCommandGroup>

View File

@ -11,21 +11,46 @@
} from "@/paraglide/runtime"
import { appConfig } from "@/stores"
import { Select, Switch } from "@kksh/svelte5"
import type { LoadingAnimation } from "@kksh/types"
import * as autoStart from "@tauri-apps/plugin-autostart"
import { onMount } from "svelte"
import { toast } from "svelte-sonner"
const languages = availableLanguageTags.map((lang) => ({
value: lang,
label: LanguageMap[lang] ?? lang
label: LanguageMap[lang as keyof typeof LanguageMap] ?? lang
}))
let value = $state(languageTag())
const triggerContent = $derived(languages.find((f) => f.value === value)?.label ?? "Language")
let loadingAnimation = $state<LoadingAnimation>("spinning-circle")
const loadingAnimations = ["spinning-circle", "kunkun-dancing"] as const
let launchAtLogin = $state(false)
let language = $state(languageTag())
onMount(() => {
autoStart.isEnabled().then((enabled) => {
launchAtLogin = enabled
})
loadingAnimation = $appConfig.loadingAnimation
})
const triggerContent = $derived(languages.find((f) => f.value === language)?.label ?? "Language")
</script>
<ul class="rounded-lg border">
<li>
<span>{m.settings_general_launch_at_login()}</span>
<Switch bind:checked={$appConfig.launchAtLogin} />
<Switch
bind:checked={launchAtLogin}
onCheckedChange={(checked) => {
const action = checked ? autoStart.enable : autoStart.disable
action()
.then(() => {
toast.success(checked ? "Enabled" : "Disabled")
})
.catch((err) => {
toast.error(checked ? "Failed to enable" : "Failed to disable", {
description: err.message
})
})
}}
/>
</li>
<li class="">
<span>{m.settings_general_hotkey()}</span>
@ -59,7 +84,7 @@
<li>
<span>{m.settings_general_language()}</span>
<Select.Root type="single" name="language" bind:value>
<Select.Root type="single" name="language" bind:value={language}>
<Select.Trigger class="w-fit">
{triggerContent}
</Select.Trigger>
@ -80,6 +105,31 @@
</Select.Content>
</Select.Root>
</li>
<li>
<span>{m.settings_general_loading_animation()}</span>
<Select.Root type="single" name="loadingAnimation" bind:value={loadingAnimation}>
<Select.Trigger class="w-fit">
{loadingAnimation}
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.GroupHeading>Loading Animation</Select.GroupHeading>
{#each loadingAnimations as anim}
<Select.Item
onclick={() => {
appConfig.setLoadingAnimation(anim)
}}
value={anim}
label={anim}
>
{anim}
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
</li>
</ul>
<style scoped>

View File

@ -1,12 +1,9 @@
<script lang="ts">
import InstallCodeBlock from "@/components/common/install-code-block.svelte"
import Icon from "@iconify/svelte"
import { IconEnum } from "@kksh/api/models"
import { Button, Tabs } from "@kksh/svelte5"
import { TauriLink } from "@kksh/ui"
import { platform } from "@tauri-apps/plugin-os"
import { onMount } from "svelte"
import { toast } from "svelte-sonner"
import { whereIsCommand } from "tauri-plugin-shellx-api"
let brewPath = $state("")
@ -21,12 +18,7 @@
</script>
<h1 class="font-mono text-2xl font-bold">Install Homebrew</h1>
<TauriLink
href="/app/help/brew-install"
icon={IconEnum.Iconify}
iconValue="devicon:homebrew"
class="flex items-center"
>
<TauriLink href="/app/help/brew-install" class="flex items-center">
<span class="text-lg">Homebrew Website</span>
<Icon icon="devicon:homebrew" class="h-6 w-6" />
</TauriLink>

View File

@ -51,18 +51,18 @@
runtime environment for executing extension code safely. It is optional but recommended.
</p>
<p class="font-mono text-sm">Choose any installation method below.</p>
<p class="font-mono text-sm">
<!-- <p class="font-mono text-sm">
If you are unsure, you can use <strong class="text-lg">Auto Install</strong>.
</p>
</p> -->
<p class="font-mono text-sm text-red-400">
After installation, ensure the `deno` command is accessible from your system's PATH.
</p>
{#if _platform === "macos" || _platform === "linux"}
<!-- {#if _platform === "macos" || _platform === "linux"}
<p class="font-mono text-sm text-red-400">
Installation with <span class="font-bold text-green-500">curl</span> command likely requires manual
configuration. So auto install is disabled. Please copy the command and run it in a terminal.
</p>
{/if}
{/if} -->
{#if denoPath}
<div class="flex items-center gap-2">
<span></span>

View File

@ -3,7 +3,7 @@
import DevExtPathForm from "@/components/standalone/settings/DevExtPathForm.svelte"
import { i18n } from "@/i18n"
import * as m from "@/paraglide/messages"
import { appConfig, extensions } from "@/stores"
import { appConfig, appState, extensions } from "@/stores"
import { goBackOnEscape } from "@/utils/key"
import { goBack } from "@/utils/route"
import { IconEnum } from "@kksh/api/models"
@ -65,10 +65,12 @@
}
async function pickExtFolders() {
appState.setLockHideOnBlur(true)
const selected = await openFileSelector({
directory: true,
multiple: true // allow install multiple extensions at once
})
appState.setLockHideOnBlur(false)
if (!selected) {
return toast.warning("No File Selected")
}
@ -91,6 +93,7 @@
toast.warning("Please set the dev extension path in the settings")
return goto(i18n.resolveRoute("/app/settings/set-dev-ext-path"))
}
appState.setLockHideOnBlur(true)
const selected = await openFileSelector({
directory: false,
multiple: true, // allow install multiple extensions at once
@ -101,6 +104,7 @@
}
]
})
appState.setLockHideOnBlur(false)
if (!selected) {
return toast.warning("No File Selected")
}
@ -111,15 +115,18 @@
</script>
<div class="my-3 flex justify-center gap-3">
<Button size="sm" onclick={pickExtFolders}
>{m.settings_add_dev_ext_install_from_ext_folders()}</Button
>
<Button size="sm" onclick={pickExtFiles}>{m.settings_add_dev_ext_install_from_ext_files()}</Button
>
<Button size="sm" onclick={pickExtFolders}>
{m.settings_add_dev_ext_install_from_ext_folders()}
</Button>
<Button size="sm" onclick={pickExtFiles}>
{m.settings_add_dev_ext_install_from_ext_files()}
</Button>
</div>
<StrikeSeparator class="my-1">
<h3 class="text-muted-foreground font-mono text-sm">{m.settings_add_dev_ext_drag_and_drop()}</h3>
<h3 class="text-muted-foreground font-mono text-sm">
{m.settings_add_dev_ext_drag_and_drop_strike()}
</h3>
</StrikeSeparator>
<Layouts.Center>
@ -150,12 +157,12 @@
icon={{ value: "mdi:folder-cog-outline", type: IconEnum.Iconify }}
class="h-10 w-10"
/>
<small class="select-none font-mono text-xs"
>{m.settings_add_dev_ext_drag_and_drop()}</small
>
<small class="select-none font-mono text-xs"
>{m.settings_add_dev_ext_drag_and_drop2()}</small
>
<small class="select-none font-mono text-xs">
{m.settings_add_dev_ext_drag_and_drop()}
</small>
<small class="select-none font-mono text-xs">
{m.settings_add_dev_ext_drag_and_drop2()}
</small>
</div>
</button>
</Card.Root>

View File

@ -23,5 +23,7 @@ export const IS_IN_TAURI =
export const LanguageMap = {
en: "English",
zh: "中文",
ru: "Русский"
ru: "Русский",
pt: "Português",
vi: "Tiếng Việt"
}

View File

@ -3,16 +3,16 @@
* It's designed to allow all components to access a shared state.
* With context, we can avoid prop drilling, and avoid using stores which makes components hard to encapsulate.
*/
import type { AppConfig } from "@/types/appConfig"
import type { AppConfigState } from "@kksh/types"
import { getContext, setContext } from "svelte"
import type { Writable } from "svelte/store"
export const APP_CONFIG_CONTEXT_KEY = Symbol("appConfig")
export function getAppConfigContext(): Writable<AppConfig> {
export function getAppConfigContext(): Writable<AppConfigState> {
return getContext(APP_CONFIG_CONTEXT_KEY)
}
export function setAppConfigContext(appConfig: Writable<AppConfig>) {
export function setAppConfigContext(appConfig: Writable<AppConfigState>) {
setContext(APP_CONFIG_CONTEXT_KEY, appConfig)
}

View File

@ -1,4 +1,4 @@
import type { AppState } from "@/types/appState"
import type { AppState } from "@kksh/types"
import { getContext, setContext } from "svelte"
import type { Writable } from "svelte/store"

View File

@ -0,0 +1,60 @@
// /* eslint-disable @typescript-eslint/no-explicit-any */
// import { db as dbCmd } from "@kksh/api/commands"
// import * as schema from "@kksh/drizzle/schema"
// import { error } from "@tauri-apps/plugin-log"
// import { drizzle } from "drizzle-orm/sqlite-proxy"
// /**
// * Loads the sqlite database via the Tauri Proxy.
// */
// // export const sqlite = await Database.load("sqlite:test.db");
// /**
// * The drizzle database instance.
// */
// export const db = drizzle<typeof schema>(
// async (sql, params, method) => {
// let rows: any = []
// let results = []
// console.log({
// sql,
// params,
// method
// })
// console.log(sql)
// // If the query is a SELECT, use the select method
// if (isSelectQuery(sql)) {
// rows = await dbCmd.select(sql, params).catch((e) => {
// error("SQL Error:", e)
// return []
// })
// } else {
// // Otherwise, use the execute method
// rows = await dbCmd.execute(sql, params).catch((e) => {
// error("SQL Error:", e)
// return []
// })
// return { rows: [] }
// }
// rows = rows.map((row: any) => {
// return Object.values(row)
// })
// // If the method is "all", return all rows
// results = method === "all" ? rows : rows[0]
// return { rows: results }
// },
// // Pass the schema to the drizzle instance
// { schema: schema, logger: true }
// )
// /**
// * Checks if the given SQL query is a SELECT query.
// * @param sql The SQL query to check.
// * @returns True if the query is a SELECT query, false otherwise.
// */
// function isSelectQuery(sql: string): boolean {
// const selectRegex = /^\s*SELECT\b/i
// return selectRegex.test(sql)
// }

View File

@ -1,15 +1,16 @@
import { getExtensionsFolder } from "@/constants"
import { createTauriSyncStore, type WithSyncStore } from "@/utils/sync-store"
import type { SearchPath } from "@kksh/api/models"
import { updateTheme, type ThemeConfig } from "@kksh/svelte5"
import { PersistedAppConfig, type AppConfig } from "@kksh/types"
import { debug, error } from "@tauri-apps/plugin-log"
import { LoadingAnimation, PersistedAppConfig, type AppConfigState } from "@kksh/types"
import { debug, error, info } from "@tauri-apps/plugin-log"
import * as os from "@tauri-apps/plugin-os"
import { load } from "@tauri-apps/plugin-store"
import { Store } from "@tauri-store/svelte"
import { toast } from "svelte-sonner"
import { get, writable } from "svelte/store"
import * as v from "valibot"
import { setLanguageTag } from "../paraglide/runtime"
export const defaultAppConfig: AppConfig = {
export const defaultAppConfig: AppConfigState = {
isInitialized: false,
platform: "macos",
language: "en",
@ -19,7 +20,6 @@ export const defaultAppConfig: AppConfig = {
lightMode: "auto"
},
triggerHotkey: null,
launchAtLogin: true,
showInTray: true,
devExtensionPath: null,
extensionsInstallDir: undefined,
@ -28,72 +28,82 @@ export const defaultAppConfig: AppConfig = {
extensionAutoUpgrade: true,
joinBetaProgram: false,
onBoarded: false,
developerMode: false
developerMode: false,
appSearchPaths: [],
loadingAnimation: "kunkun-dancing"
}
export const appConfigLoaded = writable(false)
interface AppConfigAPI {
init: () => Promise<void>
get: () => AppConfig
get: () => AppConfigState
setTheme: (theme: ThemeConfig) => void
setDevExtensionPath: (devExtensionPath: string | null) => void
setTriggerHotkey: (triggerHotkey: string[]) => void
setOnBoarded: (onBoarded: boolean) => void
setLanguage: (language: string) => void
addAppSearchPath: (appSearchPath: SearchPath) => void
removeAppSearchPath: (appSearchPath: SearchPath) => void
}
function createAppConfig(): WithSyncStore<AppConfig & { language: string }> & AppConfigAPI {
const store = createTauriSyncStore("app-config", defaultAppConfig)
async function init() {
debug("Initializing app config")
const persistStore = await load("kk-config.json", { autoSave: true })
const loadedConfig = await persistStore.get("config")
const parseRes = v.safeParse(PersistedAppConfig, loadedConfig)
if (parseRes.success) {
console.log("Parse Persisted App Config Success", parseRes.output)
const extensionsInstallDir = await getExtensionsFolder()
store.update((config) => ({
...config,
...parseRes.output,
isInitialized: true,
extensionsInstallDir,
platform: os.platform()
}))
} else {
error("Failed to parse app config, going to remove it and reinitialize")
console.error(v.flatten<typeof PersistedAppConfig>(parseRes.issues))
await persistStore.clear()
await persistStore.set("config", v.parse(PersistedAppConfig, defaultAppConfig))
}
appConfigLoaded.set(true)
store.subscribe(async (config) => {
console.log("Saving app config", config)
await persistStore.set("config", config)
updateTheme(config.theme)
class AppConfigStore extends Store<AppConfigState> implements AppConfigAPI {
constructor() {
super("app-config", defaultAppConfig, {
saveOnChange: true
})
this.start().catch((err) => {
error("Failed to start app config store", err)
toast.error("Failed to start app config store", { description: err.message })
})
}
async init() {
debug("Initializing app config")
const extensionsInstallDir = await getExtensionsFolder()
this.update((config) => ({
...config,
isInitialized: true,
platform: os.platform(),
extensionsInstallDir
}))
appConfigLoaded.set(true)
}
return {
...store,
get: () => get(store),
setTheme: (theme: ThemeConfig) => store.update((config) => ({ ...config, theme })),
setDevExtensionPath: (devExtensionPath: string | null) => {
console.log("setDevExtensionPath", devExtensionPath)
store.update((config) => ({ ...config, devExtensionPath }))
},
setTriggerHotkey: (triggerHotkey: string[]) => {
store.update((config) => ({ ...config, triggerHotkey }))
},
setOnBoarded: (onBoarded: boolean) => {
store.update((config) => ({ ...config, onBoarded }))
},
setLanguage: (language: string) => {
store.update((config) => ({ ...config, language }))
},
init
get() {
return get(this)
}
setTheme(theme: ThemeConfig) {
this.update((config) => ({ ...config, theme }))
}
setDevExtensionPath(devExtensionPath: string | null) {
info(`setDevExtensionPath ${devExtensionPath}`)
this.update((config) => ({ ...config, devExtensionPath }))
}
setTriggerHotkey(triggerHotkey: string[]) {
this.update((config) => ({ ...config, triggerHotkey }))
}
setOnBoarded(onBoarded: boolean) {
this.update((config) => ({ ...config, onBoarded }))
}
setLanguage(language: string) {
this.update((config) => ({ ...config, language }))
}
addAppSearchPath(appSearchPath: SearchPath) {
this.update((config) => ({
...config,
appSearchPaths: [...config.appSearchPaths, appSearchPath]
}))
}
removeAppSearchPath(appSearchPath: SearchPath) {
this.update((config) => ({
...config,
appSearchPaths: config.appSearchPaths.filter((path) => path.path !== appSearchPath.path)
}))
}
setLoadingAnimation(loadingAnimation: LoadingAnimation) {
this.update((config) => ({ ...config, loadingAnimation }))
}
}
export const appConfig = createAppConfig()
// export const appConfig = createAppConfig()
export const appConfig = new AppConfigStore()

View File

@ -1,15 +1,15 @@
import { findAllArgsInLink } from "@/cmds/quick-links"
import { Action as ActionSchema, CmdTypeEnum } from "@kksh/api/models"
import { Action as ActionSchema } from "@kksh/api/models"
import type { AppState } from "@kksh/types"
import type { CmdValue } from "@kksh/ui/types"
import { derived, get, writable, type Writable } from "svelte/store"
import { get, writable, type Writable } from "svelte/store"
export const defaultAppState: AppState = {
searchTerm: "",
highlightedCmd: "",
loadingBar: false,
defaultAction: "",
actionPanel: undefined
actionPanel: undefined,
lockHideOnBlur: false, // when dialog is open, we don't hide the app, we lock the hide on blur and unlock when dialog is closed
fullScreenLoading: false
}
interface AppStateAPI {
@ -18,6 +18,8 @@ interface AppStateAPI {
setLoadingBar: (loadingBar: boolean) => void
setDefaultAction: (defaultAction: string | null) => void
setActionPanel: (actionPanel?: ActionSchema.ActionPanel) => void
setLockHideOnBlur: (lockHideOnBlur: boolean) => void
setFullScreenLoading: (fullScreenLoading: boolean) => void
}
function createAppState(): Writable<AppState> & AppStateAPI {
@ -37,6 +39,12 @@ function createAppState(): Writable<AppState> & AppStateAPI {
},
setActionPanel: (actionPanel?: ActionSchema.ActionPanel) => {
store.update((state) => ({ ...state, actionPanel }))
},
setLockHideOnBlur: (lockHideOnBlur: boolean) => {
store.update((state) => ({ ...state, lockHideOnBlur }))
},
setFullScreenLoading: (fullScreenLoading: boolean) => {
store.update((state) => ({ ...state, fullScreenLoading }))
}
}
}

View File

@ -0,0 +1,48 @@
import { getAllApps, refreshApplicationsList } from "@kksh/api/commands"
import { AppInfo } from "@kksh/api/models"
import { commandScore } from "@kksh/ui/utils"
import * as fs from "@tauri-apps/plugin-fs"
import { platform } from "@tauri-apps/plugin-os"
import Fuse from "fuse.js"
import { derived, get, writable } from "svelte/store"
import { appState } from "./appState"
const fuse = new Fuse<AppInfo>([], {
includeScore: true,
threshold: 0.2,
keys: ["name"]
})
export function createAppsLoaderStore() {
const store = writable<AppInfo[]>([])
return {
...store,
get: () => get(store),
init: async () => {
await refreshApplicationsList()
let apps = await getAllApps()
if (platform() === "macos") {
apps = apps.filter((app) => {
return (
!app.app_desktop_path.includes("Parallels") &&
!app.app_desktop_path.startsWith("/Library/Application Support") &&
!app.app_desktop_path.startsWith("/System/Library/CoreServices") &&
!app.app_desktop_path.startsWith("/System/Library/PrivateFrameworks")
)
})
}
// console.log("filteredApps", apps)
// fs.writeTextFile("/Users/hk/Desktop/apps.json", JSON.stringify(apps))
store.set(apps)
fuse.setCollection(apps)
}
}
}
export const appsLoader = createAppsLoaderStore()
export const appsFiltered = derived([appsLoader, appState], ([$apps, $appState]) => {
return $appState.searchTerm.length > 0
? fuse.search($appState.searchTerm).map((result) => result.item)
: $apps.slice(0, 20)
})

View File

@ -1,11 +1,11 @@
import { getExtensionsFolder } from "@/constants"
import { db } from "@kksh/api/commands"
import type { ExtPackageJson, ExtPackageJsonExtra } from "@kksh/api/models"
import type { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models"
import { db } from "@kksh/drizzle"
import * as extAPI from "@kksh/extension"
import * as path from "@tauri-apps/api/path"
import * as fs from "@tauri-apps/plugin-fs"
import { derived, get, writable, type Readable, type Writable } from "svelte/store"
import Fuse from "fuse.js"
import { derived, get, writable, type Writable } from "svelte/store"
import { appConfig } from "./appConfig"
import { appState } from "./appState"
function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
init: () => Promise<void>
@ -27,6 +27,7 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
tarballUrl: string,
extras?: { overwritePackageJson?: string }
) => Promise<ExtPackageJsonExtra>
reloadExtension: (extPath: string) => Promise<void>
} {
const store = writable<ExtPackageJsonExtra[]>([])
@ -40,6 +41,22 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
})
}
// if dev extension's package.json is changed, use this function to reload commands
async function reloadExtension(extPath: string) {
const ext = get(extensions).find((ext) => ext.extPath === extPath)
if (ext) {
const pkgJsonPath = await path.join(extPath, "package.json")
const ext = await extAPI.loadExtensionManifestFromDisk(pkgJsonPath)
// replace the old extension with the new one
store.update((exts) => {
// filter out the old extension
return [...exts.filter((e) => e.extPath !== extPath), ext]
})
} else {
console.warn(`reloadExtension: Extension ${extPath} not found`)
}
}
/**
* Get all extensions installed from the store (non-dev extensions)
*/
@ -193,6 +210,7 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
return {
...store,
init,
reloadExtension,
getExtensionsFromStore,
findStoreExtensionByIdentifier,
registerNewExtensionByPath,
@ -207,20 +225,62 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
}
export const extensions = createExtensionsStore()
export const installedStoreExts = derived(extensions, ($extensions) => {
const extContainerPath = get(appConfig).extensionsInstallDir
if (!extContainerPath) return []
return $extensions.filter((ext) => !extAPI.isExtPathInDev(extContainerPath, ext.extPath))
})
export const devStoreExts = derived(extensions, ($extensions) => {
const extContainerPath = get(appConfig).extensionsInstallDir
if (!extContainerPath) return []
return $extensions.filter((ext) => extAPI.isExtPathInDev(extContainerPath, ext.extPath))
})
export const installedStoreExts: Readable<ExtPackageJsonExtra[]> = derived(
extensions,
($extensionsStore) => {
const extContainerPath = get(appConfig).extensionsInstallDir
if (!extContainerPath) return []
return $extensionsStore.filter((ext) => !extAPI.isExtPathInDev(extContainerPath, ext.extPath))
}
)
export const devStoreExts: Readable<ExtPackageJsonExtra[]> = derived(
extensions,
($extensionsStore) => {
const extContainerPath = get(appConfig).extensionsInstallDir
if (!extContainerPath) return []
return $extensionsStore.filter((ext) => extAPI.isExtPathInDev(extContainerPath, ext.extPath))
}
)
export type StoreExtCmd = (CustomUiCmd | TemplateUiCmd | HeadlessCmd) & {
ext: ExtPackageJsonExtra
}
export const cmdsFuse = new Fuse<StoreExtCmd>([], {
includeScore: true,
threshold: 0.2,
keys: ["name"]
})
export const devCmdsFuse = new Fuse<StoreExtCmd>([], {
includeScore: true,
threshold: 0.2,
keys: ["name"]
})
export const storeExtCmds = derived(installedStoreExts, ($exts) => {
const cmds = $exts.flatMap((ext) => {
return [
...(ext.kunkun.customUiCmds ?? []),
...(ext.kunkun.templateUiCmds ?? []),
...(ext.kunkun.headlessCmds ?? [])
].map((cmd) => ({ ...cmd, ext }))
})
cmdsFuse.setCollection(cmds)
return cmds
})
export const devStoreExtCmds = derived(devStoreExts, ($exts) => {
const cmds = $exts.flatMap((ext) => {
return [
...(ext.kunkun.customUiCmds ?? []),
...(ext.kunkun.templateUiCmds ?? []),
...(ext.kunkun.headlessCmds ?? [])
].map((cmd) => ({ ...cmd, ext }))
})
devCmdsFuse.setCollection(cmds)
return cmds
})
export const storeSearchExtCmds = derived([storeExtCmds, appState], ([$extCmds, $appState]) => {
return $appState.searchTerm
? cmdsFuse.search($appState.searchTerm).map((result) => result.item)
: $extCmds
})
export const devSearchExtCmds = derived([devStoreExtCmds, appState], ([$extCmds, $appState]) => {
return $appState.searchTerm
? devCmdsFuse.search($appState.searchTerm).map((result) => result.item)
: $extCmds
})

View File

@ -4,3 +4,4 @@ export * from "./winExtMap"
export * from "./extensions"
export * from "./auth"
export * from "./quick-links"
export * from "./apps"

View File

@ -1,7 +1,16 @@
import type { Icon } from "@kksh/api/models"
import { createQuickLinkCommand, getAllQuickLinkCommands } from "@kksh/extension/db"
import type { CmdQuery, QuickLink } from "@kksh/ui/types"
import { get, writable, type Writable } from "svelte/store"
import type { QuickLink } from "@kksh/ui/types"
import { commandScore } from "@kksh/ui/utils"
import Fuse from "fuse.js"
import { derived, get, writable, type Writable } from "svelte/store"
import { appState } from "./appState"
const fuse = new Fuse<QuickLink>([], {
includeScore: true,
threshold: 0.2,
keys: ["name"]
})
export interface QuickLinkAPI {
get: () => QuickLink[]
@ -19,7 +28,9 @@ function createQuickLinksStore(): Writable<QuickLink[]> & QuickLinkAPI {
async function refresh() {
const cmds = await getAllQuickLinkCommands()
store.set(cmds.map((cmd) => ({ link: cmd.data.link, name: cmd.name, icon: cmd.data.icon })))
const items = cmds.map((cmd) => ({ link: cmd.data.link, name: cmd.name, icon: cmd.data.icon }))
store.set(items)
fuse.setCollection(items)
}
async function createQuickLink(name: string, link: string, icon: Icon) {
@ -37,3 +48,22 @@ function createQuickLinksStore(): Writable<QuickLink[]> & QuickLinkAPI {
}
export const quickLinks = createQuickLinksStore()
export const quickLinksFiltered = derived([quickLinks, appState], ([$quickLinks, $appState]) => {
return $appState.searchTerm
? fuse.search($appState.searchTerm).map((result) => result.item)
: $quickLinks
})
// export const quickLinksFiltered = derived([quickLinks, appState], ([$quicklinks, $appState]) => {
// return $quicklinks.filter((lnk) => {
// if ($appState.searchTerm.length === 0) {
// return false
// }
// return (
// commandScore(
// lnk.name,
// $appState.searchTerm
// // []
// ) > 0.5
// )
// })
// })

View File

@ -104,6 +104,7 @@ function createWinExtMapStore(): Writable<WinExtMap> & API {
}
},
registerProcess: async (windowLabel: string, pid: number) => {
console.log("registerProcess", windowLabel, pid)
const winExtMap = get(store)
await registerExtensionSpawnedProcess(windowLabel, pid)
if (!winExtMap[windowLabel]) {
@ -116,6 +117,7 @@ function createWinExtMapStore(): Writable<WinExtMap> & API {
const winExtMap = get(store)
const found = Object.entries(winExtMap).find(([windowLabel, ext]) => ext.pids.includes(pid))
if (!found) {
warn(`Process ${pid} does not have an extension registered, thus will not be killed`)
return
}
const [windowLabel, ext] = found

View File

@ -1,19 +1,13 @@
import { SupabaseAPI } from "@kksh/supabase/api"
import type { Database } from "@kksh/supabase/types"
import { createClient, SupabaseClient } from "@supabase/supabase-js"
import * as sb from "@supabase/supabase-js"
import { SUPABASE_ANON_KEY, SUPABASE_URL } from "./constants"
// export const supabase = createSB(SUPABASE_URL, SUPABASE_ANON_KEY)
export const supabase: SupabaseClient<Database> = createClient<Database>(
SUPABASE_URL,
SUPABASE_ANON_KEY,
{
auth: {
flowType: "pkce"
}
export const supabase: sb.SupabaseClient = sb.createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: {
flowType: "pkce"
}
)
})
export const storage = supabase.storage
export const supabaseExtensionsStorage = supabase.storage.from("extensions")
export const supabaseAPI = new SupabaseAPI(supabase)
// export const supabaseAPI = new SupabaseAPI(supabase)

View File

@ -0,0 +1,12 @@
import * as v from "valibot"
export const WatchEvent = v.object({
type: v.object({
modify: v.object({
kind: v.union([v.literal("data"), v.literal("metadata")]),
mode: v.union([v.literal("any"), v.literal("content")])
})
}),
paths: v.array(v.string())
})
export type WatchEvent = v.InferOutput<typeof WatchEvent>

View File

@ -0,0 +1,44 @@
import { proxyDB, schema } from "@kksh/drizzle"
import { getExtClipboard } from "@kksh/drizzle/api"
import { error, info } from "@tauri-apps/plugin-log"
import * as orm from "drizzle-orm"
/**
* For now, simply delete all clipboard data older than 10 days
*/
export async function cleanClipboard() {
const nDays = 10
const clipboardExt = await getExtClipboard()
const nDaysAgo = new Date()
nDaysAgo.setDate(nDaysAgo.getDate() - nDays)
try {
// Select data older than 10 days to check what will be deleted
const oldClipboardData = await proxyDB
.select({ count: orm.count() })
.from(schema.extensionData)
.where(
orm.and(
orm.eq(schema.extensionData.extId, clipboardExt.extId),
orm.lt(schema.extensionData.createdAt, nDaysAgo.toISOString())
)
)
const nLinesToDelete = oldClipboardData.at(0)?.count ?? 0
console.info(`Found ${nLinesToDelete} clipboard entries older than ${nDays} days to clean up`)
info(`Found ${nLinesToDelete} clipboard entries older than ${nDays} days to clean up`)
// Now delete the old data
const deleted = await proxyDB
.delete(schema.extensionData)
.where(
orm.and(
orm.eq(schema.extensionData.extId, clipboardExt.extId),
orm.lt(schema.extensionData.createdAt, nDaysAgo.toISOString())
)
)
console.log("deleted", deleted)
} catch (e) {
error(`Error during clipboard cleanup: ${e}`)
}
}

View File

@ -0,0 +1,13 @@
import { proxyDB } from "@kksh/drizzle"
import { error, info } from "@tauri-apps/plugin-log"
import { sql } from "drizzle-orm"
export async function vacuumSqlite() {
const statement = sql`VACUUM`
try {
await proxyDB.run(statement)
info("Vacuumed sqlite")
} catch (error) {
console.error(`Failed to vacuum sqlite: ${error}`)
}
}

View File

@ -58,7 +58,7 @@ export async function handleKunkunProtocol(parsedUrl: URL) {
const parsed = v.parse(StorePathSearchParams, params)
openMainWindow()
if (parsed.identifier) {
goto(`/extension/store/${parsed.identifier}`)
goto(i18n.resolveRoute(`/app/extension/store/${parsed.identifier}`))
} else {
goto(i18n.resolveRoute("/app/extension/store"))
}
@ -66,7 +66,7 @@ export async function handleKunkunProtocol(parsedUrl: URL) {
emitRefreshDevExt()
} else if (href.startsWith(DEEP_LINK_PATH_AUTH_CONFIRM)) {
openMainWindow()
goto(`/auth/confirm?${parsedUrl.searchParams.toString()}`)
goto(i18n.resolveRoute(`/app/auth/confirm?${parsedUrl.searchParams.toString()}`))
} else {
console.error("Invalid path:", pathname)
toast.error("Invalid path", {

View File

@ -0,0 +1,19 @@
/**
* This file contains APIs for helper
*/
import { i18n } from "@/i18n"
import type { IHelper } from "@kksh/api"
import { goto } from "$app/navigation"
export const helperAPI: IHelper = {
guideInstallDeno: function (): Promise<void> {
return goto(i18n.resolveRoute("/app/help/deno-install"))
},
guideInstallFfmpeg: function (): Promise<void> {
return goto(i18n.resolveRoute("/app/help/ffmpeg-install"))
},
guideInstallHomebrew: function (): Promise<void> {
return goto(i18n.resolveRoute("/app/help/brew-install"))
}
}

View File

@ -1,7 +1,11 @@
import { getAllWindows } from "@tauri-apps/api/window"
import { app } from "@tauri-apps/api"
import { getAllWindows, getCurrentWindow, type Window } from "@tauri-apps/api/window"
import { isRegistered, register, unregister } from "@tauri-apps/plugin-global-shortcut"
import { debug, info, warn } from "@tauri-apps/plugin-log"
import * as os from "@tauri-apps/plugin-os"
import * as userInput from "tauri-plugin-user-input-api"
import { sendNotificationWithPermission } from "./notification"
import { sleep } from "./time"
/**
* Tauri global shortcut doesn't accept 'Meta' Key. This function maps browser detected keys to Tauri-accepted keys.
@ -14,6 +18,11 @@ export function mapKeyToTauriKey(key: string): string {
return key
}
/**
* Registers a global hotkey for the application. If the hotkey is already registered, it will be unregistered first.
* When the hotkey is pressed, it toggles the visibility and focus of the main window.
* @param hotkeyStr - The hotkey string to register.
*/
export async function registerAppHotkey(hotkeyStr: string) {
if (await isRegistered(hotkeyStr)) {
warn(`Hotkey (${hotkeyStr}) already registered`)
@ -39,13 +48,17 @@ export async function registerAppHotkey(hotkeyStr: string) {
mainWin.setFocus()
}
} else {
mainWin.show()
mainWin.setFocus()
mainWin.show().then(() => mainWin.setFocus())
}
}
})
}
/**
* Updates the application's global hotkey. If an old hotkey is provided, it will be unregistered first.
* @param newHotkey - The new hotkey combination to register.
* @param oldHotkey - The old hotkey combination to unregister, if any.
*/
export async function updateAppHotkey(newHotkey: string[], oldHotkey?: string[] | null) {
if (oldHotkey) {
const hotkeyStr = oldHotkey.map(mapKeyToTauriKey).join("+")
@ -56,3 +69,44 @@ export async function updateAppHotkey(newHotkey: string[], oldHotkey?: string[]
const hotkeyStr = newHotkey.map(mapKeyToTauriKey).join("+")
return registerAppHotkey(hotkeyStr)
}
/**
* Simulates a key combination press and release.
* @param keys - The array of keys to press and release.
*/
export async function applyKeyComb(keys: userInput.Key[]) {
// await Promise.all(keys.map((key) => userInput.key("KeyPress", key)))
for (const key of keys) {
await userInput.key("KeyPress", key)
await sleep(100)
}
await sleep(150)
for (const key of keys) {
await userInput.key("KeyRelease", key)
await sleep(100)
}
}
/**
* Simulates a paste operation based on the operating system.
* On macOS, it uses Command+V. On Windows and Linux, it uses Shift+Insert.
*/
export async function paste() {
const _platform = os.platform()
if (_platform === "macos") {
return applyKeyComb(["MetaLeft", "KeyV"])
} else if (_platform === "windows" || _platform === "linux") {
return applyKeyComb(["ShiftLeft", "Insert"])
} else {
console.error("Unsupported platform: " + _platform)
}
}
export async function hideAndPaste(win?: Window) {
return app
.hide()
.then(() => sleep(60))
.then(() => (win ?? getCurrentWindow()).hide())
.then(() => sleep(60))
.then(() => paste())
}

View File

@ -1,24 +1,38 @@
import { appConfig } from "@/stores"
import { appConfig, extensions } from "@/stores"
import { getCurrentWindow } from "@tauri-apps/api/window"
import { info } from "@tauri-apps/plugin-log"
import { error, info } from "@tauri-apps/plugin-log"
import { dev } from "$app/environment"
import { cleanClipboard } from "./clipboard"
import { vacuumSqlite } from "./db"
import { mapKeyToTauriKey, registerAppHotkey } from "./hotkey"
import { listenToReloadOneExtension } from "./tauri-events"
/**
* Initialize the app
*/
export function init() {
export async function init() {
const window = getCurrentWindow()
if (window.label === "main") {
initMainWindow()
}
if (!dev) {
document.addEventListener("contextmenu", function (event) {
event.preventDefault()
console.warn("contextmenu disabled in release mode", event)
listenToReloadOneExtension(({ payload: { extPath } }) => {
info(`listenToReloadOneExtension in main window: ${extPath}`)
extensions.reloadExtension(extPath)
})
}
await cleanClipboard()
.then(() => {
info("Cleaned clipboard")
})
.catch((e) => {
error(`Failed to clean clipboard: ${e}`)
})
vacuumSqlite()
if (!dev) {
// document.addEventListener("contextmenu", function (event) {
// event.preventDefault()
// console.warn("contextmenu disabled in release mode", event)
// })
}
}
export function initMainWindow() {
@ -31,6 +45,6 @@ export function initMainWindow() {
info(`Registering hotkey: ${hotkeyStr}`)
registerAppHotkey(hotkeyStr)
} else {
console.log("No hotkey found in confi")
console.log("No hotkey found in config")
}
}

View File

@ -76,7 +76,10 @@ export function goHomeOrCloseOnEscapeWithInput(e: KeyboardEvent) {
export async function globalKeyDownHandler(e: KeyboardEvent) {
keys.keydown(e.key)
const _platform = platform()
if ((_platform === "macos" && e.metaKey) || (_platform === "windows" && e.ctrlKey)) {
if (
(_platform === "macos" && e.metaKey) ||
((_platform === "windows" || _platform === "linux") && e.ctrlKey)
) {
if (e.key === ",") {
e.preventDefault()
goto(i18n.resolveRoute("/app/settings"))
@ -94,7 +97,7 @@ export async function globalKeyDownHandler(e: KeyboardEvent) {
await appWin.hide()
location.reload()
setTimeout(() => {
appWin.show()
appWin.show().then(() => appWin.setFocus())
}, 1_000)
}
}

View File

@ -0,0 +1,19 @@
import { parseAPIVersion } from "@kksh/extension/load"
import type { ExtPackageJsonExtra } from "@kunkunapi/src/models/manifest"
import semver from "semver"
/**
* Decide the serialization method for kkrpc based on the api version
* apiVersion is populated in loadExtensionManifestFromDisk, but we parse it again
* @param apiVersion - The version of the @kksh/api
* @returns "superjson" or "json"
*/
export function decideKkrpcSerialization(ext: ExtPackageJsonExtra): "superjson" | "json" {
const apiVersion = parseAPIVersion(ext.dependencies || {})
if (apiVersion && semver.lte(apiVersion, "0.1.5")) {
// 0.1.6 is the first version that supports superjson and default to use superjson
return "json"
}
// fallback default to use superjson, some extensions might not install @kksh/api
return "superjson"
}

View File

@ -16,6 +16,8 @@ export const FileDragOver = "tauri://drag-over"
export const NewClipboardItemAddedEvent = "new_clipboard_item_added"
export const RefreshConfigEvent = "kunkun://refresh-config"
export const RefreshExtEvent = "kunkun://refresh-extensions"
export const ReloadOneExtensionEvent = "kunkun://reload-one-extension"
export function listenToFileDrop(cb: EventCallback<{ paths: string[] }>) {
return listen<{ paths: string[] }>(FileDragDrop, cb)
}
@ -55,3 +57,11 @@ export function emitRefreshDevExt() {
export function listenToRefreshDevExt(cb: EventCallback<null>) {
return listen(DEEP_LINK_PATH_REFRESH_DEV_EXTENSION, cb)
}
export function emitReloadOneExtension(extPath: string) {
return emitTo("main", ReloadOneExtensionEvent, { extPath })
}
export function listenToReloadOneExtension(cb: EventCallback<{ extPath: string }>) {
return listen(ReloadOneExtensionEvent, cb)
}

View File

@ -1,8 +1,7 @@
import { extensions } from "@/stores"
import { supabaseAPI } from "@/supabase"
import { isCompatible } from "@kksh/api"
import type { ExtPackageJsonExtra } from "@kksh/api/models"
import { greaterThan } from "@std/semver"
import { getExtensionsLatestPublishByIdentifier } from "@kksh/sdk"
import { relaunch } from "@tauri-apps/plugin-process"
import { check } from "@tauri-apps/plugin-updater"
import { gt } from "semver"
@ -32,11 +31,22 @@ export async function checkSingleExtensionUpdate(
installedExt: ExtPackageJsonExtra,
autoupgrade: boolean
) {
const { data: sbExt, error } = await supabaseAPI.getLatestExtPublish(
installedExt.kunkun.identifier
)
const {
data: sbExt,
error,
response
} = await getExtensionsLatestPublishByIdentifier({
path: {
identifier: "RAG"
}
})
// const { data: sbExt, error } = await supabaseAPI.getLatestExtPublish(
// installedExt.kunkun.identifier
// )
if (error) {
return toast.error(`Failed to check update for ${installedExt.kunkun.identifier}: ${error}`)
return toast.error(
`Failed to check update for ${installedExt.kunkun.identifier}: ${error} (${response.status})`
)
}
if (!sbExt) {
@ -49,10 +59,7 @@ export async function checkSingleExtensionUpdate(
) {
if (autoupgrade) {
await extensions
.upgradeStoreExtension(
sbExt.identifier,
supabaseAPI.translateExtensionFilePathToUrl(sbExt.tarball_path)
)
.upgradeStoreExtension(sbExt.identifier, sbExt.tarball_path)
.then(() => {
toast.success(`${sbExt.name} upgraded`, {
description: `From ${installedExt.version} to ${sbExt.version}`

View File

@ -12,6 +12,7 @@
<svelte:window on:keydown={handleKeyDown} />
<div class="fixed h-12 w-full" data-tauri-drag-region></div>
<Layouts.Center class="min-h-screen py-5">
<Error.RawErrorJSONPreset
title="Error"

View File

@ -1,16 +1,12 @@
<script lang="ts">
import { ParaglideJS } from "@inlang/paraglide-sveltekit"
import { i18n } from "$lib/i18n"
import { onMount } from "svelte"
import "../app.css"
import { init } from "@/utils/init"
import FullScreenLoading from "@/components/common/FullScreenLoading.svelte"
import { appState } from "@/stores/appState"
import { ModeWatcher, ThemeWrapper } from "@kksh/svelte5"
import { Toaster } from "svelte-sonner"
onMount(() => {
init()
})
let { children } = $props()
</script>
@ -18,6 +14,9 @@
<ModeWatcher />
<Toaster richColors closeButton />
<ThemeWrapper>
{#if $appState.fullScreenLoading}
<FullScreenLoading class="bg-background absolute inset-0 z-50" />
{/if}
{@render children()}
</ThemeWrapper>
</ParaglideJS>

View File

@ -1,5 +1,15 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { browser } from "$app/environment"
// Tauri doesn't have a Node.js server to do proper SSR
// so we will use adapter-static to prerender the app (SSG)
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
export const prerender = true
export const ssr = false
export const load = () => {
if (browser) {
const win = getCurrentWebviewWindow()
return { win }
}
}

View File

@ -3,8 +3,10 @@
import { i18n, switchToLanguage } from "@/i18n"
import { setLanguageTag, type AvailableLanguageTag } from "@/paraglide/runtime"
import { appConfig, appState, extensions, quickLinks, winExtMap } from "@/stores"
import { appsLoader } from "@/stores/apps"
import { initDeeplink } from "@/utils/deeplink"
import { updateAppHotkey } from "@/utils/hotkey"
import { init as initApp } from "@/utils/init"
import { globalKeyDownHandler, globalKeyUpHandler, goBackOrCloseOnEscape } from "@/utils/key"
import { listenToWindowBlur } from "@/utils/tauri-events"
import { isInMainWindow } from "@/utils/window"
@ -12,7 +14,7 @@
import { Constants, ViewTransition } from "@kksh/ui"
import type { UnlistenFn } from "@tauri-apps/api/event"
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { attachConsole, error, info } from "@tauri-apps/plugin-log"
import { attachConsole, debug, error, info } from "@tauri-apps/plugin-log"
import { afterNavigate, beforeNavigate } from "$app/navigation"
import { gsap } from "gsap"
import { Flip } from "gsap/Flip"
@ -44,15 +46,14 @@
})
})
let { children } = $props()
let { children, data } = $props()
const unlisteners: UnlistenFn[] = []
onDestroy(() => {
unlisteners.forEach((unlistener) => unlistener())
})
onMount(async () => {
console.log("root layout onMount")
attachConsole().then((unlistener) => unlisteners.push(unlistener))
await attachConsole().then((unlistener) => unlisteners.push(unlistener))
initDeeplink().then((unlistener) => unlisteners.push(unlistener))
shellx
.fixPathEnv()
@ -60,13 +61,14 @@
info("fixed path env")
})
.catch(error)
quickLinks.init()
appConfig.init().then(() => {
initApp()
console.log("appConfig.language", $appConfig.language)
setLanguageTag($appConfig.language as AvailableLanguageTag)
switchToLanguage($appConfig.language as AvailableLanguageTag)
})
appsLoader.init()
if (isInMainWindow()) {
if ($appConfig.triggerHotkey) {
updateAppHotkey($appConfig.triggerHotkey)
@ -78,7 +80,7 @@
// this extra is focused check may be needed because blur event got triggered somehow when window show()
// for edge case: when settings page is opened and focused, switch to main window, the blur event is triggered for main window
if (!isFocused) {
if ($appConfig.hideOnBlur) {
if ($appConfig.hideOnBlur && !$appState.lockHideOnBlur) {
win.hide()
}
}
@ -88,18 +90,18 @@
extensions.init()
unlisteners.push(
await listenToRecordExtensionProcessEvent(async (event) => {
console.log("record extension process event", event)
debug(`record extension process event ${event.payload.pid}`)
winExtMap.registerProcess(event.payload.windowLabel, event.payload.pid)
})
)
unlisteners.push(
await listenToKillProcessEvent((event) => {
console.log("kill process event", event)
debug(`kill process event ${event.payload.pid}`)
winExtMap.unregisterProcess(event.payload.pid)
})
)
}
getCurrentWebviewWindow().show()
data.win?.show().then(() => data.win?.setFocus())
})
</script>

View File

@ -1,7 +1,11 @@
import { getExtensionsFolder, IS_IN_TAURI } from "@/constants"
import * as path from "@tauri-apps/api/path"
import { error } from "@tauri-apps/plugin-log"
import { setStoreCollectionPath } from "@tauri-store/svelte"
import type { LayoutLoad } from "./$types"
export const load: LayoutLoad = async () => {
return { extsInstallDir: IS_IN_TAURI ? await getExtensionsFolder() : "" }
const appDataPath = await path.appDataDir()
await setStoreCollectionPath(await path.join(appDataPath, "kk-config"))
return { extsInstallDir: IS_IN_TAURI ? await getExtensionsFolder() : "", appDataPath }
}

View File

@ -2,38 +2,51 @@
<script lang="ts">
import { commandLaunchers } from "@/cmds"
import { builtinCmds } from "@/cmds/builtin"
import { systemCommands } from "@/cmds/system"
import { systemCommands, systemCommandsFiltered } from "@/cmds/system"
import AppsCmds from "@/components/main/AppsCmds.svelte"
import { i18n } from "@/i18n"
import * as m from "@/paraglide/messages"
import {
appConfig,
appConfigLoaded,
appsFiltered,
appState,
devStoreExts,
installedStoreExts,
quickLinks
devSearchExtCmds,
devStoreExtCmds,
quickLinksFiltered,
storeExtCmds,
storeSearchExtCmds
} from "@/stores"
import { cmdQueries } from "@/stores/cmdQuery"
import { isKeyboardEventFromInputElement } from "@/utils/dom"
import Icon from "@iconify/svelte"
import { db, toggleDevTools } from "@kksh/api/commands"
import { toggleDevTools } from "@kksh/api/commands"
import { Button, Command, DropdownMenu } from "@kksh/svelte5"
import {
BuiltinCmds,
CustomCommandInput,
ExtCmdsGroup,
ExtCmds,
GlobalCommandPaletteFooter,
QuickLinks,
SystemCmds
} from "@kksh/ui/main"
import type { CmdValue } from "@kksh/ui/types"
import { cn, commandScore } from "@kksh/ui/utils"
import { cn } from "@kksh/ui/utils"
import { Ext } from "@kunkunapi/src/models/extension"
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { getCurrentWindow, Window } from "@tauri-apps/api/window"
import { platform } from "@tauri-apps/plugin-os"
import { exit } from "@tauri-apps/plugin-process"
import { goto } from "$app/navigation"
import { ArrowBigUpIcon, CircleXIcon, EllipsisVerticalIcon, RefreshCcwIcon } from "lucide-svelte"
import {
ArrowBigUpIcon,
CircleXIcon,
EllipsisVerticalIcon,
RefreshCcwIcon,
SettingsIcon
} from "lucide-svelte"
import { onMount } from "svelte"
import { Inspect } from "svelte-inspect-value"
import * as v from "valibot"
const win = getCurrentWindow()
let inputEle: HTMLInputElement | null = $state(null)
@ -53,12 +66,16 @@
if (splashscreenWin) {
splashscreenWin.close()
}
win.show()
win.show().then(() => win.setFocus())
})
win.onFocusChanged(({ payload: focused }) => {
if (focused) {
win.show()
inputEle?.focus()
win
.show()
.then(() => win.setFocus())
.finally(() => {
inputEle?.focus()
})
}
})
inputEle?.focus()
@ -66,8 +83,11 @@
// wait for appConfig store to be loaded, it's async and saved to disk when changed, so we use another store appConfigLoaded
// to keep track of the loading status
if (loaded) {
console.log("appConfig.get().onBoarded", appConfig.get().onBoarded)
if (!appConfig.get().onBoarded) {
goto(i18n.resolveRoute("/app/help/onboarding"))
setTimeout(() => {
goto(i18n.resolveRoute("/app/help/onboarding"))
}, 300)
}
}
})
@ -86,16 +106,21 @@
}
}}
/>
<!--
<Inspect name="devStoreExts" value={$devStoreExts} />
<Inspect name="extensions" value={$extensions} />
<Inspect name="installedStoreExts" value={$installedStoreExts} />
<Inspect name="storeSearchExtCmds" value={$storeSearchExtCmds} />
<Inspect name="devSearchExtCmds" value={$devSearchExtCmds} />
<Inspect name="storeExtCmds" value={$storeExtCmds} />
<Inspect name="devStoreExtCmds" value={$devStoreExtCmds} />
<Inspect name="$appState.searchTerm" value={$appState.searchTerm} />
-->
<Command.Root
class={cn("h-screen rounded-lg border shadow-md")}
class={cn("h-screen rounded-lg shadow-md")}
bind:value={$appState.highlightedCmd}
filter={(value, search, keywords) => {
return commandScore(
value.startsWith("{") ? (JSON.parse(value) as CmdValue).cmdName : value,
search,
keywords
)
}}
shouldFilter={false}
loop
>
<CustomCommandInput
@ -137,7 +162,7 @@
<DropdownMenu.Trigger>
<Button variant="outline" size="icon"><EllipsisVerticalIcon /></Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-80">
<DropdownMenu.Content class="w-fit min-w-80">
<DropdownMenu.Group>
<DropdownMenu.Item onclick={() => exit()}>
<CircleXIcon class="h-4 w-4 text-red-500" />
@ -156,18 +181,27 @@
<DropdownMenu.Item onclick={toggleDevTools}>
<Icon icon="mingcute:code-fill" class="mr-2 h-5 w-5 text-green-500" />
{m.home_command_input_dropdown_toggle_devtools()}
<DropdownMenu.Shortcut
><span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+I</span
></DropdownMenu.Shortcut
>
<DropdownMenu.Shortcut>
<span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+I </span>
</DropdownMenu.Shortcut>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => location.reload()}>
<RefreshCcwIcon class="mr-2 h-4 w-4 text-green-500" />
{m.home_command_input_dropdown_reload_window()}
<DropdownMenu.Shortcut
><span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+R</span
></DropdownMenu.Shortcut
>
<DropdownMenu.Shortcut>
<span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+R </span>
</DropdownMenu.Shortcut>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => goto(i18n.resolveRoute("/app/settings"))}>
<SettingsIcon class="mr-2 h-4 w-4 text-green-500" />
{m.home_command_input_dropdown_open_preference()}
<DropdownMenu.Shortcut>
{#if platform() === "macos"}
<span class="flex items-center">⌘+Comma</span>
{:else}
<span class="flex items-center">Ctrl+Comma</span>
{/if}
</DropdownMenu.Shortcut>
</DropdownMenu.Item>
<DropdownMenu.Item
onclick={() => {
@ -187,27 +221,37 @@
</CustomCommandInput>
<Command.List class="max-h-screen grow">
<Command.Empty data-tauri-drag-region>No results found.</Command.Empty>
{#if $appConfig.extensionsInstallDir && $devStoreExts.length > 0}
<ExtCmdsGroup
extensions={$devStoreExts}
{#if $devSearchExtCmds.length > 0}
<ExtCmds
heading={m.command_group_heading_dev_ext()}
extCmds={$devSearchExtCmds}
hmr={$appConfig.hmr}
isDev={true}
onExtCmdSelect={commandLaunchers.onExtCmdSelect}
hmr={$appConfig.hmr}
/>
{/if}
{#if $appConfig.extensionsInstallDir && $installedStoreExts.length > 0}
<ExtCmdsGroup
extensions={$installedStoreExts}
{#if $storeSearchExtCmds.length > 0}
<ExtCmds
heading={m.command_group_heading_ext()}
isDev={false}
extCmds={$storeSearchExtCmds}
hmr={false}
isDev={false}
onExtCmdSelect={commandLaunchers.onExtCmdSelect}
/>
{/if}
<QuickLinks quickLinks={$quickLinks} />
<BuiltinCmds builtinCmds={$builtinCmds} />
<SystemCmds {systemCommands} />
{#if $builtinCmds.length > 0}
<BuiltinCmds builtinCmds={$builtinCmds} />
{/if}
{#if $systemCommandsFiltered.length > 0}
<SystemCmds systemCommands={$systemCommandsFiltered} />
{/if}
{#if $appsFiltered.length > 0}
<AppsCmds apps={$appsFiltered} />
{/if}
{#if $quickLinksFiltered.length > 0}
<QuickLinks quickLinks={$quickLinksFiltered} />
{/if}
</Command.List>
<GlobalCommandPaletteFooter />
</Command.Root>

View File

@ -1,24 +1,32 @@
<script lang="ts">
import { goBack, goHome } from "@/utils/route"
import { listenToNewClipboardItem } from "@/utils/tauri-events"
import { hideAndPaste } from "@/utils/hotkey"
import { goHome } from "@/utils/route"
import { listenToNewClipboardItem, listenToWindowFocus } from "@/utils/tauri-events"
import Icon from "@iconify/svelte"
import { ClipboardContentType, db } from "@kksh/api/commands"
import { ClipboardContentType } from "@kksh/api/commands"
import { SearchModeEnum, SQLSortOrderEnum, type ExtData } from "@kksh/api/models"
import { db } from "@kksh/drizzle"
import { Button, Command, Resizable } from "@kksh/svelte5"
import { Constants } from "@kksh/ui"
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"
import type { UnlistenFn } from "@tauri-apps/api/event"
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { platform } from "@tauri-apps/plugin-os"
import { ArrowLeft, FileQuestionIcon, ImageIcon, LetterTextIcon } from "lucide-svelte"
import { onDestroy, onMount, type Snippet } from "svelte"
import { toast } from "svelte-sonner"
import clipboard from "tauri-plugin-clipboard-api"
import ContentPreview from "./content-preview.svelte"
const _platform = platform()
let inputEle = $state<HTMLInputElement | null>(null)
const curWin = getCurrentWebviewWindow()
let searchTerm = $state("")
let clipboardHistoryList = $state<ExtData[]>([])
let highlightedItemValue = $state<string>("")
let highlighted = $state<ExtData | null>(null)
let unlistenClipboard = $state<UnlistenFn | null>(null)
let unlistenFocusEvt = $state<UnlistenFn | null>(null)
let isScrolling = $state(false)
let page = $state(1)
@ -67,10 +75,19 @@
}).then((unlisten) => {
unlistenClipboard = unlisten
})
listenToWindowFocus(async () => {
if (inputEle) {
inputEle.focus()
}
}).then((unlisten) => {
unlistenFocusEvt = unlisten
})
})
onDestroy(() => {
unlistenClipboard?.()
unlistenFocusEvt?.()
})
$effect(() => {
@ -155,29 +172,41 @@
}
}
function writeToClipboard(data: ExtData) {
if (!data.data) {
toast.warning("No data found")
return Promise.reject(new Error("No data found"))
}
const dataType = data?.dataType as ClipboardContentType
switch (dataType) {
case "Text":
return clipboard.writeText(data.data)
case "Image":
return clipboard.writeImageBase64(data.data)
case "Html":
return clipboard.writeHtmlAndText(data.data, data.searchText ?? data.data)
case "Rtf":
return clipboard.writeRtf(data.data)
default:
return Promise.reject(new Error("Unsupported data type: " + dataType))
}
}
function onItemSelected(dataId: number) {
// fetch data from db
db.getExtensionDataById(dataId)
.then((data) => {
if (!data || !data.data) {
return
}
const dataType = data?.dataType as ClipboardContentType
switch (dataType) {
case "Text":
return clipboard.writeText(data.data)
case "Image":
return clipboard.writeImageBase64(data.data)
case "Html":
return clipboard.writeHtml(data.data)
case "Rtf":
return clipboard.writeRtf(data.data)
default:
return Promise.reject(new Error("Unsupported data type: " + dataType))
console.log("data", data)
if (!data) {
toast.warning("No data found")
return Promise.reject(new Error("No data found"))
}
return writeToClipboard(data).then(async () => {
return hideAndPaste(curWin)
})
})
.then(() => toast.success("Copied to clipboard"))
.catch((err) => {
console.error(err)
toast.error("Failed to fetch data from db", {
description: err.message
})
@ -219,6 +248,7 @@
autofocus
placeholder="Type a command or search..."
leftSlot={leftSlot as Snippet}
bind:ref={inputEle}
bind:value={searchTerm}
/>
<Resizable.PaneGroup direction="horizontal" class="w-full rounded-lg">

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { cn } from "@/utils"
import { db } from "@kksh/api/commands"
import type { ExtData } from "@kksh/api/models"
import { db } from "@kksh/drizzle"
import { Resizable, Separator } from "@kksh/svelte5"
import { convertFileSrc } from "@tauri-apps/api/core"
import DOMPurify from "dompurify"

View File

@ -8,6 +8,8 @@
import * as userInput from "tauri-plugin-user-input-api"
import { type InputEvent } from "tauri-plugin-user-input-api"
let { data } = $props()
const SymbolMap = {
Alt: "⎇",
AltGr: "⌥",
@ -97,10 +99,7 @@
}
$effect(() => {
const win = getCurrentWebviewWindow()
if (win) {
win.show()
}
data.win?.show().then(() => data.win?.setFocus())
userInput.setEventTypes([userInput.EventTypeEnum.KeyPress, userInput.EventTypeEnum.KeyRelease])
userInput.startListening((evt: InputEvent) => {

View File

@ -8,23 +8,24 @@
import * as clipboard from "tauri-plugin-clipboard-api"
let image = $state<string | null>(null)
const appWin = getCurrentWebviewWindow()
let { data } = $props()
let originalSize = $state<{ width: number; height: number } | null>(null)
let originalScaleFactor = $state<number | null>(null)
let scale = $state<number>(1)
let currentSize = $derived(
originalSize ? { width: originalSize.width * scale, height: originalSize.height * scale } : null
)
const win = getCurrentWebviewWindow()
$effect(() => {
if (currentSize) {
appWin.setSize(new LogicalSize(currentSize.width, currentSize.height))
win.setSize(new LogicalSize(currentSize.width, currentSize.height))
}
})
async function getWindowSize() {
const size = await appWin.outerSize()
const scaleFactor = originalScaleFactor ?? (await appWin.scaleFactor())
const size = await win.outerSize()
const scaleFactor = originalScaleFactor ?? (await win.scaleFactor())
const logicalSize = size.toLogical(scaleFactor)
return { logicalSize, scaleFactor }
}
@ -36,7 +37,7 @@
image = b64
})
.finally(() => {
appWin.show()
data.win?.show().then(() => data.win?.setFocus())
})
const { logicalSize, scaleFactor } = await getWindowSize()
originalSize = { width: logicalSize.width, height: logicalSize.height }
@ -67,13 +68,13 @@
<svelte:window
on:keydown={(e) => {
if (e.key === "Escape") {
appWin.close()
win.close()
}
}}
/>
<Button size="icon" variant="ghost" class="fixed left-2 top-2" onclick={() => appWin.close()}
><CircleX /></Button
>
<Button size="icon" variant="ghost" class="fixed left-2 top-2" onclick={() => win.close()}>
<CircleX />
</Button>
<main class="z-50 h-screen w-screen overflow-hidden" data-tauri-drag-region>
{#if image}
<img

View File

@ -1,24 +1,32 @@
<script lang="ts">
import { getExtensionsFolder } from "@/constants"
import { appState, extensions } from "@/stores"
import { supabaseAPI } from "@/supabase"
import { goBackOnEscapeClearSearchTerm, goHomeOnEscapeClearSearchTerm } from "@/utils/key"
import { keys } from "@/stores/keys"
import { goBack, goHome } from "@/utils/route"
import { SBExt } from "@kksh/supabase/models"
import type { ExtPublishMetadata } from "@kksh/supabase/models"
import { type Tables } from "@kksh/supabase/types"
import { Action as ActionSchema, ExtensionStoreListItem, ExtPublish } from "@kksh/api/models"
import { Action } from "@kksh/api/ui"
import {
getExtensionsLatestPublishByIdentifier,
postExtensionsIncrementDownloads
} from "@kksh/sdk"
import { Button, Command } from "@kksh/svelte5"
import { Constants } from "@kksh/ui"
import { ExtListItem } from "@kksh/ui/extension"
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"
import { platform } from "@tauri-apps/plugin-os"
import { goto } from "$app/navigation"
import { ArrowLeft } from "lucide-svelte"
import type { Snippet } from "svelte"
import { onMount, type Snippet } from "svelte"
import { toast } from "svelte-sonner"
import type { Action as SvelteAction } from "svelte/action"
import { getInstallExtras } from "./[identifier]/helper.js"
let { data } = $props()
const { storeExtList, installedStoreExts, installedExtsMap, upgradableExpsMap } = data
const { storeExtList, installedExtsMap, upgradableExpsMap } = data
const _platform = platform()
let actionPanelOpen = $state(false)
let listviewInputRef = $state<HTMLInputElement | null>(null)
let highlightedCmdValue = $state("")
// function isUpgradeable(item: DbExtItem): boolean {
// if (!item.version) return true // latest extensions always have version, this check should be removed later
@ -30,56 +38,133 @@
// )
// }
function onExtItemSelected(ext: SBExt) {
onMount(() => {
// setTimeout(() => {
// console.log("focus", listviewInputRef)
// listviewInputRef?.focus()
// }, 3_000)
})
const inputAction: SvelteAction = (node) => {
// the node has been mounted in the DOM
$effect(() => {
// setup goes here
console.log("inputAction", node)
listviewInputRef?.focus()
return () => {
// teardown goes here
}
})
}
$effect(() => {
function docKeyDown(e: KeyboardEvent) {
if (e.key === "/") {
listviewInputRef?.focus()
}
}
document.addEventListener("keydown", docKeyDown)
return () => {
document.removeEventListener("keydown", docKeyDown)
}
})
function onExtItemSelected(ext: ExtensionStoreListItem) {
goto(`./store/${ext.identifier}`)
}
async function onExtItemUpgrade(ext: SBExt) {
const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
if (res.error)
async function onExtItemUpgrade(ext: ExtensionStoreListItem) {
const { data, error, response } = await getExtensionsLatestPublishByIdentifier({
path: {
identifier: ext.identifier
}
})
if (error)
return toast.error("Fail to get latest extension", {
description: res.error.message
description: error.error
})
const tarballUrl = res.data.tarball_path.startsWith("http")
? res.data.tarball_path
: supabaseAPI.translateExtensionFilePathToUrl(res.data.tarball_path)
const installExtras = await getInstallExtras(
res.data as Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
)
const installExtras = await getInstallExtras(data?.metadata)
return extensions
.upgradeStoreExtension(ext.identifier, tarballUrl, installExtras)
.upgradeStoreExtension(ext.identifier, data.tarball_path, installExtras)
.then((newExt) => {
toast.success(`${ext.name} Upgraded to ${newExt.version}`)
})
}
async function onExtItemInstall(ext: SBExt) {
const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
if (res.error)
async function onExtItemInstall(ext: ExtensionStoreListItem) {
const { data, error, response } = await getExtensionsLatestPublishByIdentifier({
path: {
identifier: ext.identifier
}
})
if (error)
return toast.error("Fail to get latest extension", {
description: res.error.message
description: error.error
})
const tarballUrl = res.data.tarball_path.startsWith("http")
? res.data.tarball_path
: supabaseAPI.translateExtensionFilePathToUrl(res.data.tarball_path)
const installExtras = await getInstallExtras(
res.data as Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
)
const installExtras = await getInstallExtras(data?.metadata)
const installDir = await getExtensionsFolder()
return extensions
.installFromTarballUrl(tarballUrl, installDir, installExtras)
.installFromTarballUrl(data.tarball_path, installDir, installExtras)
.then(() => toast.success(`Plugin ${ext.name} Installed`))
.then(() =>
supabaseAPI.incrementDownloads({
identifier: ext.identifier,
version: ext.version
postExtensionsIncrementDownloads({
body: {
identifier: ext.identifier,
version: ext.version
}
})
.then(({ error }) => {
if (error) {
console.error(error)
}
})
.catch(console.error)
)
}
function onActionPanelBlur() {
setTimeout(() => {
listviewInputRef?.focus()
}, 300)
}
$effect(() => {
void $keys
const keySet = keys.getSet()
if (keySet.size === 2) {
if (keySet.has(_platform === "macos" ? "Meta" : "Control") && keySet.has("k")) {
setTimeout(() => {
actionPanelOpen = !actionPanelOpen
if (!actionPanelOpen) {
onActionPanelBlur()
listviewInputRef?.focus()
}
}, 100)
}
}
})
function onkeydown(e: KeyboardEvent) {
if (e.key === "Escape") {
if (document.activeElement === listviewInputRef) {
goHome()
}
}
}
let highlightedCmd = $derived.by(() => {
void highlightedCmdValue
const ext = storeExtList.find((ext) => ext.identifier === highlightedCmdValue)
if (ext) {
return ext
}
return null
})
</script>
<svelte:window on:keydown={goHomeOnEscapeClearSearchTerm} />
<svelte:window {onkeydown} />
{#snippet leftSlot()}
<Button
variant="outline"
@ -91,12 +176,27 @@
<ArrowLeft class="size-4" />
</Button>
{/snippet}
<Command.Root class="h-screen rounded-lg border shadow-md" loop>
<Command.Root class="h-screen rounded-lg border shadow-md" loop bind:value={highlightedCmdValue}>
<CustomCommandInput
bind:ref={listviewInputRef}
autofocus
placeholder="Type a command or search..."
placeholder="Type / to focus"
leftSlot={leftSlot as Snippet}
bind:value={$appState.searchTerm}
onkeydown={(e) => {
if (e.key === "Enter") {
const modifier = _platform === "macos" ? e.metaKey : e.ctrlKey
if (modifier) {
if (highlightedCmd) {
onExtItemInstall(highlightedCmd)
}
} else {
if (highlightedCmd) {
onExtItemSelected(highlightedCmd)
}
}
}
}}
/>
<Command.List class="max-h-screen grow">
<Command.Empty>No results found.</Command.Empty>
@ -105,11 +205,40 @@
{ext}
installedVersion={$installedExtsMap[ext.identifier]}
isUpgradable={!!$upgradableExpsMap[ext.identifier]}
onSelect={() => onExtItemSelected(ext)}
onSelect={() => {
onExtItemSelected(ext)
}}
onUpgrade={() => onExtItemUpgrade(ext)}
onInstall={() => onExtItemInstall(ext)}
/>
{/each}
</Command.List>
<GlobalCommandPaletteFooter />
<GlobalCommandPaletteFooter
defaultAction="Show Details"
bind:actionPanelOpen
{onActionPanelBlur}
actionPanel={new Action.ActionPanel({
title: "Actions",
items: [
new Action.Action({
title: `Install (${_platform === "macos" ? "⌘" : "Ctrl"} + ⏎)`,
value: "install"
})
]
})}
onActionSelected={(value) => {
if (value === "install") {
console.log("install")
if (highlightedCmd) {
onExtItemInstall(highlightedCmd)
}
}
}}
onDefaultActionSelected={() => {
console.log("default install")
if (highlightedCmd) {
onExtItemInstall(highlightedCmd)
}
}}
/>
</Command.Root>

View File

@ -1,43 +1,47 @@
import { appConfig, extensions, installedStoreExts } from "@/stores"
import { supabaseAPI } from "@/supabase"
import type { ExtPackageJsonExtra } from "@kksh/api/models"
import { appConfig, appState, extensions, installedStoreExts } from "@/stores"
import { goHome } from "@/utils/route"
// import { supabaseAPI } from "@/supabase"
import type { ExtensionStoreListItem, ExtPackageJsonExtra } from "@kksh/api/models"
import { isExtPathInDev, isUpgradable } from "@kksh/extension"
import { SBExt } from "@kksh/supabase/models"
import { error } from "@sveltejs/kit"
import { getExtensionsStoreList } from "@kksh/sdk"
import { toast } from "svelte-sonner"
import { derived, get, type Readable } from "svelte/store"
import type { PageLoad } from "./$types"
export const load: PageLoad = async (): Promise<{
storeExtList: SBExt[]
export const load: PageLoad = (): Promise<{
storeExtList: ExtensionStoreListItem[]
installedStoreExts: Readable<ExtPackageJsonExtra[]>
installedExtsMap: Readable<Record<string, string>>
upgradableExpsMap: Readable<Record<string, boolean>>
}> => {
const storeExtList = await supabaseAPI.getExtList()
// map identifier to extItem
const storeExtsMap = Object.fromEntries(storeExtList.map((ext) => [ext.identifier, ext]))
const _appConfig = get(appConfig)
// const installedStoreExts = derived(extensions, ($extensions) => {
// if (!_appConfig.extensionPath) return []
// return $extensions.filter((ext) => !isExtPathInDev(_appConfig.extensionPath!, ext.extPath))
// })
// map installed extension identifier to version
const installedExtsMap = derived(installedStoreExts, ($exts) =>
Object.fromEntries($exts.map((ext) => [ext.kunkun.identifier, ext.version]))
)
const upgradableExpsMap = derived(installedStoreExts, ($exts) =>
Object.fromEntries(
$exts.map((ext) => {
const dbExt: SBExt | undefined = storeExtsMap[ext.kunkun.identifier]
return [ext.kunkun.identifier, dbExt ? isUpgradable(dbExt, ext.version) : false]
})
)
)
return {
storeExtList,
installedStoreExts,
installedExtsMap,
upgradableExpsMap
}
appState.setFullScreenLoading(true)
return getExtensionsStoreList()
.then(({ data: storeExtList, error, response }) => {
storeExtList = storeExtList ?? []
if (error) {
toast.error(`Failed to load extension store: ${error} (${response.status})`)
goHome()
}
const storeExtsMap = Object.fromEntries(storeExtList.map((ext) => [ext.identifier, ext]))
const installedExtsMap = derived(installedStoreExts, ($exts) =>
Object.fromEntries($exts.map((ext) => [ext.kunkun.identifier, ext.version]))
)
const upgradableExpsMap = derived(installedStoreExts, ($exts) =>
Object.fromEntries(
$exts.map((ext) => {
const dbExt: ExtensionStoreListItem | undefined = storeExtsMap[ext.kunkun.identifier]
return [ext.kunkun.identifier, dbExt ? isUpgradable(dbExt, ext.version) : false]
})
)
)
return {
storeExtList,
installedStoreExts,
installedExtsMap,
upgradableExpsMap
}
})
.finally(() => {
appState.setFullScreenLoading(false)
})
}

View File

@ -2,11 +2,8 @@
import { getExtensionsFolder } from "@/constants.js"
import { i18n } from "@/i18n.js"
import { extensions, installedStoreExts } from "@/stores/extensions.js"
import { supabaseAPI } from "@/supabase"
import { goBack } from "@/utils/route.js"
import { ExtPackageJson } from "@kksh/api/models"
import { ExtPublishMetadata } from "@kksh/supabase/models"
import type { Tables } from "@kksh/supabase/types"
import { DBExtension, ExtensionStoreListItem, ExtPackageJson, ExtPublish } from "@kksh/api/models"
import { postExtensionsIncrementDownloads } from "@kksh/sdk"
import { Button } from "@kksh/svelte5"
import { cn } from "@kksh/svelte5/utils"
import { Constants } from "@kksh/ui"
@ -22,10 +19,8 @@
import { getInstallExtras } from "./helper"
const { data } = $props()
const extPublish: Tables<"ext_publish"> & { metadata: ExtPublishMetadata } = $derived(
data.extPublish
)
const ext: Tables<"extensions"> = $derived(data.ext)
const extPublish = $derived(data.extPublish)
const ext = $derived(data.ext)
const manifest = $derived(data.manifest)
const installedExt = storeDerived(installedStoreExts, ($e) => {
return $e.find((e) => e.kunkun.identifier === extPublish.identifier)
@ -77,26 +72,29 @@
}, 500)
})
const demoImages = $derived(
extPublish.demo_images.map((src) => supabaseAPI.translateExtensionFilePathToUrl(src))
)
const demoImages = $derived(extPublish.demo_images)
async function onInstallSelected() {
loading.install = true
const tarballUrl = extPublish.tarball_path.startsWith("http")
? extPublish.tarball_path
: supabaseAPI.translateExtensionFilePathToUrl(extPublish.tarball_path)
const installExtras = await getInstallExtras(extPublish)
const installExtras = await getInstallExtras(extPublish.metadata)
const installDir = await getExtensionsFolder()
return extensions
.installFromTarballUrl(tarballUrl, installDir, installExtras)
.installFromTarballUrl(extPublish.tarball_path, installDir, installExtras)
.then(() => toast.success(`Plugin ${extPublish.name} Installed`))
.then((loadedExt) => {
info(`Successfully installed ${extPublish.name}`)
supabaseAPI.incrementDownloads({
identifier: extPublish.identifier,
version: extPublish.version
postExtensionsIncrementDownloads({
body: {
identifier: extPublish.identifier,
version: extPublish.version
}
})
.then(({ error }) => {
if (error) {
console.error(error)
}
})
.catch(console.error)
showBtn.install = false
showBtn.uninstall = true
})
@ -111,9 +109,8 @@
function onUpgradeSelected() {
loading.upgrade = true
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(extPublish.tarball_path)
return extensions
.upgradeStoreExtension(extPublish.identifier, tarballUrl)
.upgradeStoreExtension(extPublish.identifier, extPublish.tarball_path)
.then((newExt) => {
toast.success(
`${extPublish.name} Upgraded from ${$installedExt?.version} to ${newExt.version}`

View File

@ -1,53 +1,57 @@
import { extensions } from "@/stores"
import { supabaseAPI } from "@/supabase"
import { KunkunExtManifest, type ExtPackageJsonExtra } from "@kksh/api/models"
import { ExtPublishMetadata } from "@kksh/supabase/models"
import type { Tables } from "@kksh/supabase/types"
import { appState } from "@/stores"
import { DBExtension, ExtPublish, KunkunExtManifest } from "@kksh/api/models"
import { getExtensionsByIdentifier, getExtensionsLatestPublishByIdentifier } from "@kksh/sdk"
import { error } from "@sveltejs/kit"
import { toast } from "svelte-sonner"
import * as v from "valibot"
import type { PageLoad } from "./$types"
export const load: PageLoad = async ({
export const load: PageLoad = ({
params
}): Promise<{
extPublish: Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
ext: Tables<"extensions">
extPublish: ExtPublish
ext: DBExtension
manifest: KunkunExtManifest
params: {
identifier: string
}
}> => {
const { error: dbError, data: extPublish } = await supabaseAPI.getLatestExtPublish(
params.identifier
)
const metadataParse = v.safeParse(ExtPublishMetadata, extPublish?.metadata ?? {})
if (dbError) {
return error(400, {
message: dbError.message
appState.setFullScreenLoading(true)
return getExtensionsLatestPublishByIdentifier({
path: {
identifier: params.identifier
}
})
.then(async ({ data: extPublish, error: err, response }) => {
if (err || !extPublish) {
return error(response.status, {
message: "Failed to get extension publish"
})
}
const {
data: ext,
error: extError,
response: extRes
} = await getExtensionsByIdentifier({
path: {
identifier: params.identifier
}
})
if (extError || !ext) {
console.error(extError)
return error(extRes.status, {
message: extError.error || "Failed to get extension"
})
}
return {
extPublish: v.parse(ExtPublish, extPublish),
ext,
manifest: v.parse(KunkunExtManifest, extPublish.manifest),
params
}
})
}
const metadata = metadataParse.success ? metadataParse.output : {}
const parseManifest = v.safeParse(KunkunExtManifest, extPublish.manifest)
if (!parseManifest.success) {
const errMsg = "Invalid extension manifest, you may need to upgrade your app."
toast.error(errMsg)
throw error(400, errMsg)
}
const { data: ext, error: extError } = await supabaseAPI.getExtension(params.identifier)
if (extError) {
return error(400, {
message: extError.message
.finally(() => {
appState.setFullScreenLoading(false)
})
}
return {
extPublish: { ...extPublish, metadata },
ext,
params,
manifest: parseManifest.output
}
}
export const csr = true

View File

@ -1,15 +1,15 @@
import type { ExtPublishMetadata } from "@kksh/supabase/models"
import type { Tables } from "@kksh/supabase/types"
import type { ExtPublishMetadata } from "@kunkunapi/src/models"
export async function getInstallExtras(
ext: Tables<"ext_publish"> & { metadata?: ExtPublishMetadata }
): Promise<{ overwritePackageJson?: string }> {
export async function getInstallExtras(extMetadata?: {
sourceType?: string
source?: string
}): Promise<{ overwritePackageJson?: string }> {
const extras: { overwritePackageJson?: string } = {}
if (ext.metadata?.sourceType) {
if (ext.metadata?.sourceType === "jsr") {
if (ext.metadata?.source) {
if (extMetadata?.sourceType) {
if (extMetadata?.sourceType === "jsr") {
if (extMetadata?.source) {
try {
const res = await fetch(`${ext.metadata.source}/package.json`)
const res = await fetch(`${extMetadata.source}/package.json`)
const pkgJsonContent = await res.text()
extras.overwritePackageJson = pkgJsonContent
} catch (error) {

View File

@ -1,24 +1,34 @@
<script lang="ts">
import DanceTransition from "@/components/dance/dance-transition.svelte"
import { i18n } from "@/i18n"
import { appConfig, winExtMap } from "@/stores"
import { appConfig, appState, winExtMap } from "@/stores"
import { helperAPI } from "@/utils/helper"
import { paste } from "@/utils/hotkey"
import { goBackOnEscape } from "@/utils/key"
import { decideKkrpcSerialization } from "@/utils/kkrpc"
import { goHome } from "@/utils/route"
import { positionToCssStyleString, positionToTailwindClasses } from "@/utils/style"
import { sleep } from "@/utils/time"
import { isInMainWindow } from "@/utils/window"
import { db } from "@kksh/api/commands"
import { CustomPosition, ThemeColor, type Position } from "@kksh/api/models"
import {
constructJarvisServerAPIWithPermissions,
// exposeApiToWindow,
type IApp,
type IUiIframe
type IUiCustom
} from "@kksh/api/ui"
import { toast, type IUiIframeServer1, type IUiIframeServer2 } from "@kksh/api/ui/iframe"
import { toast, type IUiCustomServer1, type IUiCustomServer2 } from "@kksh/api/ui/custom"
import { db } from "@kksh/drizzle"
import { Button } from "@kksh/svelte5"
import { cn } from "@kksh/ui/utils"
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
import {
RECORD_EXTENSION_PROCESS_EVENT,
type IRecordExtensionProcessEvent
} from "@kunkunapi/src/events"
import { emitTo } from "@tauri-apps/api/event"
import { getCurrentWindow } from "@tauri-apps/api/window"
import { info } from "@tauri-apps/plugin-log"
import { goto } from "$app/navigation"
import { IframeParentIO, RPCChannel } from "kkrpc/browser"
import { ArrowLeftIcon, MoveIcon, RefreshCcwIcon, XIcon } from "lucide-svelte"
@ -27,7 +37,7 @@
let { data }: { data: PageData } = $props()
const { loadedExt, url, extPath, extInfoInDB } = data
const appWin = getCurrentWindow()
let extSpawnedProcesses = $state<number[]>([])
let iframeRef: HTMLIFrameElement
let uiControl = $state<{
iframeLoaded: boolean
@ -49,12 +59,12 @@
transparentBg: false
})
const iframeUiAPI: IUiIframeServer2 = {
const iframeUiAPI: IUiCustomServer2 = {
goBack: async () => {
if (isInMainWindow()) {
goto(i18n.resolveRoute("/app/"))
} else {
appWin.close()
data.win?.close()
}
},
hideBackButton: async () => {
@ -106,14 +116,33 @@
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
loadedExt.kunkun.permissions,
loadedExt.extPath
loadedExt.extPath,
{
recordSpawnedProcess: async (pid: number) => {
extSpawnedProcesses = [...extSpawnedProcesses, pid]
// winExtMap.registerProcess(appWin.label, pid)
const curWin = await getCurrentWindow()
await emitTo("main", RECORD_EXTENSION_PROCESS_EVENT, {
windowLabel: curWin.label,
pid
} satisfies IRecordExtensionProcessEvent)
// TODO: record process in a store
},
getSpawnedProcesses: () => Promise.resolve(extSpawnedProcesses),
paste: async () => {
await data.win?.hide()
await sleep(200)
return paste()
}
}
)
const serverAPI2 = {
...serverAPI,
iframeUi: {
...serverAPI.iframeUi,
...iframeUiAPI
} satisfies IUiIframeServer1 & IUiIframeServer2,
} satisfies IUiCustomServer1 & IUiCustomServer2,
helper: helperAPI,
db: new db.JarvisExtDB(extInfoInDB.extId),
kv: new db.KV(extInfoInDB.extId),
app: {
@ -125,7 +154,7 @@
if (isInMainWindow()) {
goHome()
} else {
appWin.close()
data.win?.close()
}
}
@ -133,16 +162,27 @@
setTimeout(() => {
iframeRef.focus()
uiControl.iframeLoaded = true
appState.setFullScreenLoading(false)
}, 300)
}
onMount(() => {
appState.setFullScreenLoading(true)
setTimeout(() => {
appWin.show()
data.win?.setFocus()
}, 200)
if (iframeRef?.contentWindow) {
const io = new IframeParentIO(iframeRef.contentWindow)
const rpc = new RPCChannel(io, { expose: serverAPI2 })
const kkrpcSerialization = decideKkrpcSerialization(loadedExt)
info(
`Establishing kkrpc connection for ${loadedExt.kunkun.identifier} with serialization: ${kkrpcSerialization}`
)
const rpc = new RPCChannel(io, {
expose: serverAPI2,
serialization: {
version: kkrpcSerialization
}
})
} else {
toast.warning("iframeRef.contentWindow not available")
}
@ -155,7 +195,7 @@
})
onDestroy(() => {
winExtMap.unregisterExtensionFromWindow(appWin.label)
winExtMap.unregisterExtensionFromWindow(data.win?.label ?? "")
})
</script>
@ -168,7 +208,7 @@
onclick={onBackBtnClicked}
style={`${positionToCssStyleString(uiControl.backBtnPosition)}`}
>
{#if appWin.label === "main"}
{#if data.win?.label === "main"}
<ArrowLeftIcon class="w-4" />
{:else}
<XIcon class="w-4" />
@ -199,7 +239,6 @@
{/if}
<main class="h-screen">
<DanceTransition delay={300} autoHide={false} show={!uiControl.iframeLoaded} />
<iframe
bind:this={iframeRef}
class={cn("h-full", {

View File

@ -1,11 +1,14 @@
import { KunkunIframeExtParams } from "@/cmds/ext"
import { i18n } from "@/i18n"
import { db, unregisterExtensionWindow } from "@kksh/api/commands"
import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models"
import { db } from "@kksh/drizzle"
import { loadExtensionManifestFromDisk } from "@kksh/extension"
import { error as svError } from "@sveltejs/kit"
import { join } from "@tauri-apps/api/path"
import { error } from "@tauri-apps/plugin-log"
import { goto } from "$app/navigation"
import { toast } from "svelte-sonner"
import * as v from "valibot"
import { z } from "zod"
import type { PageLoad } from "./$types"
@ -20,15 +23,35 @@ export const load: PageLoad = async ({
extInfoInDB: ExtInfoInDB
}> => {
// both query parameter must exist
const rawKunkunIframeExtParams = localStorage.getItem("kunkun-iframe-ext-params")
if (!rawKunkunIframeExtParams) {
toast.error("Invalid extension path or url")
return svError(404, "Invalid extension path or url")
}
// localStorage.removeItem("kunkun-iframe-ext-params")
const parsed = v.safeParse(KunkunIframeExtParams, JSON.parse(rawKunkunIframeExtParams))
if (!parsed.success) {
toast.error("Fail to parse extension params from local storage", {
description: `${v.flatten<typeof KunkunIframeExtParams>(parsed.issues)}`
})
return svError(400, "Fail to parse extension params from local storage")
}
const { url: extUrl, extPath } = parsed.output
console.log("extUrl extPath", extUrl, extPath)
const _extPath = url.searchParams.get("extPath")
const _extUrl = url.searchParams.get("url")
if (!_extPath || !_extUrl) {
toast.error("Invalid extension path or url")
error("Invalid extension path or url")
goto(i18n.resolveRoute("/app/"))
}
const extPath = z.string().parse(_extPath)
const extUrl = z.string().parse(_extUrl)
console.log("_extPath", _extPath)
console.log("_extUrl", _extUrl)
// if (!_extPath || !_extUrl) {
// toast.error("Invalid extension path or url", {
// description: `_extPath: ${_extPath}; _extUrl: ${_extUrl}`
// })
// error("Invalid extension path or url")
// goto(i18n.resolveRoute("/app/"))
// }
// const extPath = z.string().parse(_extPath)
// const extUrl = z.string().parse(_extUrl)
let _loadedExt: ExtPackageJsonExtra | undefined
try {
_loadedExt = await loadExtensionManifestFromDisk(await join(extPath, "package.json"))

View File

@ -3,10 +3,21 @@
import { appState } from "@/stores/appState.js"
import { keys } from "@/stores/keys"
import { winExtMap } from "@/stores/winExtMap.js"
import { listenToFileDrop, listenToRefreshDevExt } from "@/utils/tauri-events.js"
import { helperAPI } from "@/utils/helper.js"
import { paste } from "@/utils/hotkey"
import { decideKkrpcSerialization } from "@/utils/kkrpc.js"
import {
emitReloadOneExtension,
listenToFileDrop,
listenToRefreshDevExt
} from "@/utils/tauri-events.js"
import { sleep } from "@/utils/time.js"
import { isInMainWindow } from "@/utils/window.js"
import { db } from "@kksh/api/commands"
import { constructJarvisServerAPIWithPermissions, type IApp, type IUiWorker } from "@kksh/api/ui"
import {
constructJarvisServerAPIWithPermissions,
type IApp,
type IUiTemplate
} from "@kksh/api/ui"
import {
FormNodeNameEnum,
FormSchema,
@ -15,44 +26,57 @@
NodeNameEnum,
toast,
type IComponent,
type WorkerExtension
} from "@kksh/api/ui/worker"
type TemplateUiCommand
} from "@kksh/api/ui/template"
import { db } from "@kksh/drizzle"
import { Button, Form } from "@kksh/svelte5"
import { LoadingBar } from "@kksh/ui"
import { Templates } from "@kksh/ui/extension"
import { GlobalCommandPaletteFooter } from "@kksh/ui/main"
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
import type { UnlistenFn } from "@tauri-apps/api/event"
import {
RECORD_EXTENSION_PROCESS_EVENT,
type IRecordExtensionProcessEvent
} from "@kunkunapi/src/events.js"
import { Channel, invoke } from "@tauri-apps/api/core"
import { emitTo, type UnlistenFn } from "@tauri-apps/api/event"
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { getCurrentWindow } from "@tauri-apps/api/window"
import * as fs from "@tauri-apps/plugin-fs"
import { readTextFile } from "@tauri-apps/plugin-fs"
import { debug } from "@tauri-apps/plugin-log"
import { debug, info } from "@tauri-apps/plugin-log"
import { platform } from "@tauri-apps/plugin-os"
import { goto } from "$app/navigation"
import { RPCChannel, WorkerParentIO } from "kkrpc/browser"
import { onDestroy, onMount, tick } from "svelte"
import Inspect from "svelte-inspect-value"
import { type CommandEvent } from "tauri-plugin-shellx-api"
import * as v from "valibot"
const { data } = $props()
let listviewInputRef = $state<HTMLInputElement | null>(null)
let { loadedExt, scriptPath, extInfoInDB } = $derived(data)
let actionPanelOpen = $state(false)
let workerAPI: WorkerExtension | undefined = undefined
let workerAPI: TemplateUiCommand | undefined = undefined
let unlistenRefreshWorkerExt: UnlistenFn | undefined
let unlistenFileDrop: UnlistenFn | undefined
let worker: Worker | undefined
let listViewContent = $state<ListSchema.List>()
let formViewContent = $state<FormSchema.Form>()
let markdownViewContent = $state<MarkdownSchema>()
let listViewContent = $state<ListSchema.List | null>(null)
let formViewContent = $state<FormSchema.Form | null>(null)
let markdownViewContent = $state<MarkdownSchema | null>(null)
let extensionLoadingBar = $state(false) // whether extension called showLoadingBar
let pbar = $state<number | null>(null)
let loading = $state(false)
let searchTerm = $state("")
let searchBarPlaceholder = $state("")
let extSpawnedProcesses = $state<number[]>([])
const appWin = getCurrentWebviewWindow()
const loadingBar = $derived($appState.loadingBar || extensionLoadingBar)
let loaded = $state(false)
let listview: Templates.ListView | undefined = $state(undefined)
const _platform = platform()
let unlistenPkgJsonWatch: UnlistenFn | undefined
let curViewNodeName = $state<NodeNameEnum | FormNodeNameEnum | null>(null)
async function goBack() {
if (isInMainWindow()) {
goto(i18n.resolveRoute("/app/"))
@ -61,23 +85,28 @@
}
}
function clearViewContent(keep?: "list" | "form" | "markdown") {
async function clearViewContent(keep?: "list" | "form" | "markdown") {
if (keep !== "list") {
listViewContent = undefined
listViewContent = null
}
if (keep !== "form") {
formViewContent = undefined
formViewContent = null
}
if (keep !== "markdown") {
markdownViewContent = undefined
markdownViewContent = null
}
await tick()
// await sleep(3000)
}
const extUiAPI: IUiWorker = {
async render(view: IComponent<ListSchema.List | FormSchema.Form | MarkdownSchema>) {
if (view.nodeName === NodeNameEnum.List) {
clearViewContent("list")
const parsedListViewRes = v.safeParse(ListSchema.List, view)
const extUiAPI: IUiTemplate = {
async render(_view: IComponent<ListSchema.List | FormSchema.Form | MarkdownSchema>) {
// console.log("render nodeName", _view.nodeName)
// console.log("render", _view)
curViewNodeName = _view.nodeName
if (_view.nodeName === NodeNameEnum.List) {
await clearViewContent("list")
const parsedListViewRes = v.safeParse(ListSchema.List, _view)
if (!parsedListViewRes.success) {
toast.error("Invalid List View", {
description: "See console for details"
@ -157,20 +186,22 @@
// } else {
// listViewContent = parsedListView
// }
} else if (view.nodeName === FormNodeNameEnum.Form) {
listViewContent = undefined
clearViewContent("form")
const parsedForm = v.parse(FormSchema.Form, view)
} else if (_view.nodeName === FormNodeNameEnum.Form) {
listViewContent = null
// await clearViewContent("form")
// await tick()
const parsedForm = v.parse(FormSchema.Form, _view)
formViewContent = parsedForm
// TODO: convert form to zod schema
// const zodSchema = convertFormToZod(parsedForm)
// formViewZodSchema = zodSchema
// formFieldConfig = buildFieldConfig(parsedForm)
} else if (view.nodeName === NodeNameEnum.Markdown) {
clearViewContent("markdown")
markdownViewContent = v.parse(MarkdownSchema, view)
} else if (_view.nodeName === NodeNameEnum.Markdown) {
await clearViewContent("markdown")
await tick()
markdownViewContent = v.parse(MarkdownSchema, _view)
} else {
toast.error(`Unsupported view type: ${view.nodeName}`)
toast.error(`Unsupported view type: ${_view.nodeName}`)
}
},
async showLoadingBar(loading: boolean) {
@ -187,7 +218,6 @@
searchTerm = term
},
async setSearchBarPlaceholder(placeholder: string) {
console.log("setSearchBarPlaceholder", placeholder)
searchBarPlaceholder = placeholder
},
async goBack() {
@ -209,22 +239,47 @@
worker = new Worker(blobURL)
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
loadedExt.kunkun.permissions,
loadedExt.extPath
loadedExt.extPath,
{
recordSpawnedProcess: async (pid: number) => {
extSpawnedProcesses = [...extSpawnedProcesses, pid]
// winExtMap.registerProcess(appWin.label, pid)
const curWin = await getCurrentWindow()
await emitTo("main", RECORD_EXTENSION_PROCESS_EVENT, {
windowLabel: curWin.label,
pid
} satisfies IRecordExtensionProcessEvent)
// TODO: record process in a store
},
getSpawnedProcesses: () => Promise.resolve(extSpawnedProcesses),
paste: async () => {
await appWin.hide()
await sleep(200)
return paste()
}
}
)
const serverAPI2 = {
...serverAPI,
iframeUi: undefined,
workerUi: extUiAPI,
helper: helperAPI,
db: new db.JarvisExtDB(extInfoInDB.extId),
kv: new db.KV(extInfoInDB.extId),
app: {
language: () => Promise.resolve("en")
} satisfies IApp
}
const io = new WorkerParentIO(worker)
const rpc = new RPCChannel<typeof serverAPI2, WorkerExtension>(io, {
expose: serverAPI2
const kkrpcSerialization = decideKkrpcSerialization(loadedExt)
info(
`Establishing kkrpc connection for ${loadedExt.kunkun.identifier} with serialization: ${kkrpcSerialization}`
)
const rpc = new RPCChannel<typeof serverAPI2, TemplateUiCommand>(io, {
expose: serverAPI2,
serialization: {
version: kkrpcSerialization
}
})
workerAPI = rpc.getAPI()
await workerAPI.load()
@ -236,10 +291,26 @@
worker?.terminate()
}
})
// function onPkgJsonChange(evt: fs.WatchEvent) {
// const parsed = v.safeParse(WatchEvent, evt)
// if (parsed.success) {
// if (
// parsed.output.type.modify.kind === "data" &&
// parsed.output.type.modify.mode === "content" &&
// parsed.output.paths.includes(data.pkgJsonPath)
// ) {
// console.log("pkgJson changed", parsed.output.paths)
// // emit event to reload extension commands
// emitReloadOneExtension(loadedExt.extPath)
// }
// }
// }
onMount(async () => {
setTimeout(() => {
appState.setLoadingBar(true)
appWin.show()
appWin.show().then(() => appWin.setFocus())
}, 100)
unlistenRefreshWorkerExt = await listenToRefreshDevExt(() => {
debug("Refreshing Worker Extension")
@ -255,14 +326,20 @@
appState.setLoadingBar(false)
loaded = true
}, 500)
// fs.watch(data.pkgJsonPath, onPkgJsonChange).then((unlisten) => {
// unlistenPkgJsonWatch = unlisten
// })
})
onDestroy(() => {
unlistenRefreshWorkerExt?.()
unlistenFileDrop?.()
unlistenPkgJsonWatch?.()
winExtMap.unregisterExtensionFromWindow(appWin.label)
extensionLoadingBar = false
appState.setActionPanel(undefined)
appState.setDefaultAction(null)
appState.setActionPanel(undefined)
})
$effect(() => {
@ -273,8 +350,13 @@
keySet.has(_platform === "macos" ? "Meta" : "Control") &&
keySet.has("k")
) {
console.log("open action panel")
actionPanelOpen = true
// actionPanelOpen = true
setTimeout(() => {
actionPanelOpen = !actionPanelOpen
if (!actionPanelOpen) {
onActionPanelBlur()
}
}, 100)
}
})
@ -286,8 +368,6 @@
function onkeydown(e: KeyboardEvent) {
if (e.key === "Escape") {
console.log(document.activeElement)
console.log(document.activeElement?.nodeName)
if (document.activeElement?.nodeName === "INPUT") {
console.log("input")
}
@ -299,7 +379,8 @@
{#if loadingBar}
<LoadingBar class="fixed left-0 top-0 w-full" color="white" />
{/if}
{#if loaded && listViewContent !== undefined}
{#if curViewNodeName === NodeNameEnum.List && listViewContent}
<Templates.ListView
bind:inputRef={listviewInputRef}
bind:searchTerm
@ -321,26 +402,18 @@
onSearchTermChange={(searchTerm: string) => {
workerAPI?.onSearchTermChange(searchTerm)
}}
onHighlightedItemChanged={(value: string) => {
// workerAPI?.onHighlightedListItemChanged(value)
// if (listViewContent?.defaultAction) {
// appState.setDefaultAction(listViewContent.defaultAction)
// }
// if (listViewContent?.actions) {
// appState.setActionPanel(listViewContent.actions)
// }
try {
const parsedItem = v.parse(ListSchema.Item, JSON.parse(value))
if (parsedItem.defaultAction) {
appState.setDefaultAction(parsedItem.defaultAction)
}
if (parsedItem.actions) {
appState.setActionPanel(parsedItem.actions)
}
workerAPI?.onHighlightedListItemChanged(parsedItem.value)
} catch (error) {
console.error(error)
onHighlightedItemChanged={(item: ListSchema.Item) => {
if (item.defaultAction) {
appState.setDefaultAction(item.defaultAction)
} else if (listViewContent?.defaultAction) {
appState.setDefaultAction(listViewContent.defaultAction)
}
if (item.actions) {
appState.setActionPanel(item.actions)
} else if (listViewContent?.actions) {
appState.setActionPanel(listViewContent.actions)
}
workerAPI?.onHighlightedListItemChanged(item.value)
}}
>
{#snippet footer()}
@ -358,15 +431,18 @@
/>
{/snippet}
</Templates.ListView>
{:else if loaded && formViewContent !== undefined}
{/if}
{#if curViewNodeName === FormNodeNameEnum.Form && formViewContent}
<Templates.FormView
{formViewContent}
{pbar}
onGoBack={goBack}
onSubmit={(formData: Record<string, string | number | boolean>) => {
console.log("formData", formData)
console.log("Submit formData", formData)
workerAPI?.onFormSubmit(formData)
}}
/>
{:else if loaded && markdownViewContent !== undefined}
{/if}
{#if curViewNodeName === NodeNameEnum.Markdown && markdownViewContent}
<Templates.MarkdownView {markdownViewContent} onGoBack={goBack} />
{/if}

Some files were not shown because too many files have changed in this diff Show More