mirror of
https://github.com/kunkunsh/kunkun.git
synced 2025-04-13 10:14:37 +00:00
Compare commits
100 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3542eec277 | ||
![]() |
de3886d416 | ||
![]() |
bb9a46935c | ||
![]() |
bf51fdadbc | ||
![]() |
9cf06b1835 | ||
![]() |
48e2e47f96 | ||
![]() |
9fe51f6260 | ||
![]() |
7759e615dd | ||
![]() |
11226ee2ef | ||
![]() |
c39e98258c | ||
![]() |
d27731d0e6 | ||
![]() |
0bca6739a7 | ||
![]() |
993e276e72 | ||
![]() |
310969e597 | ||
![]() |
cd7301255b | ||
![]() |
b4afcaac6c | ||
![]() |
234f245a9c | ||
![]() |
cc7cea7fe9 | ||
![]() |
90ba943fb6 | ||
![]() |
6ffc6f1543 | ||
![]() |
2cbe45f6d1 | ||
![]() |
a42d4d97eb | ||
![]() |
5fc99ca26c | ||
![]() |
41302a29ff | ||
![]() |
8751fbeff4 | ||
![]() |
6df1c9865a | ||
![]() |
f09b2832e9 | ||
![]() |
6555ebcfcb | ||
![]() |
9e52ea331e | ||
![]() |
70f7d4131e | ||
![]() |
97cd20906f | ||
![]() |
a92c266d32 | ||
![]() |
66135624b9 | ||
![]() |
8940d25245 | ||
![]() |
8d49f50495 | ||
![]() |
441abbcdae | ||
![]() |
330678cb45 | ||
![]() |
52919b8d2f | ||
![]() |
ed20f9a142 | ||
![]() |
b5ea128aca | ||
![]() |
872b601338 | ||
![]() |
a0bd2d8573 | ||
![]() |
ec951bfc80 | ||
![]() |
4d90e2cf29 | ||
![]() |
8a9f6bcb09 | ||
![]() |
07c62e236c | ||
![]() |
ac6e2c3f78 | ||
![]() |
b986121708 | ||
![]() |
369a9719fd | ||
![]() |
9cfb59e7e4 | ||
![]() |
d3215d386d | ||
![]() |
0bd65db3e5 | ||
![]() |
dfd84db783 | ||
![]() |
60f442dafb | ||
![]() |
742cf3af09 | ||
![]() |
ba36b6226a | ||
![]() |
eeeeaf1822 | ||
![]() |
71b88e0a22 | ||
![]() |
513cf16d72 | ||
![]() |
41f864e996 | ||
![]() |
b119c4e8aa | ||
![]() |
bbee92fa9f | ||
![]() |
162a8dd685 | ||
![]() |
b866967dda | ||
![]() |
a359c8b739 | ||
![]() |
b5848ecea5 | ||
![]() |
63403b6118 | ||
![]() |
03450e8a12 | ||
![]() |
e36237facd | ||
![]() |
4221b574c9 | ||
![]() |
f1cace38d4 | ||
![]() |
c8112b43bc | ||
![]() |
e9dfaf519e | ||
![]() |
474b0f59b3 | ||
![]() |
6bd7d71df6 | ||
![]() |
b28573713a | ||
![]() |
bdf99ee196 | ||
![]() |
183af3fb84 | ||
![]() |
5573923a76 | ||
![]() |
0eacf01de2 | ||
![]() |
839bad6751 | ||
![]() |
27fdff03d9 | ||
![]() |
7b6c0934ab | ||
![]() |
490368428e | ||
![]() |
e49c0f5da5 | ||
![]() |
f37605f9a2 | ||
![]() |
872bcfdfd1 | ||
![]() |
f895594b62 | ||
![]() |
46d6872614 | ||
![]() |
b4b7851366 | ||
![]() |
4862484857 | ||
![]() |
4626acf534 | ||
![]() |
2fd119f842 | ||
![]() |
892c31ae31 | ||
![]() |
e260264797 | ||
![]() |
798cc773e5 | ||
![]() |
33e4451be2 | ||
![]() |
0bb59e4f66 | ||
![]() |
c93ebd895e | ||
![]() |
63bc401d8e |
@ -11,11 +11,9 @@
|
|||||||
"jarvis",
|
"jarvis",
|
||||||
"form-view",
|
"form-view",
|
||||||
"@kksh/desktop",
|
"@kksh/desktop",
|
||||||
"@kksh/supabase",
|
|
||||||
"@kksh/utils",
|
"@kksh/utils",
|
||||||
"@kksh/extension",
|
"@kksh/extension",
|
||||||
"@kksh/schema",
|
"@kksh/schema",
|
||||||
"@kksh/supabase",
|
|
||||||
"@kksh/ui"
|
"@kksh/ui"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -41,3 +41,10 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I am willing to submit a PR to fix this issue
|
- 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
|
||||||
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -2,7 +2,7 @@ blank_issues_enabled: false
|
|||||||
contact_links:
|
contact_links:
|
||||||
- name: GitHub Discussions
|
- name: GitHub Discussions
|
||||||
url: https://github.com/kunkunsh/kunkun/discussions
|
url: https://github.com/kunkunsh/kunkun/discussions
|
||||||
about: Please ask and answer questions here.
|
about: Discussions and questions here
|
||||||
- name: 💬 Discord
|
- name: 💬 Discord
|
||||||
url: https://discord.gg/7dzw3TYeTU
|
url: https://discord.gg/7dzw3TYeTU
|
||||||
about: Please ask and answer questions here.
|
about: Please ask and answer questions here
|
||||||
|
7
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
7
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -41,4 +41,9 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I am willing to submit a PR to implement this feature
|
- 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
|
||||||
|
24
.github/workflows/beta-build.yml
vendored
24
.github/workflows/beta-build.yml
vendored
@ -1,20 +1,23 @@
|
|||||||
name: Build Beta Package
|
name: Build Beta
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "22 22 * * *"
|
- cron: "22 22 * * *"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
updater:
|
updater:
|
||||||
description: "Enable updater?"
|
description: "Enable updater?"
|
||||||
required: true
|
required: true
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: false
|
||||||
platform_windows:
|
platform_windows:
|
||||||
description: "windows"
|
description: "windows"
|
||||||
required: true
|
required: true
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: false
|
||||||
platform_linux:
|
platform_linux:
|
||||||
description: "linux"
|
description: "linux"
|
||||||
required: true
|
required: true
|
||||||
@ -90,8 +93,8 @@ jobs:
|
|||||||
id: setting
|
id: setting
|
||||||
run: |
|
run: |
|
||||||
matrix=""
|
matrix=""
|
||||||
if [ "${{ github.event_name }}" == "schedule" ]; then
|
if [ "${{ github.event_name }}" == "schedule" ] || [ "${{ github.event_name }}" == "pull_request" ]; then
|
||||||
matrix="\"windows-latest\",\"ubuntu-22.04\",\"macos-14\",\"macos-12\""
|
matrix="\"windows-latest\",\"ubuntu-22.04\",\"macos-14\",\"macos-13\""
|
||||||
build_mode=""
|
build_mode=""
|
||||||
build_path="release"
|
build_path="release"
|
||||||
retention_days='1'
|
retention_days='1'
|
||||||
@ -106,7 +109,7 @@ jobs:
|
|||||||
matrix="${matrix}\"macos-14\","
|
matrix="${matrix}\"macos-14\","
|
||||||
fi
|
fi
|
||||||
if [ "${{ inputs.platform_macos_x86_64 }}" == "true" ]; then
|
if [ "${{ inputs.platform_macos_x86_64 }}" == "true" ]; then
|
||||||
matrix="${matrix}\"macos-12\","
|
matrix="${matrix}\"macos-13\","
|
||||||
fi
|
fi
|
||||||
if [ -z "${matrix}" ]; then
|
if [ -z "${matrix}" ]; then
|
||||||
matrix="\"windows-latest\","
|
matrix="\"windows-latest\","
|
||||||
@ -137,8 +140,6 @@ jobs:
|
|||||||
RETENTION_DAYS: ${{ needs.preprocess.outputs.retention_days }}
|
RETENTION_DAYS: ${{ needs.preprocess.outputs.retention_days }}
|
||||||
FILE_PREFIX: ${{ needs.preprocess.outputs.file_prefix }}
|
FILE_PREFIX: ${{ needs.preprocess.outputs.file_prefix }}
|
||||||
# BUILD_TIME: ${{ needs.preprocess.outputs.build_time }}
|
# 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
|
NO_STRIP: true
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@ -204,12 +205,15 @@ jobs:
|
|||||||
run: pnpm prepare
|
run: pnpm prepare
|
||||||
|
|
||||||
- name: Build Packages
|
- name: Build Packages
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Build the app (Windows)
|
- name: Build the App
|
||||||
working-directory: apps/desktop
|
working-directory: apps/desktop
|
||||||
env:
|
env:
|
||||||
CI: false
|
CI: false
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
run: pnpm tauri build ${{ env.BUILD_MODE}} ${{ matrix.os == 'windows-latest' && '-b nsis' || '' }}
|
run: pnpm tauri build ${{ env.BUILD_MODE}} ${{ matrix.os == 'windows-latest' && '-b nsis' || '' }}
|
||||||
|
|
||||||
- name: Rename macos-aarch64
|
- 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
|
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
|
- 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
|
run: mv target/${{ env.BUILD_PATH }}/bundle/dmg/*.dmg target/${{ env.BUILD_PATH }}/bundle/dmg/${{ env.FILE_PREFIX }}-amd64.dmg
|
||||||
|
|
||||||
- name: Rename windows
|
- name: Rename windows
|
||||||
|
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -5,6 +5,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- develop
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-test:
|
build-test:
|
||||||
@ -51,6 +52,8 @@ jobs:
|
|||||||
- name: Setup
|
- name: Setup
|
||||||
run: pnpm prepare
|
run: pnpm prepare
|
||||||
- name: Build
|
- name: Build
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
- name: JS Test
|
- name: JS Test
|
||||||
if: matrix.os == 'ubuntu-24.04'
|
if: matrix.os == 'ubuntu-24.04'
|
||||||
|
14
.github/workflows/desktop-publish.yml
vendored
14
.github/workflows/desktop-publish.yml
vendored
@ -15,16 +15,16 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
settings:
|
settings:
|
||||||
- platform: "macos-14" # for Arm based macs (M1 and above).
|
- 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.
|
- 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.
|
# Universal Build no longer supported after adding openssl, which is not cross-compilable.
|
||||||
- platform: "macos-14" # for Both Arm and Intel based macs.
|
- 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.
|
- 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"
|
- platform: "windows-latest"
|
||||||
args: "--verbose"
|
args: "--verbose --config src-tauri/tauri.conf.publish.json"
|
||||||
|
|
||||||
runs-on: ${{ matrix.settings.platform }}
|
runs-on: ${{ matrix.settings.platform }}
|
||||||
steps:
|
steps:
|
||||||
@ -87,6 +87,8 @@ jobs:
|
|||||||
# pnpm --filter=@kksh/ci run ci-env-check
|
# pnpm --filter=@kksh/ci run ci-env-check
|
||||||
bun packages/ci/scripts/ci-env-check.ts
|
bun packages/ci/scripts/ci-env-check.ts
|
||||||
- name: Build Packages
|
- name: Build Packages
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
- name: Get App Version
|
- name: Get App Version
|
||||||
if: matrix.settings.platform == 'windows-latest'
|
if: matrix.settings.platform == 'windows-latest'
|
||||||
@ -96,7 +98,9 @@ jobs:
|
|||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
CI: false
|
CI: false
|
||||||
|
KUNKUN_PUBLISH: true
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
|
2
.github/workflows/jsr-publish.yml
vendored
2
.github/workflows/jsr-publish.yml
vendored
@ -1,5 +1,7 @@
|
|||||||
name: JSR Publish
|
name: JSR Publish
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches: [develop, main]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
39
.github/workflows/npm-publish.yml
vendored
Normal file
39
.github/workflows/npm-publish.yml
vendored
Normal 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
121
.github/workflows/test-build.yml
vendored
Normal 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"
|
@ -1,4 +1,5 @@
|
|||||||
.svelte-kit/
|
.svelte-kit/
|
||||||
target/
|
target/
|
||||||
|
vendors/**
|
||||||
vendors
|
vendors
|
||||||
.nuxt/
|
.nuxt/
|
||||||
|
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@ -10,5 +10,10 @@
|
|||||||
"titleBar.activeForeground": "#FFFBFC"
|
"titleBar.activeForeground": "#FFFBFC"
|
||||||
},
|
},
|
||||||
"svelte.enable-ts-plugin": true,
|
"svelte.enable-ts-plugin": true,
|
||||||
"deno.enable": false
|
"deno.enable": false,
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/*.code-search": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,15 @@ If you are interested in contributing to the project, please read the following
|
|||||||
- [cmake](https://cmake.org/)
|
- [cmake](https://cmake.org/)
|
||||||
- MacOS: `brew install cmake`
|
- MacOS: `brew install cmake`
|
||||||
- Linux: `sudo apt install -y 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
|
### Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/kunkunsh/kunkun.git --recursive
|
git clone https://github.com/kunkunsh/kunkun.git --recursive
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm prepare
|
pnpm build # build submodules
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run Desktop App
|
### Run Desktop App
|
||||||
@ -44,6 +46,15 @@ cd apps/desktop
|
|||||||
pnpm tauri dev
|
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
|
## i188n
|
||||||
|
|
||||||
If you are willing to help with the translation, please use translations in json files in `apps/desktop/messages`.
|
If you are willing to help with the translation, please use translations in json files in `apps/desktop/messages`.
|
||||||
|
1078
Cargo.lock
generated
1078
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,8 @@ tokio-util = "0.7.12"
|
|||||||
mdns-sd = "0.11.1"
|
mdns-sd = "0.11.1"
|
||||||
tauri-plugin-network = { path = "./vendors/tauri-plugin-network" }
|
tauri-plugin-network = { path = "./vendors/tauri-plugin-network" }
|
||||||
tauri-plugin-keyring = { path = "./vendors/tauri-plugin-keyring" }
|
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" }
|
mac-security-rs = { path = "./packages/mac-security-rs" }
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
strum = "0.26"
|
strum = "0.26"
|
||||||
|
120
README.md
120
README.md
@ -1,36 +1,75 @@
|
|||||||
# Kunkun
|

|
||||||
|
|
||||||
> Kunkun is a cross-platform extensible app launcher like Raycast or Alfred.
|
> [!WARNING]
|
||||||
> All extensions run in a sandboxed environment by default to ensure security.
|
> 🚧 Work in Progress 🚧
|
||||||
|
> This project is still in its early stages.
|
||||||
## Demo Video and Instructions
|
>
|
||||||
|
> We know it’s not perfect yet. The author is pouring heart, soul, and a few sleepless nights into fixing the issues. Your patience means everything.
|
||||||
- https://docs.kunkun.sh/guides/demo/
|
>
|
||||||
- Download extension from https://kunkun.sh/download
|
> Got feedback or found a bug? Open an issue—it helps more than you know.
|
||||||
|
|
||||||

|

|
||||||
[![YouTube badge][]][YouTube link]
|
[![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; ">
|
[](https://discord.gg/7dzw3TYeTU)
|
||||||
<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/
|
|
||||||
|
|
||||||
[YouTube badge]: https://img.shields.io/youtube/channel/subscribers/UC1gJeFbvRcQXDC_C8nKetdA?style=social
|
[YouTube badge]: https://img.shields.io/youtube/channel/subscribers/UC1gJeFbvRcQXDC_C8nKetdA?style=social
|
||||||
[YouTube link]: https://www.youtube.com/@huakun
|
[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/
|
<table>
|
||||||
- From GitHub Releases: https://github.com/kunkunsh/kunkun/releases
|
<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
|
<a href="https://star-history.com/#kunkunsh/kunkun&Date">
|
||||||
- [x] Linux
|
<picture>
|
||||||
- [x] Windows
|
<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
|
## Sample Extensions
|
||||||
|
|
||||||
@ -110,3 +149,40 @@
|
|||||||
##### Clipboard History
|
##### Clipboard History
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Stats
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<!-- 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/) -->
|
||||||
|
@ -1,5 +1,40 @@
|
|||||||
# kksh
|
# 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
|
## 0.0.30
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
@ -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 })
|
|
||||||
})
|
|
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { buildCmd, verifyCmd } from "@/commands"
|
import { verifyCmd } from "@/commands"
|
||||||
import { getDockerFolder, NODE_ENV } from "@/constants"
|
import { NODE_ENV } from "@/constants"
|
||||||
import logger from "@/logger"
|
import logger from "@/logger"
|
||||||
import { program } from "commander"
|
import { program } from "commander"
|
||||||
import { version } from "./package.json"
|
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()
|
program.parse()
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
export { buildWithDocker, buildWithDockerAndValidate } from "@/utils"
|
|
||||||
export type { BuildResult } from "@/types"
|
|
||||||
export {
|
export {
|
||||||
verifyCustomUiCommand,
|
verifyCustomUiCommand,
|
||||||
verifyTemplateUiCommand,
|
verifyTemplateUiCommand,
|
||||||
verifySingleProject,
|
verifySingleProject,
|
||||||
verifyCmd
|
verifyCmd
|
||||||
} from "@/commands/verify"
|
} from "@/commands/verify"
|
||||||
export { buildCmd } from "@/commands/build"
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kksh",
|
"name": "kksh",
|
||||||
"module": "dist/cli.js",
|
"module": "dist/cli.js",
|
||||||
"version": "0.0.30",
|
"version": "0.1.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"kksh": "./dist/cli.js",
|
"kksh": "./dist/cli.js",
|
||||||
@ -31,7 +31,7 @@
|
|||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"inquirer": "^10.1.2",
|
"inquirer": "^10.1.2",
|
||||||
"valibot": "^1.0.0-beta.10"
|
"valibot": "^1.0.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
@ -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
|
|
@ -1,2 +1 @@
|
|||||||
export { default as verifyCmd } from "./verify"
|
export { default as verifyCmd } from "./verify"
|
||||||
export { default as buildCmd } from "./build"
|
|
||||||
|
@ -52,7 +52,8 @@ export function verifySingleProject(projectPath: string): boolean {
|
|||||||
logger.info(`name`, pkg.name)
|
logger.info(`name`, pkg.name)
|
||||||
logger.info(`version`, pkg.version)
|
logger.info(`version`, pkg.version)
|
||||||
logger.info(`identifier`, pkg.kunkun.identifier)
|
logger.info(`identifier`, pkg.kunkun.identifier)
|
||||||
if (pkg.files.length === 0) {
|
|
||||||
|
if ((pkg.files?.length ?? 0) === 0) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`"files" field is empty, it is recommended to include only the necessary files, e.g. dist`
|
`"files" field is empty, it is recommended to include only the necessary files, e.g. dist`
|
||||||
)
|
)
|
||||||
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,5 +1,40 @@
|
|||||||
# create-kunkun
|
# 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
|
## 0.1.42
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
@ -15,7 +15,9 @@ const distDir = path.join(getRootDir(), "dist")
|
|||||||
const indexjsPath = path.join(distDir, "index.mjs")
|
const indexjsPath = path.join(distDir, "index.mjs")
|
||||||
const templateNames = ["template", "react", "vue", "nuxt", "svelte", "sveltekit"]
|
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)
|
fs.mkdirpSync(testDir)
|
||||||
for (const templateName of templateNames) {
|
for (const templateName of templateNames) {
|
||||||
const folderName = `${templateName}-ext`
|
const folderName = `${templateName}-ext`
|
||||||
|
@ -94,7 +94,7 @@ async function copyTemplate(templateTgz: string, targetFolderName: string): Prom
|
|||||||
message: "Select an Extension Template",
|
message: "Select an Extension Template",
|
||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
name: "Preset Template (Web Worker)",
|
name: "Template UI (Web Worker)",
|
||||||
value: "template",
|
value: "template",
|
||||||
description:
|
description:
|
||||||
"Write regular logic in TypeScript in OOP manner to render extension UI based on predefined template."
|
"Write regular logic in TypeScript in OOP manner to render extension UI based on predefined template."
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "create-kunkun",
|
"name": "create-kunkun",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.1.42",
|
"version": "0.1.49",
|
||||||
"bin": {
|
"bin": {
|
||||||
"create-kunkun": "dist/index.mjs"
|
"create-kunkun": "dist/index.mjs"
|
||||||
},
|
},
|
||||||
@ -15,7 +15,7 @@
|
|||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"get-folder-size": "^5.0.0",
|
"get-folder-size": "^5.0.0",
|
||||||
"tar": "^7.4.3",
|
"tar": "^7.4.3",
|
||||||
"vitest": "^2.0.0"
|
"vitest": "^2.1.9"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
@ -27,7 +27,7 @@
|
|||||||
"commander": "^12.1.0",
|
"commander": "^12.1.0",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"valibot": "^1.0.0-beta.10"
|
"valibot": "^1.0.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
13
apps/desktop/dev.ts
Normal file
13
apps/desktop/dev.ts
Normal 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)
|
91
apps/desktop/messages/de.json
Normal file
91
apps/desktop/messages/de.json
Normal 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"
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
"home_command_input_dropdown_close_window": "Close Window",
|
"home_command_input_dropdown_close_window": "Close Window",
|
||||||
"home_command_input_dropdown_toggle_devtools": "Toggle Devtools",
|
"home_command_input_dropdown_toggle_devtools": "Toggle Devtools",
|
||||||
"home_command_input_dropdown_reload_window": "Reload Window",
|
"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",
|
"home_command_input_dropdown_toggle_dev_extension_hmr": "Toggle Dev Extension HMR",
|
||||||
|
|
||||||
"command_group_heading_dev_ext": "Dev Extensions",
|
"command_group_heading_dev_ext": "Dev Extensions",
|
||||||
@ -23,6 +24,7 @@
|
|||||||
|
|
||||||
"settings_menu_settings": "Settings",
|
"settings_menu_settings": "Settings",
|
||||||
"settings_menu_general": "General",
|
"settings_menu_general": "General",
|
||||||
|
"settings_menu_app_search_paths": "App Search Paths",
|
||||||
"settings_menu_developer": "Developer",
|
"settings_menu_developer": "Developer",
|
||||||
"settings_menu_extensions": "Extensions",
|
"settings_menu_extensions": "Extensions",
|
||||||
"settings_menu_set_dev_ext": "Set Dev Extension",
|
"settings_menu_set_dev_ext": "Set Dev Extension",
|
||||||
@ -38,6 +40,13 @@
|
|||||||
"settings_general_join_beta_updates": "Join Beta Updates",
|
"settings_general_join_beta_updates": "Join Beta Updates",
|
||||||
"settings_general_developer_mode": "Developer Mode",
|
"settings_general_developer_mode": "Developer Mode",
|
||||||
"settings_general_language": "Language",
|
"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_version": "Version",
|
||||||
"settings_about_author": "Author",
|
"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_folders": "Install from Extension Folders",
|
||||||
"settings_add_dev_ext_install_from_ext_files": "Install from Extension Tarball File",
|
"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": "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_drag_and_drop2": "Extension Folder or Tarball",
|
||||||
"settings_add_dev_ext_install_tarball_from_url": "Install Tarball From URL",
|
"settings_add_dev_ext_install_tarball_from_url": "Install Tarball From URL",
|
||||||
|
|
||||||
|
84
apps/desktop/messages/pt.json
Normal file
84
apps/desktop/messages/pt.json
Normal 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"
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
"home_command_input_dropdown_close_window": "Закрыть окно",
|
"home_command_input_dropdown_close_window": "Закрыть окно",
|
||||||
"home_command_input_dropdown_toggle_devtools": "Вкл/выкл инструменты разработчика",
|
"home_command_input_dropdown_toggle_devtools": "Вкл/выкл инструменты разработчика",
|
||||||
"home_command_input_dropdown_reload_window": "Перезагрузить окно",
|
"home_command_input_dropdown_reload_window": "Перезагрузить окно",
|
||||||
|
"home_command_input_dropdown_open_preference": "Открыть настройки",
|
||||||
"home_command_input_dropdown_toggle_dev_extension_hmr": "Вкл/выкл горячую замену (HMR) у dev-расширений",
|
"home_command_input_dropdown_toggle_dev_extension_hmr": "Вкл/выкл горячую замену (HMR) у dev-расширений",
|
||||||
|
|
||||||
"command_group_heading_dev_ext": "Dev-расширения",
|
"command_group_heading_dev_ext": "Dev-расширения",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"settings_general_join_beta_updates": "Получать бета-обновления",
|
"settings_general_join_beta_updates": "Получать бета-обновления",
|
||||||
"settings_general_developer_mode": "Режим разработчика",
|
"settings_general_developer_mode": "Режим разработчика",
|
||||||
"settings_general_language": "Язык",
|
"settings_general_language": "Язык",
|
||||||
|
"settings_general_loading_animation": "Анимация загрузки",
|
||||||
|
|
||||||
"settings_about_version": "Версия",
|
"settings_about_version": "Версия",
|
||||||
"settings_about_author": "Автор",
|
"settings_about_author": "Автор",
|
||||||
@ -60,8 +62,10 @@
|
|||||||
"settings_add_dev_ext_description": "В режиме разработчика у вас есть четыре способа установки dev-расширений: из локального tar-архива, из локальной директории, по URL-адресу на tar-архив либо по названию пакета npm.",
|
"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_folders": "Установить из директори",
|
||||||
"settings_add_dev_ext_install_from_ext_files": "Установить из tar-архива",
|
"settings_add_dev_ext_install_from_ext_files": "Установить из tar-архива",
|
||||||
"settings_add_dev_ext_drag_and_drop": "Локальные расширения",
|
"settings_add_dev_ext_drag_and_drop_strike": "Локальные расширения",
|
||||||
"settings_add_dev_ext_drag_and_drop2": "в виде директории или tar-архива могут быть перемещены сюда",
|
"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-адресу",
|
"settings_add_dev_ext_install_tarball_from_url": "Установить tar-архив по URL-адресу",
|
||||||
|
|
||||||
"troubleshooters_sidebar_title": "Диагностика",
|
"troubleshooters_sidebar_title": "Диагностика",
|
||||||
|
84
apps/desktop/messages/vi.json
Normal file
84
apps/desktop/messages/vi.json
Normal 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"
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
"home_command_input_dropdown_close_window": "关闭窗口",
|
"home_command_input_dropdown_close_window": "关闭窗口",
|
||||||
"home_command_input_dropdown_toggle_devtools": "切换开发者工具",
|
"home_command_input_dropdown_toggle_devtools": "切换开发者工具",
|
||||||
"home_command_input_dropdown_reload_window": "重新加载窗口",
|
"home_command_input_dropdown_reload_window": "重新加载窗口",
|
||||||
|
"home_command_input_dropdown_open_preference": "打开设置",
|
||||||
"home_command_input_dropdown_toggle_dev_extension_hmr": "切换开发插件 HMR",
|
"home_command_input_dropdown_toggle_dev_extension_hmr": "切换开发插件 HMR",
|
||||||
|
|
||||||
"command_group_heading_dev_ext": "开发插件",
|
"command_group_heading_dev_ext": "开发插件",
|
||||||
@ -23,6 +24,7 @@
|
|||||||
|
|
||||||
"settings_menu_settings": "设置",
|
"settings_menu_settings": "设置",
|
||||||
"settings_menu_general": "通用",
|
"settings_menu_general": "通用",
|
||||||
|
"settings_menu_app_search_paths": "应用搜索路径",
|
||||||
"settings_menu_developer": "开发者",
|
"settings_menu_developer": "开发者",
|
||||||
"settings_menu_extensions": "插件",
|
"settings_menu_extensions": "插件",
|
||||||
"settings_menu_set_dev_ext": "设置开发插件",
|
"settings_menu_set_dev_ext": "设置开发插件",
|
||||||
@ -38,6 +40,13 @@
|
|||||||
"settings_general_join_beta_updates": "加入 Beta 更新",
|
"settings_general_join_beta_updates": "加入 Beta 更新",
|
||||||
"settings_general_developer_mode": "开发者模式",
|
"settings_general_developer_mode": "开发者模式",
|
||||||
"settings_general_language": "语言",
|
"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_version": "版本",
|
||||||
"settings_about_author": "作者",
|
"settings_about_author": "作者",
|
||||||
@ -61,6 +70,7 @@
|
|||||||
"settings_add_dev_ext_install_from_ext_folders": "从插件文件夹安装",
|
"settings_add_dev_ext_install_from_ext_folders": "从插件文件夹安装",
|
||||||
"settings_add_dev_ext_install_from_ext_files": "从插件 tarball 文件安装",
|
"settings_add_dev_ext_install_from_ext_files": "从插件 tarball 文件安装",
|
||||||
"settings_add_dev_ext_drag_and_drop": "拖放",
|
"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_drag_and_drop2": "插件文件夹或 tarball",
|
||||||
"settings_add_dev_ext_install_tarball_from_url": "从 tarball URL 安装",
|
"settings_add_dev_ext_install_tarball_from_url": "从 tarball URL 安装",
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kksh/desktop",
|
"name": "@kksh/desktop",
|
||||||
"version": "0.1.19",
|
"version": "0.1.37",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -16,61 +16,71 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^0.8.2",
|
"@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/extension": "workspace:*",
|
||||||
"@kksh/supabase": "workspace:*",
|
"@kksh/svelte5": "^0.1.15",
|
||||||
"@kksh/ui": "workspace:*",
|
"@kksh/ui": "workspace:*",
|
||||||
"@kksh/utils": "workspace:*",
|
"@kksh/utils": "workspace:*",
|
||||||
"@std/semver": "npm:@jsr/std__semver@^1.0.3",
|
"@std/semver": "npm:@jsr/std__semver@^1.0.4",
|
||||||
"@supabase/supabase-js": "^2.48.0",
|
"@supabase/supabase-js": "^2.49.1",
|
||||||
"@tanstack/table-core": "^8.20.5",
|
"@tanstack/table-core": "^8.21.2",
|
||||||
"@tauri-apps/api": "^2.1.1",
|
"@tauri-apps/api": "^2.3.0",
|
||||||
|
"@tauri-apps/plugin-autostart": "^2.2.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.2.0",
|
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-sql": "^2.2.0",
|
||||||
"@tauri-apps/plugin-stronghold": "^2.2.0",
|
"@tauri-apps/plugin-stronghold": "^2.2.0",
|
||||||
"dompurify": "^3.2.3",
|
"@tauri-store/svelte": "^2.1.1",
|
||||||
"gsap": "^3.12.5",
|
"dompurify": "^3.2.4",
|
||||||
"kkrpc": "^0.0.13",
|
"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",
|
"lz-string": "^1.5.0",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.7.1",
|
||||||
|
"svelte-inspect-value": "^0.5.0",
|
||||||
"svelte-sonner": "^0.3.28",
|
"svelte-sonner": "^0.3.28",
|
||||||
"sveltekit-superforms": "^2.22.1",
|
"sveltekit-superforms": "^2.23.1",
|
||||||
"tauri-plugin-clipboard-api": "^2.1.11",
|
"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:*",
|
"tauri-plugin-user-input-api": "workspace:*",
|
||||||
"uuid": "^11.0.3"
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^9.21.0",
|
||||||
"@inlang/paraglide-js": "1.11.8",
|
"@inlang/paraglide-js": "1.11.8",
|
||||||
"@kksh/types": "workspace:*",
|
"@kksh/types": "workspace:*",
|
||||||
"@sveltejs/adapter-static": "^3.0.6",
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
"@sveltejs/kit": "^2.12.1",
|
"@sveltejs/kit": "^2.17.3",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tauri-apps/cli": "^2.1.0",
|
"@tauri-apps/cli": "^2.3.1",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
||||||
"@typescript-eslint/parser": "^8.20.0",
|
"@typescript-eslint/parser": "^8.25.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"bits-ui": "1.0.0-next.72",
|
"bits-ui": "1.0.0-next.86",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.46.1",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
"lucide-svelte": "^0.469.0",
|
"lucide-svelte": "^0.474.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.5.2",
|
||||||
"svelte-radix": "^2.0.1",
|
"svelte-radix": "^2.0.1",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwind-variants": "^0.3.0",
|
"tailwind-variants": "^0.3.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.25.0",
|
||||||
"vite": "^6.0.3"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://inlang.com/schema/project-settings",
|
"$schema": "https://inlang.com/schema/project-settings",
|
||||||
"sourceLanguageTag": "en",
|
"sourceLanguageTag": "en",
|
||||||
"languageTags": ["en", "zh", "ru"],
|
"languageTags": ["en", "zh", "ru", "pt", "vi", "de"],
|
||||||
"modules": [
|
"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-empty-pattern@latest/dist/index.js",
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
|
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
|
||||||
|
@ -14,10 +14,10 @@ name = "kunkun_lib"
|
|||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.0.3", features = [] }
|
tauri-build = { version = "2.0.5", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2.1.1", features = [
|
tauri = { version = "2.2.5", features = [
|
||||||
"macos-private-api",
|
"macos-private-api",
|
||||||
"image-png",
|
"image-png",
|
||||||
"image-ico",
|
"image-ico",
|
||||||
@ -34,12 +34,12 @@ chrono = { workspace = true }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
tauri-plugin-process = "2.2.0"
|
tauri-plugin-process = "2.2.0"
|
||||||
tauri-plugin-shellx = "2.0.12"
|
tauri-plugin-shellx = { workspace = true }
|
||||||
tauri-plugin-fs = "2.2.0"
|
tauri-plugin-fs = { version = "2.2.0", features = ["watch"] }
|
||||||
tauri-plugin-dialog = "2.2.0"
|
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-os = "2.2.0"
|
||||||
tauri-plugin-http = "2.2.0"
|
tauri-plugin-http = "2.3.0"
|
||||||
tauri-plugin-upload = { workspace = true }
|
tauri-plugin-upload = { workspace = true }
|
||||||
# tauri-plugin-upload = "2.2.1"
|
# tauri-plugin-upload = "2.2.1"
|
||||||
tauri-plugin-jarvis = { workspace = true }
|
tauri-plugin-jarvis = { workspace = true }
|
||||||
@ -50,23 +50,26 @@ tauri-plugin-user-input = { workspace = true }
|
|||||||
tauri-plugin-clipboard = { workspace = true }
|
tauri-plugin-clipboard = { workspace = true }
|
||||||
tauri-plugin-store = "2.2.0"
|
tauri-plugin-store = "2.2.0"
|
||||||
tauri-plugin-deep-link = "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 }
|
crypto = { workspace = true }
|
||||||
zip = "2.2.2"
|
zip = "2.2.2"
|
||||||
uuid = "1.11.0"
|
uuid = "1.14.0"
|
||||||
# tauri-plugin-devtools = "2.0.0"
|
# tauri-plugin-devtools = "2.0.0"
|
||||||
obfstr = { workspace = true }
|
obfstr = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
tauri-plugin-stronghold = "2.2.0"
|
tauri-plugin-stronghold = "2.2.0"
|
||||||
|
tauri-plugin-sql = "2"
|
||||||
|
|
||||||
|
|
||||||
[target."cfg(target_os = \"macos\")".dependencies]
|
[target."cfg(target_os = \"macos\")".dependencies]
|
||||||
cocoa = "0.24.1"
|
cocoa = "0.24.1"
|
||||||
mac-security-rs = { workspace = true }
|
mac-security-rs = { workspace = true }
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
|
|
||||||
|
|
||||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||||
|
tauri-plugin-autostart = "2"
|
||||||
tauri-plugin-cli = "2"
|
tauri-plugin-cli = "2"
|
||||||
tauri-plugin-global-shortcut = "2.0.1"
|
tauri-plugin-global-shortcut = "2.0.1"
|
||||||
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
||||||
tauri-plugin-updater = "2.0.2"
|
tauri-plugin-updater = "2.0.2"
|
||||||
|
tauri-plugin-svelte = "2.1.1"
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"core:event:default",
|
"core:event:default",
|
||||||
"core:window:default",
|
"core:window:default",
|
||||||
"core:window:allow-set-size",
|
"core:window:allow-set-size",
|
||||||
|
"core:window:allow-set-enabled",
|
||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
"core:window:allow-set-focus",
|
"core:window:allow-set-focus",
|
||||||
"core:window:allow-toggle-maximize",
|
"core:window:allow-toggle-maximize",
|
||||||
@ -39,6 +40,7 @@
|
|||||||
"core:webview:allow-create-webview",
|
"core:webview:allow-create-webview",
|
||||||
"core:webview:allow-create-webview-window",
|
"core:webview:allow-create-webview-window",
|
||||||
"core:app:default",
|
"core:app:default",
|
||||||
|
"core:app:allow-app-hide",
|
||||||
"core:resources:default",
|
"core:resources:default",
|
||||||
"core:menu:default",
|
"core:menu:default",
|
||||||
"core:tray:default",
|
"core:tray:default",
|
||||||
@ -59,6 +61,7 @@
|
|||||||
"shellx:allow-execute",
|
"shellx:allow-execute",
|
||||||
"shellx:allow-open",
|
"shellx:allow-open",
|
||||||
"shellx:allow-kill",
|
"shellx:allow-kill",
|
||||||
|
"shellx:allow-kill-pid",
|
||||||
"shellx:allow-spawn",
|
"shellx:allow-spawn",
|
||||||
"shellx:allow-stdin-write",
|
"shellx:allow-stdin-write",
|
||||||
"shellx:allow-fix-path-env",
|
"shellx:allow-fix-path-env",
|
||||||
@ -111,28 +114,7 @@
|
|||||||
"identifier": "fs:scope",
|
"identifier": "fs:scope",
|
||||||
"allow": [
|
"allow": [
|
||||||
{
|
{
|
||||||
"path": "$DESKTOP"
|
"path": "**/*"
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "$DESKTOP/**"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "$DOWNLOAD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "$DOWNLOAD/**"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "$DOCUMENT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "$DOCUMENT/**"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "$TEMP/**"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "$TEMP"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -164,6 +146,9 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"deep-link:default"
|
"deep-link:default",
|
||||||
|
"autostart:allow-enable",
|
||||||
|
"autostart:allow-disable",
|
||||||
|
"autostart:allow-is-enabled"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
5
apps/desktop/src-tauri/capabilities/svelte.json
Normal file
5
apps/desktop/src-tauri/capabilities/svelte.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"identifier": "svelte",
|
||||||
|
"windows": ["*"],
|
||||||
|
"permissions": ["svelte:default", "core:event:default"]
|
||||||
|
}
|
@ -7,8 +7,10 @@ use log;
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use tauri::ActivationPolicy;
|
use tauri::ActivationPolicy;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
use tauri_plugin_jarvis::{
|
use tauri_plugin_jarvis::{
|
||||||
|
constants::KUNKUN_PUBLISH,
|
||||||
db::JarvisDB,
|
db::JarvisDB,
|
||||||
server::Protocol,
|
server::Protocol,
|
||||||
utils::{
|
utils::{
|
||||||
@ -25,7 +27,7 @@ use utils::server::tauri_file_server;
|
|||||||
pub fn run() {
|
pub fn run() {
|
||||||
let context = tauri::generate_context!();
|
let context = tauri::generate_context!();
|
||||||
let mut builder = tauri::Builder::default();
|
let mut builder = tauri::Builder::default();
|
||||||
|
// let app_data_path = tauri::path::PathResolver::app_data_dir().unwrap();
|
||||||
// let db_key = if cfg!(debug_assertions) {
|
// let db_key = if cfg!(debug_assertions) {
|
||||||
// None
|
// None
|
||||||
// } else {
|
// } else {
|
||||||
@ -63,14 +65,29 @@ pub fn run() {
|
|||||||
// .build(),
|
// .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;
|
let shell_unlocked = true;
|
||||||
builder = builder
|
builder = builder
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
|
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
|
||||||
let _ = app
|
let window = app.get_webview_window("main").expect("no main window");
|
||||||
.get_webview_window("main")
|
|
||||||
.expect("no main window")
|
// if toggle is passed, we want to show/hide the main window
|
||||||
.set_focus();
|
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(
|
.plugin(
|
||||||
tauri_plugin_log::Builder::new()
|
tauri_plugin_log::Builder::new()
|
||||||
@ -91,13 +108,22 @@ pub fn run() {
|
|||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.plugin(tauri_plugin_cli::init())
|
.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_user_input::init())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
|
.plugin(tauri_plugin_svelte::init())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
|
.plugin(tauri_plugin_autostart::init(
|
||||||
|
MacosLauncher::LaunchAgent,
|
||||||
|
Some(vec![]),
|
||||||
|
))
|
||||||
.plugin(tauri_plugin_upload::init())
|
.plugin(tauri_plugin_upload::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
|
||||||
.plugin(tauri_plugin_store::Builder::default().build())
|
.plugin(tauri_plugin_store::Builder::default().build())
|
||||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
@ -111,7 +137,7 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_network::init())
|
.plugin(tauri_plugin_network::init())
|
||||||
.plugin(tauri_plugin_system_info::init())
|
.plugin(tauri_plugin_system_info::init())
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
commands::keyring::get_stronghold_key
|
commands::keyring::get_stronghold_key,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let app = builder
|
let app = builder
|
||||||
@ -123,7 +149,7 @@ pub fn run() {
|
|||||||
})
|
})
|
||||||
.register_uri_scheme_protocol("ext", |app, request| {
|
.register_uri_scheme_protocol("ext", |app, request| {
|
||||||
let app_handle = app.app_handle();
|
let app_handle = app.app_handle();
|
||||||
// app_handle.
|
|
||||||
let win_label = app.webview_label();
|
let win_label = app.webview_label();
|
||||||
let jarvis_state = app_handle.state::<tauri_plugin_jarvis::JarvisState>();
|
let jarvis_state = app_handle.state::<tauri_plugin_jarvis::JarvisState>();
|
||||||
let window_ext_map = jarvis_state.window_label_ext_map.lock().unwrap();
|
let window_ext_map = jarvis_state.window_label_ext_map.lock().unwrap();
|
||||||
@ -210,7 +236,8 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
// setup::deeplink::setup_deeplink(app);
|
// setup::deeplink::setup_deeplink(app);
|
||||||
// #[cfg(all(target_os = "macos", debug_assertions))]
|
// #[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 mut store = StoreBuilder::new("appConfig.bin").build(app.handle().clone());
|
||||||
// let store = app.handle().store_builder("appConfig.json").build()?;
|
// let store = app.handle().store_builder("appConfig.json").build()?;
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"identifier": "sh.kunkun.desktop",
|
"identifier": "sh.kunkun.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:1566",
|
||||||
"beforeBuildCommand": "pnpm build",
|
"beforeBuildCommand": "pnpm build",
|
||||||
"frontendDist": "../build"
|
"frontendDist": "../build"
|
||||||
},
|
},
|
||||||
@ -20,19 +20,24 @@
|
|||||||
"url": "/app",
|
"url": "/app",
|
||||||
"title": "Kunkun",
|
"title": "Kunkun",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
|
"label": "main",
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"height": 600,
|
"height": 600,
|
||||||
"decorations": true
|
"decorations": true,
|
||||||
|
"center": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/splashscreen",
|
"url": "/splashscreen",
|
||||||
"visible": false,
|
"visible": true,
|
||||||
"label": "splashscreen"
|
"label": "splashscreen",
|
||||||
|
"center": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"createUpdaterArtifacts": true,
|
"macOS": {
|
||||||
|
"minimumSystemVersion": "10.15"
|
||||||
|
},
|
||||||
"fileAssociations": [
|
"fileAssociations": [
|
||||||
{
|
{
|
||||||
"ext": ["kunkun"],
|
"ext": ["kunkun"],
|
||||||
@ -55,10 +60,6 @@
|
|||||||
"fs": {
|
"fs": {
|
||||||
"requireLiteralLeadingDot": false
|
"requireLiteralLeadingDot": false
|
||||||
},
|
},
|
||||||
"updater": {
|
|
||||||
"endpoints": ["https://updater.kunkun.sh"],
|
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDc1NENCRjZFM0JBOEQ0ODMKUldTRDFLZzdicjlNZFhHS0ZKYk13WkdZUTFUM01LNjkvVW5Bb2x1SnB1R0crbFRuMnlRSlJ0STgK"
|
|
||||||
},
|
|
||||||
"deep-link": {
|
"deep-link": {
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"schemes": ["kunkun"]
|
"schemes": ["kunkun"]
|
||||||
|
11
apps/desktop/src-tauri/tauri.conf.publish.json
Normal file
11
apps/desktop/src-tauri/tauri.conf.publish.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"bundle": {
|
||||||
|
"createUpdaterArtifacts": true
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"updater": {
|
||||||
|
"endpoints": ["https://updater.kunkun.sh"],
|
||||||
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDc1NENCRjZFM0JBOEQ0ODMKUldTRDFLZzdicjlNZFhHS0ZKYk13WkdZUTFUM01LNjkvVW5Bb2x1SnB1R0crbFRuMnlRSlJ0STgK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,12 +4,14 @@ import { checkUpdateAndInstall } from "@/utils/updater"
|
|||||||
import { setTransparentTitlebar } from "@kksh/api/commands"
|
import { setTransparentTitlebar } from "@kksh/api/commands"
|
||||||
import { IconEnum } from "@kksh/api/models"
|
import { IconEnum } from "@kksh/api/models"
|
||||||
import type { BuiltinCmd } from "@kksh/ui/types"
|
import type { BuiltinCmd } from "@kksh/ui/types"
|
||||||
|
import { commandScore } from "@kksh/ui/utils"
|
||||||
import { getVersion } from "@tauri-apps/api/app"
|
import { getVersion } from "@tauri-apps/api/app"
|
||||||
import { appDataDir } from "@tauri-apps/api/path"
|
import { appDataDir } from "@tauri-apps/api/path"
|
||||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow"
|
import { WebviewWindow } from "@tauri-apps/api/webviewWindow"
|
||||||
import { exit } from "@tauri-apps/plugin-process"
|
import { exit } from "@tauri-apps/plugin-process"
|
||||||
import { dev } from "$app/environment"
|
import { dev } from "$app/environment"
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from "$app/navigation"
|
||||||
|
import Fuse from "fuse.js"
|
||||||
import { toast } from "svelte-sonner"
|
import { toast } from "svelte-sonner"
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import * as clipboard from "tauri-plugin-clipboard-api"
|
import * as clipboard from "tauri-plugin-clipboard-api"
|
||||||
@ -240,6 +242,23 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
|
|||||||
},
|
},
|
||||||
keywords: ["extension", "troubleshooter"]
|
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",
|
name: "Create Quicklink",
|
||||||
icon: {
|
icon: {
|
||||||
@ -409,7 +428,7 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
|
|||||||
visible: false
|
visible: false
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.show()
|
window.show().then(() => window.setFocus())
|
||||||
}, 2_000)
|
}, 2_000)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -474,10 +493,19 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
|
|||||||
}
|
}
|
||||||
].map((cmd) => ({ ...cmd, id: uuidv4() }))
|
].map((cmd) => ({ ...cmd, id: uuidv4() }))
|
||||||
|
|
||||||
export const builtinCmds = derived(appConfig, ($appConfig) => {
|
export const fuse = new Fuse<BuiltinCmd>(rawBuiltinCmds, {
|
||||||
return rawBuiltinCmds.filter((cmd) => {
|
includeScore: true,
|
||||||
const passDeveloper = cmd.flags?.developer ? $appConfig.developerMode : true
|
threshold: 0.2,
|
||||||
const passDev = cmd.flags?.dev ? dev : true
|
keys: ["name"]
|
||||||
return passDeveloper && passDev
|
})
|
||||||
})
|
|
||||||
|
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
|
||||||
})
|
})
|
||||||
|
@ -1,20 +1,41 @@
|
|||||||
import { i18n } from "@/i18n"
|
import { i18n } from "@/i18n"
|
||||||
import { appState } from "@/stores"
|
import { appState } from "@/stores"
|
||||||
import { winExtMap } from "@/stores/winExtMap"
|
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 { trimSlash } from "@/utils/url"
|
||||||
import { constructExtensionSupportDir } from "@kksh/api"
|
import { constructExtensionSupportDir } from "@kksh/api"
|
||||||
import { db, spawnExtensionFileServer } from "@kksh/api/commands"
|
import { spawnExtensionFileServer } from "@kksh/api/commands"
|
||||||
import { HeadlessWorkerExtension } from "@kksh/api/headless"
|
import type { HeadlessCommand } from "@kksh/api/headless"
|
||||||
import { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models"
|
import { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models"
|
||||||
import { constructJarvisServerAPIWithPermissions, type IApp } from "@kksh/api/ui"
|
import { constructJarvisServerAPIWithPermissions, type IApp } from "@kksh/api/ui"
|
||||||
|
import { db } from "@kksh/drizzle"
|
||||||
import { launchNewExtWindow, loadExtensionManifestFromDisk } from "@kksh/extension"
|
import { launchNewExtWindow, loadExtensionManifestFromDisk } from "@kksh/extension"
|
||||||
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
|
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
|
||||||
import { convertFileSrc } from "@tauri-apps/api/core"
|
import { convertFileSrc } from "@tauri-apps/api/core"
|
||||||
import * as path from "@tauri-apps/api/path"
|
import * as path from "@tauri-apps/api/path"
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
import * as fs from "@tauri-apps/plugin-fs"
|
import * as fs from "@tauri-apps/plugin-fs"
|
||||||
|
import { info } from "@tauri-apps/plugin-log"
|
||||||
import { platform } from "@tauri-apps/plugin-os"
|
import { platform } from "@tauri-apps/plugin-os"
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from "$app/navigation"
|
||||||
import { RPCChannel, WorkerParentIO } from "kkrpc/browser"
|
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) {
|
export async function createExtSupportDir(extPath: string) {
|
||||||
const extSupportDir = await constructExtensionSupportDir(extPath)
|
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(
|
export async function onTemplateUiCmdSelect(
|
||||||
ext: ExtPackageJsonExtra,
|
ext: ExtPackageJsonExtra,
|
||||||
cmd: TemplateUiCmd,
|
cmd: TemplateUiCmd,
|
||||||
{ isDev, hmr }: { isDev: boolean; hmr: boolean }
|
{ isDev, hmr }: { isDev: boolean; hmr: boolean }
|
||||||
) {
|
) {
|
||||||
await createExtSupportDir(ext.extPath)
|
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)}`
|
const url = `/app/extension/ui-worker?extPath=${encodeURIComponent(ext.extPath)}&cmdName=${encodeURIComponent(cmd.name)}`
|
||||||
|
setTemplateExtParams(ext.extPath, cmd.name, url)
|
||||||
if (cmd.window) {
|
if (cmd.window) {
|
||||||
const winLabel = await winExtMap.registerExtensionWithWindow({ extPath: ext.extPath })
|
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)
|
const window = launchNewExtWindow(winLabel, url, cmd.window)
|
||||||
window.onCloseRequested(async (event) => {
|
window.onCloseRequested(async (event) => {
|
||||||
await winExtMap.unregisterExtensionFromWindow(winLabel)
|
await winExtMap.unregisterExtensionFromWindow(winLabel)
|
||||||
@ -54,6 +88,7 @@ export async function onHeadlessCmdSelect(
|
|||||||
const loadedExt = await loadExtensionManifestFromDisk(
|
const loadedExt = await loadExtensionManifestFromDisk(
|
||||||
await path.join(ext.extPath, "package.json")
|
await path.join(ext.extPath, "package.json")
|
||||||
)
|
)
|
||||||
|
|
||||||
const scriptPath = await path.join(loadedExt.extPath, cmd.main)
|
const scriptPath = await path.join(loadedExt.extPath, cmd.main)
|
||||||
const workerScript = await fs.readTextFile(scriptPath)
|
const workerScript = await fs.readTextFile(scriptPath)
|
||||||
const blob = new Blob([workerScript], { type: "application/javascript" })
|
const blob = new Blob([workerScript], { type: "application/javascript" })
|
||||||
@ -65,11 +100,26 @@ export async function onHeadlessCmdSelect(
|
|||||||
}
|
}
|
||||||
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
|
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
|
||||||
loadedExt.kunkun.permissions,
|
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 = {
|
const serverAPI2 = {
|
||||||
...serverAPI,
|
...serverAPI,
|
||||||
iframeUi: undefined,
|
iframeUi: undefined,
|
||||||
|
helper: helperAPI,
|
||||||
workerUi: undefined,
|
workerUi: undefined,
|
||||||
db: new db.JarvisExtDB(extInfoInDB.extId),
|
db: new db.JarvisExtDB(extInfoInDB.extId),
|
||||||
kv: new db.KV(extInfoInDB.extId),
|
kv: new db.KV(extInfoInDB.extId),
|
||||||
@ -78,13 +128,27 @@ export async function onHeadlessCmdSelect(
|
|||||||
} satisfies IApp
|
} satisfies IApp
|
||||||
}
|
}
|
||||||
const io = new WorkerParentIO(worker)
|
const io = new WorkerParentIO(worker)
|
||||||
const rpc = new RPCChannel<typeof serverAPI2, HeadlessWorkerExtension>(io, {
|
const kkrpcSerialization = decideKkrpcSerialization(loadedExt)
|
||||||
expose: serverAPI2
|
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()
|
const workerAPI = rpc.getAPI()
|
||||||
await workerAPI.load()
|
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(
|
export async function onCustomUiCmdSelect(
|
||||||
ext: ExtPackageJsonExtra,
|
ext: ExtPackageJsonExtra,
|
||||||
cmd: CustomUiCmd,
|
cmd: CustomUiCmd,
|
||||||
@ -102,6 +166,9 @@ export async function onCustomUiCmdSelect(
|
|||||||
: decodeURIComponent(convertFileSrc(`${trimSlash(cmd.main)}`, "ext"))
|
: decodeURIComponent(convertFileSrc(`${trimSlash(cmd.main)}`, "ext"))
|
||||||
}
|
}
|
||||||
let url2 = `/app/extension/ui-iframe?url=${encodeURIComponent(url)}&extPath=${encodeURIComponent(ext.extPath)}`
|
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) {
|
if (cmd.window) {
|
||||||
const winLabel = await winExtMap.registerExtensionWithWindow({
|
const winLabel = await winExtMap.registerExtensionWithWindow({
|
||||||
extPath: ext.extPath,
|
extPath: ext.extPath,
|
||||||
@ -111,7 +178,12 @@ export async function onCustomUiCmdSelect(
|
|||||||
const addr = await spawnExtensionFileServer(winLabel)
|
const addr = await spawnExtensionFileServer(winLabel)
|
||||||
const newUrl = `http://${addr}`
|
const newUrl = `http://${addr}`
|
||||||
url2 = `/app/extension/ui-iframe?url=${encodeURIComponent(newUrl)}&extPath=${encodeURIComponent(ext.extPath)}`
|
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)
|
const window = launchNewExtWindow(winLabel, url2, cmd.window)
|
||||||
window.onCloseRequested(async (event) => {
|
window.onCloseRequested(async (event) => {
|
||||||
await winExtMap.unregisterExtensionFromWindow(winLabel)
|
await winExtMap.unregisterExtensionFromWindow(winLabel)
|
||||||
@ -123,11 +195,13 @@ export async function onCustomUiCmdSelect(
|
|||||||
extPath: ext.extPath,
|
extPath: ext.extPath,
|
||||||
dist: cmd.dist
|
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>"
|
const addr = await spawnExtensionFileServer(winLabel) // addr has format "127.0.0.1:<port>"
|
||||||
console.log("Extension file server address: ", addr)
|
console.log("Extension file server address: ", addr)
|
||||||
const newUrl = `http://${addr}`
|
const newUrl = `http://${addr}`
|
||||||
url2 = `/app/extension/ui-iframe?url=${encodeURIComponent(newUrl)}&extPath=${encodeURIComponent(ext.extPath)}`
|
url2 = `/app/extension/ui-iframe?url=${encodeURIComponent(newUrl)}&extPath=${encodeURIComponent(ext.extPath)}`
|
||||||
|
setIframeExtParams(ext.extPath, newUrl)
|
||||||
}
|
}
|
||||||
goto(i18n.resolveRoute(url2))
|
goto(i18n.resolveRoute(url2))
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import { onQuickLinkSelect } from "./quick-links"
|
|||||||
|
|
||||||
const onExtCmdSelect: OnExtCmdSelect = (
|
const onExtCmdSelect: OnExtCmdSelect = (
|
||||||
ext: ExtPackageJsonExtra,
|
ext: ExtPackageJsonExtra,
|
||||||
cmd: CustomUiCmd | TemplateUiCmd,
|
cmd: CustomUiCmd | TemplateUiCmd | HeadlessCmd,
|
||||||
{ isDev, hmr }: { isDev: boolean; hmr: boolean }
|
{ isDev, hmr }: { isDev: boolean; hmr: boolean }
|
||||||
) => {
|
) => {
|
||||||
switch (cmd.type) {
|
switch (cmd.type) {
|
||||||
|
@ -1,4 +1,27 @@
|
|||||||
import { getSystemCommands } from "@kksh/api/commands"
|
import { getSystemCommands } from "@kksh/api/commands"
|
||||||
import type { SysCommand } from "@kksh/api/models"
|
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)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
@ -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>
|
@ -75,7 +75,7 @@
|
|||||||
<div class={cn("flex items-center gap-2", className)}>
|
<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} />
|
<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={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
|
Auto Install
|
||||||
</Button>
|
</Button> -->
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goHome } from "@/utils/route"
|
import { goHome } from "@/utils/route"
|
||||||
import { Button, SideBar } from "@kksh/svelte5"
|
import { Button, Sidebar } from "@kksh/svelte5"
|
||||||
import { Constants } from "@kksh/ui"
|
import { Constants } from "@kksh/ui"
|
||||||
import { ArrowLeftIcon } from "lucide-svelte"
|
import { ArrowLeftIcon } from "lucide-svelte"
|
||||||
|
|
||||||
const { useSidebar } = SideBar
|
const { useSidebar } = Sidebar
|
||||||
const sidebar = useSidebar()
|
const sidebar = useSidebar()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="fixed flex h-10 w-full items-center gap-2 pl-1 pt-1" data-tauri-drag-region>
|
<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"}
|
{#if sidebar.state === "collapsed"}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setAppConfigContext } from "@/context"
|
import { setAppConfigContext } from "@/context"
|
||||||
import { setAppStateContext } from "@/context/appState"
|
import { setAppStateContext } from "@/context/appState"
|
||||||
import type { AppConfig, AppState } from "@kksh/types"
|
import type { AppConfigState, AppState } from "@kksh/types"
|
||||||
import type { Snippet } from "svelte"
|
import { type Snippet } from "svelte"
|
||||||
import type { Writable } from "svelte/store"
|
import type { Writable } from "svelte/store"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -10,7 +10,7 @@
|
|||||||
appState,
|
appState,
|
||||||
children
|
children
|
||||||
}: {
|
}: {
|
||||||
appConfig: Writable<AppConfig>
|
appConfig: Writable<AppConfigState>
|
||||||
appState: Writable<AppState>
|
appState: Writable<AppState>
|
||||||
children: Snippet<[]>
|
children: Snippet<[]>
|
||||||
} = $props()
|
} = $props()
|
||||||
|
63
apps/desktop/src/lib/components/main/AppsCmds.svelte
Normal file
63
apps/desktop/src/lib/components/main/AppsCmds.svelte
Normal 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>
|
@ -11,21 +11,46 @@
|
|||||||
} from "@/paraglide/runtime"
|
} from "@/paraglide/runtime"
|
||||||
import { appConfig } from "@/stores"
|
import { appConfig } from "@/stores"
|
||||||
import { Select, Switch } from "@kksh/svelte5"
|
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) => ({
|
const languages = availableLanguageTags.map((lang) => ({
|
||||||
value: lang,
|
value: lang,
|
||||||
label: LanguageMap[lang] ?? lang
|
label: LanguageMap[lang as keyof typeof LanguageMap] ?? lang
|
||||||
}))
|
}))
|
||||||
|
let loadingAnimation = $state<LoadingAnimation>("spinning-circle")
|
||||||
let value = $state(languageTag())
|
const loadingAnimations = ["spinning-circle", "kunkun-dancing"] as const
|
||||||
|
let launchAtLogin = $state(false)
|
||||||
const triggerContent = $derived(languages.find((f) => f.value === value)?.label ?? "Language")
|
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>
|
</script>
|
||||||
|
|
||||||
<ul class="rounded-lg border">
|
<ul class="rounded-lg border">
|
||||||
<li>
|
<li>
|
||||||
<span>{m.settings_general_launch_at_login()}</span>
|
<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>
|
||||||
<li class="">
|
<li class="">
|
||||||
<span>{m.settings_general_hotkey()}</span>
|
<span>{m.settings_general_hotkey()}</span>
|
||||||
@ -59,7 +84,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<span>{m.settings_general_language()}</span>
|
<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">
|
<Select.Trigger class="w-fit">
|
||||||
{triggerContent}
|
{triggerContent}
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
@ -80,6 +105,31 @@
|
|||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import InstallCodeBlock from "@/components/common/install-code-block.svelte"
|
import InstallCodeBlock from "@/components/common/install-code-block.svelte"
|
||||||
import Icon from "@iconify/svelte"
|
import Icon from "@iconify/svelte"
|
||||||
import { IconEnum } from "@kksh/api/models"
|
|
||||||
import { Button, Tabs } from "@kksh/svelte5"
|
|
||||||
import { TauriLink } from "@kksh/ui"
|
import { TauriLink } from "@kksh/ui"
|
||||||
import { platform } from "@tauri-apps/plugin-os"
|
import { platform } from "@tauri-apps/plugin-os"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { toast } from "svelte-sonner"
|
|
||||||
import { whereIsCommand } from "tauri-plugin-shellx-api"
|
import { whereIsCommand } from "tauri-plugin-shellx-api"
|
||||||
|
|
||||||
let brewPath = $state("")
|
let brewPath = $state("")
|
||||||
@ -21,12 +18,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="font-mono text-2xl font-bold">Install Homebrew</h1>
|
<h1 class="font-mono text-2xl font-bold">Install Homebrew</h1>
|
||||||
<TauriLink
|
<TauriLink href="/app/help/brew-install" class="flex items-center">
|
||||||
href="/app/help/brew-install"
|
|
||||||
icon={IconEnum.Iconify}
|
|
||||||
iconValue="devicon:homebrew"
|
|
||||||
class="flex items-center"
|
|
||||||
>
|
|
||||||
<span class="text-lg">Homebrew Website</span>
|
<span class="text-lg">Homebrew Website</span>
|
||||||
<Icon icon="devicon:homebrew" class="h-6 w-6" />
|
<Icon icon="devicon:homebrew" class="h-6 w-6" />
|
||||||
</TauriLink>
|
</TauriLink>
|
||||||
|
@ -51,18 +51,18 @@
|
|||||||
runtime environment for executing extension code safely. It is optional but recommended.
|
runtime environment for executing extension code safely. It is optional but recommended.
|
||||||
</p>
|
</p>
|
||||||
<p class="font-mono text-sm">Choose any installation method below.</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>.
|
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">
|
<p class="font-mono text-sm text-red-400">
|
||||||
After installation, ensure the `deno` command is accessible from your system's PATH.
|
After installation, ensure the `deno` command is accessible from your system's PATH.
|
||||||
</p>
|
</p>
|
||||||
{#if _platform === "macos" || _platform === "linux"}
|
<!-- {#if _platform === "macos" || _platform === "linux"}
|
||||||
<p class="font-mono text-sm text-red-400">
|
<p class="font-mono text-sm text-red-400">
|
||||||
Installation with <span class="font-bold text-green-500">curl</span> command likely requires manual
|
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.
|
configuration. So auto install is disabled. Please copy the command and run it in a terminal.
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if} -->
|
||||||
{#if denoPath}
|
{#if denoPath}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span>✅</span>
|
<span>✅</span>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import DevExtPathForm from "@/components/standalone/settings/DevExtPathForm.svelte"
|
import DevExtPathForm from "@/components/standalone/settings/DevExtPathForm.svelte"
|
||||||
import { i18n } from "@/i18n"
|
import { i18n } from "@/i18n"
|
||||||
import * as m from "@/paraglide/messages"
|
import * as m from "@/paraglide/messages"
|
||||||
import { appConfig, extensions } from "@/stores"
|
import { appConfig, appState, extensions } from "@/stores"
|
||||||
import { goBackOnEscape } from "@/utils/key"
|
import { goBackOnEscape } from "@/utils/key"
|
||||||
import { goBack } from "@/utils/route"
|
import { goBack } from "@/utils/route"
|
||||||
import { IconEnum } from "@kksh/api/models"
|
import { IconEnum } from "@kksh/api/models"
|
||||||
@ -65,10 +65,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pickExtFolders() {
|
async function pickExtFolders() {
|
||||||
|
appState.setLockHideOnBlur(true)
|
||||||
const selected = await openFileSelector({
|
const selected = await openFileSelector({
|
||||||
directory: true,
|
directory: true,
|
||||||
multiple: true // allow install multiple extensions at once
|
multiple: true // allow install multiple extensions at once
|
||||||
})
|
})
|
||||||
|
appState.setLockHideOnBlur(false)
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
return toast.warning("No File Selected")
|
return toast.warning("No File Selected")
|
||||||
}
|
}
|
||||||
@ -91,6 +93,7 @@
|
|||||||
toast.warning("Please set the dev extension path in the settings")
|
toast.warning("Please set the dev extension path in the settings")
|
||||||
return goto(i18n.resolveRoute("/app/settings/set-dev-ext-path"))
|
return goto(i18n.resolveRoute("/app/settings/set-dev-ext-path"))
|
||||||
}
|
}
|
||||||
|
appState.setLockHideOnBlur(true)
|
||||||
const selected = await openFileSelector({
|
const selected = await openFileSelector({
|
||||||
directory: false,
|
directory: false,
|
||||||
multiple: true, // allow install multiple extensions at once
|
multiple: true, // allow install multiple extensions at once
|
||||||
@ -101,6 +104,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
appState.setLockHideOnBlur(false)
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
return toast.warning("No File Selected")
|
return toast.warning("No File Selected")
|
||||||
}
|
}
|
||||||
@ -111,15 +115,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="my-3 flex justify-center gap-3">
|
<div class="my-3 flex justify-center gap-3">
|
||||||
<Button size="sm" onclick={pickExtFolders}
|
<Button size="sm" onclick={pickExtFolders}>
|
||||||
>{m.settings_add_dev_ext_install_from_ext_folders()}</Button
|
{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={pickExtFiles}>
|
||||||
>
|
{m.settings_add_dev_ext_install_from_ext_files()}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StrikeSeparator class="my-1">
|
<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>
|
</StrikeSeparator>
|
||||||
|
|
||||||
<Layouts.Center>
|
<Layouts.Center>
|
||||||
@ -150,12 +157,12 @@
|
|||||||
icon={{ value: "mdi:folder-cog-outline", type: IconEnum.Iconify }}
|
icon={{ value: "mdi:folder-cog-outline", type: IconEnum.Iconify }}
|
||||||
class="h-10 w-10"
|
class="h-10 w-10"
|
||||||
/>
|
/>
|
||||||
<small class="select-none font-mono text-xs"
|
<small class="select-none font-mono text-xs">
|
||||||
>{m.settings_add_dev_ext_drag_and_drop()}</small
|
{m.settings_add_dev_ext_drag_and_drop()}
|
||||||
>
|
</small>
|
||||||
<small class="select-none font-mono text-xs"
|
<small class="select-none font-mono text-xs">
|
||||||
>{m.settings_add_dev_ext_drag_and_drop2()}</small
|
{m.settings_add_dev_ext_drag_and_drop2()}
|
||||||
>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
@ -23,5 +23,7 @@ export const IS_IN_TAURI =
|
|||||||
export const LanguageMap = {
|
export const LanguageMap = {
|
||||||
en: "English",
|
en: "English",
|
||||||
zh: "中文",
|
zh: "中文",
|
||||||
ru: "Русский"
|
ru: "Русский",
|
||||||
|
pt: "Português",
|
||||||
|
vi: "Tiếng Việt"
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,16 @@
|
|||||||
* It's designed to allow all components to access a shared state.
|
* 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.
|
* 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 { getContext, setContext } from "svelte"
|
||||||
import type { Writable } from "svelte/store"
|
import type { Writable } from "svelte/store"
|
||||||
|
|
||||||
export const APP_CONFIG_CONTEXT_KEY = Symbol("appConfig")
|
export const APP_CONFIG_CONTEXT_KEY = Symbol("appConfig")
|
||||||
|
|
||||||
export function getAppConfigContext(): Writable<AppConfig> {
|
export function getAppConfigContext(): Writable<AppConfigState> {
|
||||||
return getContext(APP_CONFIG_CONTEXT_KEY)
|
return getContext(APP_CONFIG_CONTEXT_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAppConfigContext(appConfig: Writable<AppConfig>) {
|
export function setAppConfigContext(appConfig: Writable<AppConfigState>) {
|
||||||
setContext(APP_CONFIG_CONTEXT_KEY, appConfig)
|
setContext(APP_CONFIG_CONTEXT_KEY, appConfig)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { AppState } from "@/types/appState"
|
import type { AppState } from "@kksh/types"
|
||||||
import { getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
import type { Writable } from "svelte/store"
|
import type { Writable } from "svelte/store"
|
||||||
|
|
||||||
|
60
apps/desktop/src/lib/orm/database.ts
Normal file
60
apps/desktop/src/lib/orm/database.ts
Normal 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)
|
||||||
|
// }
|
@ -1,15 +1,16 @@
|
|||||||
import { getExtensionsFolder } from "@/constants"
|
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 { updateTheme, type ThemeConfig } from "@kksh/svelte5"
|
||||||
import { PersistedAppConfig, type AppConfig } from "@kksh/types"
|
import { LoadingAnimation, PersistedAppConfig, type AppConfigState } from "@kksh/types"
|
||||||
import { debug, error } from "@tauri-apps/plugin-log"
|
import { debug, error, info } from "@tauri-apps/plugin-log"
|
||||||
import * as os from "@tauri-apps/plugin-os"
|
import * as os from "@tauri-apps/plugin-os"
|
||||||
import { load } from "@tauri-apps/plugin-store"
|
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 { get, writable } from "svelte/store"
|
||||||
import * as v from "valibot"
|
import * as v from "valibot"
|
||||||
import { setLanguageTag } from "../paraglide/runtime"
|
|
||||||
|
|
||||||
export const defaultAppConfig: AppConfig = {
|
export const defaultAppConfig: AppConfigState = {
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
platform: "macos",
|
platform: "macos",
|
||||||
language: "en",
|
language: "en",
|
||||||
@ -19,7 +20,6 @@ export const defaultAppConfig: AppConfig = {
|
|||||||
lightMode: "auto"
|
lightMode: "auto"
|
||||||
},
|
},
|
||||||
triggerHotkey: null,
|
triggerHotkey: null,
|
||||||
launchAtLogin: true,
|
|
||||||
showInTray: true,
|
showInTray: true,
|
||||||
devExtensionPath: null,
|
devExtensionPath: null,
|
||||||
extensionsInstallDir: undefined,
|
extensionsInstallDir: undefined,
|
||||||
@ -28,72 +28,82 @@ export const defaultAppConfig: AppConfig = {
|
|||||||
extensionAutoUpgrade: true,
|
extensionAutoUpgrade: true,
|
||||||
joinBetaProgram: false,
|
joinBetaProgram: false,
|
||||||
onBoarded: false,
|
onBoarded: false,
|
||||||
developerMode: false
|
developerMode: false,
|
||||||
|
appSearchPaths: [],
|
||||||
|
loadingAnimation: "kunkun-dancing"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appConfigLoaded = writable(false)
|
export const appConfigLoaded = writable(false)
|
||||||
|
|
||||||
interface AppConfigAPI {
|
interface AppConfigAPI {
|
||||||
init: () => Promise<void>
|
init: () => Promise<void>
|
||||||
get: () => AppConfig
|
get: () => AppConfigState
|
||||||
setTheme: (theme: ThemeConfig) => void
|
setTheme: (theme: ThemeConfig) => void
|
||||||
setDevExtensionPath: (devExtensionPath: string | null) => void
|
setDevExtensionPath: (devExtensionPath: string | null) => void
|
||||||
setTriggerHotkey: (triggerHotkey: string[]) => void
|
setTriggerHotkey: (triggerHotkey: string[]) => void
|
||||||
setOnBoarded: (onBoarded: boolean) => void
|
setOnBoarded: (onBoarded: boolean) => void
|
||||||
setLanguage: (language: string) => void
|
setLanguage: (language: string) => void
|
||||||
|
addAppSearchPath: (appSearchPath: SearchPath) => void
|
||||||
|
removeAppSearchPath: (appSearchPath: SearchPath) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAppConfig(): WithSyncStore<AppConfig & { language: string }> & AppConfigAPI {
|
class AppConfigStore extends Store<AppConfigState> implements AppConfigAPI {
|
||||||
const store = createTauriSyncStore("app-config", defaultAppConfig)
|
constructor() {
|
||||||
|
super("app-config", defaultAppConfig, {
|
||||||
async function init() {
|
saveOnChange: true
|
||||||
debug("Initializing app config")
|
})
|
||||||
const persistStore = await load("kk-config.json", { autoSave: true })
|
this.start().catch((err) => {
|
||||||
const loadedConfig = await persistStore.get("config")
|
error("Failed to start app config store", err)
|
||||||
const parseRes = v.safeParse(PersistedAppConfig, loadedConfig)
|
toast.error("Failed to start app config store", { description: err.message })
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async init() {
|
||||||
|
debug("Initializing app config")
|
||||||
|
const extensionsInstallDir = await getExtensionsFolder()
|
||||||
|
this.update((config) => ({
|
||||||
|
...config,
|
||||||
|
isInitialized: true,
|
||||||
|
platform: os.platform(),
|
||||||
|
extensionsInstallDir
|
||||||
|
}))
|
||||||
|
appConfigLoaded.set(true)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
get() {
|
||||||
...store,
|
return get(this)
|
||||||
get: () => get(store),
|
}
|
||||||
setTheme: (theme: ThemeConfig) => store.update((config) => ({ ...config, theme })),
|
setTheme(theme: ThemeConfig) {
|
||||||
setDevExtensionPath: (devExtensionPath: string | null) => {
|
this.update((config) => ({ ...config, theme }))
|
||||||
console.log("setDevExtensionPath", devExtensionPath)
|
}
|
||||||
store.update((config) => ({ ...config, devExtensionPath }))
|
setDevExtensionPath(devExtensionPath: string | null) {
|
||||||
},
|
info(`setDevExtensionPath ${devExtensionPath}`)
|
||||||
setTriggerHotkey: (triggerHotkey: string[]) => {
|
this.update((config) => ({ ...config, devExtensionPath }))
|
||||||
store.update((config) => ({ ...config, triggerHotkey }))
|
}
|
||||||
},
|
setTriggerHotkey(triggerHotkey: string[]) {
|
||||||
setOnBoarded: (onBoarded: boolean) => {
|
this.update((config) => ({ ...config, triggerHotkey }))
|
||||||
store.update((config) => ({ ...config, onBoarded }))
|
}
|
||||||
},
|
setOnBoarded(onBoarded: boolean) {
|
||||||
setLanguage: (language: string) => {
|
this.update((config) => ({ ...config, onBoarded }))
|
||||||
store.update((config) => ({ ...config, language }))
|
}
|
||||||
},
|
setLanguage(language: string) {
|
||||||
init
|
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()
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { findAllArgsInLink } from "@/cmds/quick-links"
|
import { Action as ActionSchema } from "@kksh/api/models"
|
||||||
import { Action as ActionSchema, CmdTypeEnum } from "@kksh/api/models"
|
|
||||||
import type { AppState } from "@kksh/types"
|
import type { AppState } from "@kksh/types"
|
||||||
import type { CmdValue } from "@kksh/ui/types"
|
import { get, writable, type Writable } from "svelte/store"
|
||||||
import { derived, get, writable, type Writable } from "svelte/store"
|
|
||||||
|
|
||||||
export const defaultAppState: AppState = {
|
export const defaultAppState: AppState = {
|
||||||
searchTerm: "",
|
searchTerm: "",
|
||||||
highlightedCmd: "",
|
highlightedCmd: "",
|
||||||
loadingBar: false,
|
loadingBar: false,
|
||||||
defaultAction: "",
|
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 {
|
interface AppStateAPI {
|
||||||
@ -18,6 +18,8 @@ interface AppStateAPI {
|
|||||||
setLoadingBar: (loadingBar: boolean) => void
|
setLoadingBar: (loadingBar: boolean) => void
|
||||||
setDefaultAction: (defaultAction: string | null) => void
|
setDefaultAction: (defaultAction: string | null) => void
|
||||||
setActionPanel: (actionPanel?: ActionSchema.ActionPanel) => void
|
setActionPanel: (actionPanel?: ActionSchema.ActionPanel) => void
|
||||||
|
setLockHideOnBlur: (lockHideOnBlur: boolean) => void
|
||||||
|
setFullScreenLoading: (fullScreenLoading: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAppState(): Writable<AppState> & AppStateAPI {
|
function createAppState(): Writable<AppState> & AppStateAPI {
|
||||||
@ -37,6 +39,12 @@ function createAppState(): Writable<AppState> & AppStateAPI {
|
|||||||
},
|
},
|
||||||
setActionPanel: (actionPanel?: ActionSchema.ActionPanel) => {
|
setActionPanel: (actionPanel?: ActionSchema.ActionPanel) => {
|
||||||
store.update((state) => ({ ...state, actionPanel }))
|
store.update((state) => ({ ...state, actionPanel }))
|
||||||
|
},
|
||||||
|
setLockHideOnBlur: (lockHideOnBlur: boolean) => {
|
||||||
|
store.update((state) => ({ ...state, lockHideOnBlur }))
|
||||||
|
},
|
||||||
|
setFullScreenLoading: (fullScreenLoading: boolean) => {
|
||||||
|
store.update((state) => ({ ...state, fullScreenLoading }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
apps/desktop/src/lib/stores/apps.ts
Normal file
48
apps/desktop/src/lib/stores/apps.ts
Normal 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)
|
||||||
|
})
|
@ -1,11 +1,11 @@
|
|||||||
import { getExtensionsFolder } from "@/constants"
|
import type { CustomUiCmd, ExtPackageJsonExtra, HeadlessCmd, TemplateUiCmd } from "@kksh/api/models"
|
||||||
import { db } from "@kksh/api/commands"
|
import { db } from "@kksh/drizzle"
|
||||||
import type { ExtPackageJson, ExtPackageJsonExtra } from "@kksh/api/models"
|
|
||||||
import * as extAPI from "@kksh/extension"
|
import * as extAPI from "@kksh/extension"
|
||||||
import * as path from "@tauri-apps/api/path"
|
import * as path from "@tauri-apps/api/path"
|
||||||
import * as fs from "@tauri-apps/plugin-fs"
|
import Fuse from "fuse.js"
|
||||||
import { derived, get, writable, type Readable, type Writable } from "svelte/store"
|
import { derived, get, writable, type Writable } from "svelte/store"
|
||||||
import { appConfig } from "./appConfig"
|
import { appConfig } from "./appConfig"
|
||||||
|
import { appState } from "./appState"
|
||||||
|
|
||||||
function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
|
function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
|
||||||
init: () => Promise<void>
|
init: () => Promise<void>
|
||||||
@ -27,6 +27,7 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
|
|||||||
tarballUrl: string,
|
tarballUrl: string,
|
||||||
extras?: { overwritePackageJson?: string }
|
extras?: { overwritePackageJson?: string }
|
||||||
) => Promise<ExtPackageJsonExtra>
|
) => Promise<ExtPackageJsonExtra>
|
||||||
|
reloadExtension: (extPath: string) => Promise<void>
|
||||||
} {
|
} {
|
||||||
const store = writable<ExtPackageJsonExtra[]>([])
|
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)
|
* Get all extensions installed from the store (non-dev extensions)
|
||||||
*/
|
*/
|
||||||
@ -193,6 +210,7 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
|
|||||||
return {
|
return {
|
||||||
...store,
|
...store,
|
||||||
init,
|
init,
|
||||||
|
reloadExtension,
|
||||||
getExtensionsFromStore,
|
getExtensionsFromStore,
|
||||||
findStoreExtensionByIdentifier,
|
findStoreExtensionByIdentifier,
|
||||||
registerNewExtensionByPath,
|
registerNewExtensionByPath,
|
||||||
@ -207,20 +225,62 @@ function createExtensionsStore(): Writable<ExtPackageJsonExtra[]> & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const extensions = createExtensionsStore()
|
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(
|
export type StoreExtCmd = (CustomUiCmd | TemplateUiCmd | HeadlessCmd) & {
|
||||||
extensions,
|
ext: ExtPackageJsonExtra
|
||||||
($extensionsStore) => {
|
}
|
||||||
const extContainerPath = get(appConfig).extensionsInstallDir
|
|
||||||
if (!extContainerPath) return []
|
export const cmdsFuse = new Fuse<StoreExtCmd>([], {
|
||||||
return $extensionsStore.filter((ext) => !extAPI.isExtPathInDev(extContainerPath, ext.extPath))
|
includeScore: true,
|
||||||
}
|
threshold: 0.2,
|
||||||
)
|
keys: ["name"]
|
||||||
export const devStoreExts: Readable<ExtPackageJsonExtra[]> = derived(
|
})
|
||||||
extensions,
|
export const devCmdsFuse = new Fuse<StoreExtCmd>([], {
|
||||||
($extensionsStore) => {
|
includeScore: true,
|
||||||
const extContainerPath = get(appConfig).extensionsInstallDir
|
threshold: 0.2,
|
||||||
if (!extContainerPath) return []
|
keys: ["name"]
|
||||||
return $extensionsStore.filter((ext) => extAPI.isExtPathInDev(extContainerPath, ext.extPath))
|
})
|
||||||
}
|
|
||||||
)
|
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
|
||||||
|
})
|
||||||
|
@ -4,3 +4,4 @@ export * from "./winExtMap"
|
|||||||
export * from "./extensions"
|
export * from "./extensions"
|
||||||
export * from "./auth"
|
export * from "./auth"
|
||||||
export * from "./quick-links"
|
export * from "./quick-links"
|
||||||
|
export * from "./apps"
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
import type { Icon } from "@kksh/api/models"
|
import type { Icon } from "@kksh/api/models"
|
||||||
import { createQuickLinkCommand, getAllQuickLinkCommands } from "@kksh/extension/db"
|
import { createQuickLinkCommand, getAllQuickLinkCommands } from "@kksh/extension/db"
|
||||||
import type { CmdQuery, QuickLink } from "@kksh/ui/types"
|
import type { QuickLink } from "@kksh/ui/types"
|
||||||
import { get, writable, type Writable } from "svelte/store"
|
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 {
|
export interface QuickLinkAPI {
|
||||||
get: () => QuickLink[]
|
get: () => QuickLink[]
|
||||||
@ -19,7 +28,9 @@ function createQuickLinksStore(): Writable<QuickLink[]> & QuickLinkAPI {
|
|||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
const cmds = await getAllQuickLinkCommands()
|
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) {
|
async function createQuickLink(name: string, link: string, icon: Icon) {
|
||||||
@ -37,3 +48,22 @@ function createQuickLinksStore(): Writable<QuickLink[]> & QuickLinkAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const quickLinks = createQuickLinksStore()
|
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
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
@ -104,6 +104,7 @@ function createWinExtMapStore(): Writable<WinExtMap> & API {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
registerProcess: async (windowLabel: string, pid: number) => {
|
registerProcess: async (windowLabel: string, pid: number) => {
|
||||||
|
console.log("registerProcess", windowLabel, pid)
|
||||||
const winExtMap = get(store)
|
const winExtMap = get(store)
|
||||||
await registerExtensionSpawnedProcess(windowLabel, pid)
|
await registerExtensionSpawnedProcess(windowLabel, pid)
|
||||||
if (!winExtMap[windowLabel]) {
|
if (!winExtMap[windowLabel]) {
|
||||||
@ -116,6 +117,7 @@ function createWinExtMapStore(): Writable<WinExtMap> & API {
|
|||||||
const winExtMap = get(store)
|
const winExtMap = get(store)
|
||||||
const found = Object.entries(winExtMap).find(([windowLabel, ext]) => ext.pids.includes(pid))
|
const found = Object.entries(winExtMap).find(([windowLabel, ext]) => ext.pids.includes(pid))
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
warn(`Process ${pid} does not have an extension registered, thus will not be killed`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const [windowLabel, ext] = found
|
const [windowLabel, ext] = found
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
import { SupabaseAPI } from "@kksh/supabase/api"
|
import * as sb from "@supabase/supabase-js"
|
||||||
import type { Database } from "@kksh/supabase/types"
|
|
||||||
import { createClient, SupabaseClient } from "@supabase/supabase-js"
|
|
||||||
import { SUPABASE_ANON_KEY, SUPABASE_URL } from "./constants"
|
import { SUPABASE_ANON_KEY, SUPABASE_URL } from "./constants"
|
||||||
|
|
||||||
// export const supabase = createSB(SUPABASE_URL, SUPABASE_ANON_KEY)
|
// export const supabase = createSB(SUPABASE_URL, SUPABASE_ANON_KEY)
|
||||||
export const supabase: SupabaseClient<Database> = createClient<Database>(
|
export const supabase: sb.SupabaseClient = sb.createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||||
SUPABASE_URL,
|
|
||||||
SUPABASE_ANON_KEY,
|
|
||||||
{
|
|
||||||
auth: {
|
auth: {
|
||||||
flowType: "pkce"
|
flowType: "pkce"
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
export const storage = supabase.storage
|
export const storage = supabase.storage
|
||||||
export const supabaseExtensionsStorage = supabase.storage.from("extensions")
|
export const supabaseExtensionsStorage = supabase.storage.from("extensions")
|
||||||
export const supabaseAPI = new SupabaseAPI(supabase)
|
// export const supabaseAPI = new SupabaseAPI(supabase)
|
||||||
|
12
apps/desktop/src/lib/types/fs.ts
Normal file
12
apps/desktop/src/lib/types/fs.ts
Normal 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>
|
44
apps/desktop/src/lib/utils/clipboard.ts
Normal file
44
apps/desktop/src/lib/utils/clipboard.ts
Normal 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}`)
|
||||||
|
}
|
||||||
|
}
|
13
apps/desktop/src/lib/utils/db.ts
Normal file
13
apps/desktop/src/lib/utils/db.ts
Normal 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}`)
|
||||||
|
}
|
||||||
|
}
|
@ -58,7 +58,7 @@ export async function handleKunkunProtocol(parsedUrl: URL) {
|
|||||||
const parsed = v.parse(StorePathSearchParams, params)
|
const parsed = v.parse(StorePathSearchParams, params)
|
||||||
openMainWindow()
|
openMainWindow()
|
||||||
if (parsed.identifier) {
|
if (parsed.identifier) {
|
||||||
goto(`/extension/store/${parsed.identifier}`)
|
goto(i18n.resolveRoute(`/app/extension/store/${parsed.identifier}`))
|
||||||
} else {
|
} else {
|
||||||
goto(i18n.resolveRoute("/app/extension/store"))
|
goto(i18n.resolveRoute("/app/extension/store"))
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ export async function handleKunkunProtocol(parsedUrl: URL) {
|
|||||||
emitRefreshDevExt()
|
emitRefreshDevExt()
|
||||||
} else if (href.startsWith(DEEP_LINK_PATH_AUTH_CONFIRM)) {
|
} else if (href.startsWith(DEEP_LINK_PATH_AUTH_CONFIRM)) {
|
||||||
openMainWindow()
|
openMainWindow()
|
||||||
goto(`/auth/confirm?${parsedUrl.searchParams.toString()}`)
|
goto(i18n.resolveRoute(`/app/auth/confirm?${parsedUrl.searchParams.toString()}`))
|
||||||
} else {
|
} else {
|
||||||
console.error("Invalid path:", pathname)
|
console.error("Invalid path:", pathname)
|
||||||
toast.error("Invalid path", {
|
toast.error("Invalid path", {
|
||||||
|
19
apps/desktop/src/lib/utils/helper.ts
Normal file
19
apps/desktop/src/lib/utils/helper.ts
Normal 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"))
|
||||||
|
}
|
||||||
|
}
|
@ -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 { isRegistered, register, unregister } from "@tauri-apps/plugin-global-shortcut"
|
||||||
import { debug, info, warn } from "@tauri-apps/plugin-log"
|
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 { 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.
|
* 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
|
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) {
|
export async function registerAppHotkey(hotkeyStr: string) {
|
||||||
if (await isRegistered(hotkeyStr)) {
|
if (await isRegistered(hotkeyStr)) {
|
||||||
warn(`Hotkey (${hotkeyStr}) already registered`)
|
warn(`Hotkey (${hotkeyStr}) already registered`)
|
||||||
@ -39,13 +48,17 @@ export async function registerAppHotkey(hotkeyStr: string) {
|
|||||||
mainWin.setFocus()
|
mainWin.setFocus()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mainWin.show()
|
mainWin.show().then(() => mainWin.setFocus())
|
||||||
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) {
|
export async function updateAppHotkey(newHotkey: string[], oldHotkey?: string[] | null) {
|
||||||
if (oldHotkey) {
|
if (oldHotkey) {
|
||||||
const hotkeyStr = oldHotkey.map(mapKeyToTauriKey).join("+")
|
const hotkeyStr = oldHotkey.map(mapKeyToTauriKey).join("+")
|
||||||
@ -56,3 +69,44 @@ export async function updateAppHotkey(newHotkey: string[], oldHotkey?: string[]
|
|||||||
const hotkeyStr = newHotkey.map(mapKeyToTauriKey).join("+")
|
const hotkeyStr = newHotkey.map(mapKeyToTauriKey).join("+")
|
||||||
return registerAppHotkey(hotkeyStr)
|
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())
|
||||||
|
}
|
||||||
|
@ -1,24 +1,38 @@
|
|||||||
import { appConfig } from "@/stores"
|
import { appConfig, extensions } from "@/stores"
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
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 { dev } from "$app/environment"
|
||||||
|
import { cleanClipboard } from "./clipboard"
|
||||||
|
import { vacuumSqlite } from "./db"
|
||||||
import { mapKeyToTauriKey, registerAppHotkey } from "./hotkey"
|
import { mapKeyToTauriKey, registerAppHotkey } from "./hotkey"
|
||||||
|
import { listenToReloadOneExtension } from "./tauri-events"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the app
|
* Initialize the app
|
||||||
*/
|
*/
|
||||||
export function init() {
|
export async function init() {
|
||||||
const window = getCurrentWindow()
|
const window = getCurrentWindow()
|
||||||
if (window.label === "main") {
|
if (window.label === "main") {
|
||||||
initMainWindow()
|
initMainWindow()
|
||||||
}
|
listenToReloadOneExtension(({ payload: { extPath } }) => {
|
||||||
|
info(`listenToReloadOneExtension in main window: ${extPath}`)
|
||||||
if (!dev) {
|
extensions.reloadExtension(extPath)
|
||||||
document.addEventListener("contextmenu", function (event) {
|
|
||||||
event.preventDefault()
|
|
||||||
console.warn("contextmenu disabled in release mode", event)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
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() {
|
export function initMainWindow() {
|
||||||
@ -31,6 +45,6 @@ export function initMainWindow() {
|
|||||||
info(`Registering hotkey: ${hotkeyStr}`)
|
info(`Registering hotkey: ${hotkeyStr}`)
|
||||||
registerAppHotkey(hotkeyStr)
|
registerAppHotkey(hotkeyStr)
|
||||||
} else {
|
} else {
|
||||||
console.log("No hotkey found in confi")
|
console.log("No hotkey found in config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,10 @@ export function goHomeOrCloseOnEscapeWithInput(e: KeyboardEvent) {
|
|||||||
export async function globalKeyDownHandler(e: KeyboardEvent) {
|
export async function globalKeyDownHandler(e: KeyboardEvent) {
|
||||||
keys.keydown(e.key)
|
keys.keydown(e.key)
|
||||||
const _platform = platform()
|
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 === ",") {
|
if (e.key === ",") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
goto(i18n.resolveRoute("/app/settings"))
|
goto(i18n.resolveRoute("/app/settings"))
|
||||||
@ -94,7 +97,7 @@ export async function globalKeyDownHandler(e: KeyboardEvent) {
|
|||||||
await appWin.hide()
|
await appWin.hide()
|
||||||
location.reload()
|
location.reload()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
appWin.show()
|
appWin.show().then(() => appWin.setFocus())
|
||||||
}, 1_000)
|
}, 1_000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
apps/desktop/src/lib/utils/kkrpc.ts
Normal file
19
apps/desktop/src/lib/utils/kkrpc.ts
Normal 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"
|
||||||
|
}
|
@ -16,6 +16,8 @@ export const FileDragOver = "tauri://drag-over"
|
|||||||
export const NewClipboardItemAddedEvent = "new_clipboard_item_added"
|
export const NewClipboardItemAddedEvent = "new_clipboard_item_added"
|
||||||
export const RefreshConfigEvent = "kunkun://refresh-config"
|
export const RefreshConfigEvent = "kunkun://refresh-config"
|
||||||
export const RefreshExtEvent = "kunkun://refresh-extensions"
|
export const RefreshExtEvent = "kunkun://refresh-extensions"
|
||||||
|
export const ReloadOneExtensionEvent = "kunkun://reload-one-extension"
|
||||||
|
|
||||||
export function listenToFileDrop(cb: EventCallback<{ paths: string[] }>) {
|
export function listenToFileDrop(cb: EventCallback<{ paths: string[] }>) {
|
||||||
return listen<{ paths: string[] }>(FileDragDrop, cb)
|
return listen<{ paths: string[] }>(FileDragDrop, cb)
|
||||||
}
|
}
|
||||||
@ -55,3 +57,11 @@ export function emitRefreshDevExt() {
|
|||||||
export function listenToRefreshDevExt(cb: EventCallback<null>) {
|
export function listenToRefreshDevExt(cb: EventCallback<null>) {
|
||||||
return listen(DEEP_LINK_PATH_REFRESH_DEV_EXTENSION, cb)
|
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)
|
||||||
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { extensions } from "@/stores"
|
import { extensions } from "@/stores"
|
||||||
import { supabaseAPI } from "@/supabase"
|
|
||||||
import { isCompatible } from "@kksh/api"
|
import { isCompatible } from "@kksh/api"
|
||||||
import type { ExtPackageJsonExtra } from "@kksh/api/models"
|
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 { relaunch } from "@tauri-apps/plugin-process"
|
||||||
import { check } from "@tauri-apps/plugin-updater"
|
import { check } from "@tauri-apps/plugin-updater"
|
||||||
import { gt } from "semver"
|
import { gt } from "semver"
|
||||||
@ -32,11 +31,22 @@ export async function checkSingleExtensionUpdate(
|
|||||||
installedExt: ExtPackageJsonExtra,
|
installedExt: ExtPackageJsonExtra,
|
||||||
autoupgrade: boolean
|
autoupgrade: boolean
|
||||||
) {
|
) {
|
||||||
const { data: sbExt, error } = await supabaseAPI.getLatestExtPublish(
|
const {
|
||||||
installedExt.kunkun.identifier
|
data: sbExt,
|
||||||
)
|
error,
|
||||||
|
response
|
||||||
|
} = await getExtensionsLatestPublishByIdentifier({
|
||||||
|
path: {
|
||||||
|
identifier: "RAG"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// const { data: sbExt, error } = await supabaseAPI.getLatestExtPublish(
|
||||||
|
// installedExt.kunkun.identifier
|
||||||
|
// )
|
||||||
if (error) {
|
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) {
|
if (!sbExt) {
|
||||||
@ -49,10 +59,7 @@ export async function checkSingleExtensionUpdate(
|
|||||||
) {
|
) {
|
||||||
if (autoupgrade) {
|
if (autoupgrade) {
|
||||||
await extensions
|
await extensions
|
||||||
.upgradeStoreExtension(
|
.upgradeStoreExtension(sbExt.identifier, sbExt.tarball_path)
|
||||||
sbExt.identifier,
|
|
||||||
supabaseAPI.translateExtensionFilePathToUrl(sbExt.tarball_path)
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success(`${sbExt.name} upgraded`, {
|
toast.success(`${sbExt.name} upgraded`, {
|
||||||
description: `From ${installedExt.version} to ${sbExt.version}`
|
description: `From ${installedExt.version} to ${sbExt.version}`
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<svelte:window on:keydown={handleKeyDown} />
|
<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">
|
<Layouts.Center class="min-h-screen py-5">
|
||||||
<Error.RawErrorJSONPreset
|
<Error.RawErrorJSONPreset
|
||||||
title="Error"
|
title="Error"
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ParaglideJS } from "@inlang/paraglide-sveltekit"
|
import { ParaglideJS } from "@inlang/paraglide-sveltekit"
|
||||||
import { i18n } from "$lib/i18n"
|
import { i18n } from "$lib/i18n"
|
||||||
import { onMount } from "svelte"
|
|
||||||
import "../app.css"
|
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 { ModeWatcher, ThemeWrapper } from "@kksh/svelte5"
|
||||||
import { Toaster } from "svelte-sonner"
|
import { Toaster } from "svelte-sonner"
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
init()
|
|
||||||
})
|
|
||||||
|
|
||||||
let { children } = $props()
|
let { children } = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -18,6 +14,9 @@
|
|||||||
<ModeWatcher />
|
<ModeWatcher />
|
||||||
<Toaster richColors closeButton />
|
<Toaster richColors closeButton />
|
||||||
<ThemeWrapper>
|
<ThemeWrapper>
|
||||||
|
{#if $appState.fullScreenLoading}
|
||||||
|
<FullScreenLoading class="bg-background absolute inset-0 z-50" />
|
||||||
|
{/if}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</ThemeWrapper>
|
</ThemeWrapper>
|
||||||
</ParaglideJS>
|
</ParaglideJS>
|
||||||
|
@ -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
|
// Tauri doesn't have a Node.js server to do proper SSR
|
||||||
// so we will use adapter-static to prerender the app (SSG)
|
// so we will use adapter-static to prerender the app (SSG)
|
||||||
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
|
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
|
||||||
export const prerender = true
|
export const prerender = true
|
||||||
export const ssr = false
|
export const ssr = false
|
||||||
|
|
||||||
|
export const load = () => {
|
||||||
|
if (browser) {
|
||||||
|
const win = getCurrentWebviewWindow()
|
||||||
|
return { win }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
import { i18n, switchToLanguage } from "@/i18n"
|
import { i18n, switchToLanguage } from "@/i18n"
|
||||||
import { setLanguageTag, type AvailableLanguageTag } from "@/paraglide/runtime"
|
import { setLanguageTag, type AvailableLanguageTag } from "@/paraglide/runtime"
|
||||||
import { appConfig, appState, extensions, quickLinks, winExtMap } from "@/stores"
|
import { appConfig, appState, extensions, quickLinks, winExtMap } from "@/stores"
|
||||||
|
import { appsLoader } from "@/stores/apps"
|
||||||
import { initDeeplink } from "@/utils/deeplink"
|
import { initDeeplink } from "@/utils/deeplink"
|
||||||
import { updateAppHotkey } from "@/utils/hotkey"
|
import { updateAppHotkey } from "@/utils/hotkey"
|
||||||
|
import { init as initApp } from "@/utils/init"
|
||||||
import { globalKeyDownHandler, globalKeyUpHandler, goBackOrCloseOnEscape } from "@/utils/key"
|
import { globalKeyDownHandler, globalKeyUpHandler, goBackOrCloseOnEscape } from "@/utils/key"
|
||||||
import { listenToWindowBlur } from "@/utils/tauri-events"
|
import { listenToWindowBlur } from "@/utils/tauri-events"
|
||||||
import { isInMainWindow } from "@/utils/window"
|
import { isInMainWindow } from "@/utils/window"
|
||||||
@ -12,7 +14,7 @@
|
|||||||
import { Constants, ViewTransition } from "@kksh/ui"
|
import { Constants, ViewTransition } from "@kksh/ui"
|
||||||
import type { UnlistenFn } from "@tauri-apps/api/event"
|
import type { UnlistenFn } from "@tauri-apps/api/event"
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
|
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 { afterNavigate, beforeNavigate } from "$app/navigation"
|
||||||
import { gsap } from "gsap"
|
import { gsap } from "gsap"
|
||||||
import { Flip } from "gsap/Flip"
|
import { Flip } from "gsap/Flip"
|
||||||
@ -44,15 +46,14 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let { children } = $props()
|
let { children, data } = $props()
|
||||||
|
|
||||||
const unlisteners: UnlistenFn[] = []
|
const unlisteners: UnlistenFn[] = []
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
unlisteners.forEach((unlistener) => unlistener())
|
unlisteners.forEach((unlistener) => unlistener())
|
||||||
})
|
})
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
console.log("root layout onMount")
|
await attachConsole().then((unlistener) => unlisteners.push(unlistener))
|
||||||
|
|
||||||
attachConsole().then((unlistener) => unlisteners.push(unlistener))
|
|
||||||
initDeeplink().then((unlistener) => unlisteners.push(unlistener))
|
initDeeplink().then((unlistener) => unlisteners.push(unlistener))
|
||||||
shellx
|
shellx
|
||||||
.fixPathEnv()
|
.fixPathEnv()
|
||||||
@ -60,13 +61,14 @@
|
|||||||
info("fixed path env")
|
info("fixed path env")
|
||||||
})
|
})
|
||||||
.catch(error)
|
.catch(error)
|
||||||
|
|
||||||
quickLinks.init()
|
quickLinks.init()
|
||||||
appConfig.init().then(() => {
|
appConfig.init().then(() => {
|
||||||
|
initApp()
|
||||||
console.log("appConfig.language", $appConfig.language)
|
console.log("appConfig.language", $appConfig.language)
|
||||||
setLanguageTag($appConfig.language as AvailableLanguageTag)
|
setLanguageTag($appConfig.language as AvailableLanguageTag)
|
||||||
switchToLanguage($appConfig.language as AvailableLanguageTag)
|
switchToLanguage($appConfig.language as AvailableLanguageTag)
|
||||||
})
|
})
|
||||||
|
appsLoader.init()
|
||||||
if (isInMainWindow()) {
|
if (isInMainWindow()) {
|
||||||
if ($appConfig.triggerHotkey) {
|
if ($appConfig.triggerHotkey) {
|
||||||
updateAppHotkey($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()
|
// 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
|
// 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 (!isFocused) {
|
||||||
if ($appConfig.hideOnBlur) {
|
if ($appConfig.hideOnBlur && !$appState.lockHideOnBlur) {
|
||||||
win.hide()
|
win.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,18 +90,18 @@
|
|||||||
extensions.init()
|
extensions.init()
|
||||||
unlisteners.push(
|
unlisteners.push(
|
||||||
await listenToRecordExtensionProcessEvent(async (event) => {
|
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)
|
winExtMap.registerProcess(event.payload.windowLabel, event.payload.pid)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
unlisteners.push(
|
unlisteners.push(
|
||||||
await listenToKillProcessEvent((event) => {
|
await listenToKillProcessEvent((event) => {
|
||||||
console.log("kill process event", event)
|
debug(`kill process event ${event.payload.pid}`)
|
||||||
winExtMap.unregisterProcess(event.payload.pid)
|
winExtMap.unregisterProcess(event.payload.pid)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
getCurrentWebviewWindow().show()
|
data.win?.show().then(() => data.win?.setFocus())
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { getExtensionsFolder, IS_IN_TAURI } from "@/constants"
|
import { getExtensionsFolder, IS_IN_TAURI } from "@/constants"
|
||||||
|
import * as path from "@tauri-apps/api/path"
|
||||||
import { error } from "@tauri-apps/plugin-log"
|
import { error } from "@tauri-apps/plugin-log"
|
||||||
|
import { setStoreCollectionPath } from "@tauri-store/svelte"
|
||||||
import type { LayoutLoad } from "./$types"
|
import type { LayoutLoad } from "./$types"
|
||||||
|
|
||||||
export const load: LayoutLoad = async () => {
|
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 }
|
||||||
}
|
}
|
||||||
|
@ -2,38 +2,51 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { commandLaunchers } from "@/cmds"
|
import { commandLaunchers } from "@/cmds"
|
||||||
import { builtinCmds } from "@/cmds/builtin"
|
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 { i18n } from "@/i18n"
|
||||||
import * as m from "@/paraglide/messages"
|
import * as m from "@/paraglide/messages"
|
||||||
import {
|
import {
|
||||||
appConfig,
|
appConfig,
|
||||||
appConfigLoaded,
|
appConfigLoaded,
|
||||||
|
appsFiltered,
|
||||||
appState,
|
appState,
|
||||||
devStoreExts,
|
devSearchExtCmds,
|
||||||
installedStoreExts,
|
devStoreExtCmds,
|
||||||
quickLinks
|
quickLinksFiltered,
|
||||||
|
storeExtCmds,
|
||||||
|
storeSearchExtCmds
|
||||||
} from "@/stores"
|
} from "@/stores"
|
||||||
import { cmdQueries } from "@/stores/cmdQuery"
|
import { cmdQueries } from "@/stores/cmdQuery"
|
||||||
import { isKeyboardEventFromInputElement } from "@/utils/dom"
|
import { isKeyboardEventFromInputElement } from "@/utils/dom"
|
||||||
import Icon from "@iconify/svelte"
|
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 { Button, Command, DropdownMenu } from "@kksh/svelte5"
|
||||||
import {
|
import {
|
||||||
BuiltinCmds,
|
BuiltinCmds,
|
||||||
CustomCommandInput,
|
CustomCommandInput,
|
||||||
ExtCmdsGroup,
|
ExtCmds,
|
||||||
GlobalCommandPaletteFooter,
|
GlobalCommandPaletteFooter,
|
||||||
QuickLinks,
|
QuickLinks,
|
||||||
SystemCmds
|
SystemCmds
|
||||||
} from "@kksh/ui/main"
|
} from "@kksh/ui/main"
|
||||||
import type { CmdValue } from "@kksh/ui/types"
|
import { cn } from "@kksh/ui/utils"
|
||||||
import { cn, commandScore } from "@kksh/ui/utils"
|
import { Ext } from "@kunkunapi/src/models/extension"
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
|
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
|
||||||
import { getCurrentWindow, Window } from "@tauri-apps/api/window"
|
import { getCurrentWindow, Window } from "@tauri-apps/api/window"
|
||||||
|
import { platform } from "@tauri-apps/plugin-os"
|
||||||
import { exit } from "@tauri-apps/plugin-process"
|
import { exit } from "@tauri-apps/plugin-process"
|
||||||
import { goto } from "$app/navigation"
|
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 { onMount } from "svelte"
|
||||||
|
import { Inspect } from "svelte-inspect-value"
|
||||||
|
import * as v from "valibot"
|
||||||
|
|
||||||
const win = getCurrentWindow()
|
const win = getCurrentWindow()
|
||||||
let inputEle: HTMLInputElement | null = $state(null)
|
let inputEle: HTMLInputElement | null = $state(null)
|
||||||
@ -53,12 +66,16 @@
|
|||||||
if (splashscreenWin) {
|
if (splashscreenWin) {
|
||||||
splashscreenWin.close()
|
splashscreenWin.close()
|
||||||
}
|
}
|
||||||
win.show()
|
win.show().then(() => win.setFocus())
|
||||||
})
|
})
|
||||||
win.onFocusChanged(({ payload: focused }) => {
|
win.onFocusChanged(({ payload: focused }) => {
|
||||||
if (focused) {
|
if (focused) {
|
||||||
win.show()
|
win
|
||||||
|
.show()
|
||||||
|
.then(() => win.setFocus())
|
||||||
|
.finally(() => {
|
||||||
inputEle?.focus()
|
inputEle?.focus()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
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
|
// 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
|
// to keep track of the loading status
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
|
console.log("appConfig.get().onBoarded", appConfig.get().onBoarded)
|
||||||
if (!appConfig.get().onBoarded) {
|
if (!appConfig.get().onBoarded) {
|
||||||
|
setTimeout(() => {
|
||||||
goto(i18n.resolveRoute("/app/help/onboarding"))
|
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
|
<Command.Root
|
||||||
class={cn("h-screen rounded-lg border shadow-md")}
|
class={cn("h-screen rounded-lg shadow-md")}
|
||||||
bind:value={$appState.highlightedCmd}
|
bind:value={$appState.highlightedCmd}
|
||||||
filter={(value, search, keywords) => {
|
shouldFilter={false}
|
||||||
return commandScore(
|
|
||||||
value.startsWith("{") ? (JSON.parse(value) as CmdValue).cmdName : value,
|
|
||||||
search,
|
|
||||||
keywords
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
loop
|
loop
|
||||||
>
|
>
|
||||||
<CustomCommandInput
|
<CustomCommandInput
|
||||||
@ -137,7 +162,7 @@
|
|||||||
<DropdownMenu.Trigger>
|
<DropdownMenu.Trigger>
|
||||||
<Button variant="outline" size="icon"><EllipsisVerticalIcon /></Button>
|
<Button variant="outline" size="icon"><EllipsisVerticalIcon /></Button>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content class="w-80">
|
<DropdownMenu.Content class="w-fit min-w-80">
|
||||||
<DropdownMenu.Group>
|
<DropdownMenu.Group>
|
||||||
<DropdownMenu.Item onclick={() => exit()}>
|
<DropdownMenu.Item onclick={() => exit()}>
|
||||||
<CircleXIcon class="h-4 w-4 text-red-500" />
|
<CircleXIcon class="h-4 w-4 text-red-500" />
|
||||||
@ -156,18 +181,27 @@
|
|||||||
<DropdownMenu.Item onclick={toggleDevTools}>
|
<DropdownMenu.Item onclick={toggleDevTools}>
|
||||||
<Icon icon="mingcute:code-fill" class="mr-2 h-5 w-5 text-green-500" />
|
<Icon icon="mingcute:code-fill" class="mr-2 h-5 w-5 text-green-500" />
|
||||||
{m.home_command_input_dropdown_toggle_devtools()}
|
{m.home_command_input_dropdown_toggle_devtools()}
|
||||||
<DropdownMenu.Shortcut
|
<DropdownMenu.Shortcut>
|
||||||
><span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+I</span
|
<span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+I </span>
|
||||||
></DropdownMenu.Shortcut
|
</DropdownMenu.Shortcut>
|
||||||
>
|
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item onclick={() => location.reload()}>
|
<DropdownMenu.Item onclick={() => location.reload()}>
|
||||||
<RefreshCcwIcon class="mr-2 h-4 w-4 text-green-500" />
|
<RefreshCcwIcon class="mr-2 h-4 w-4 text-green-500" />
|
||||||
{m.home_command_input_dropdown_reload_window()}
|
{m.home_command_input_dropdown_reload_window()}
|
||||||
<DropdownMenu.Shortcut
|
<DropdownMenu.Shortcut>
|
||||||
><span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+R</span
|
<span class="flex items-center">⌃+<ArrowBigUpIcon class="h-4 w-4" />+R </span>
|
||||||
></DropdownMenu.Shortcut
|
</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>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
@ -187,27 +221,37 @@
|
|||||||
</CustomCommandInput>
|
</CustomCommandInput>
|
||||||
<Command.List class="max-h-screen grow">
|
<Command.List class="max-h-screen grow">
|
||||||
<Command.Empty data-tauri-drag-region>No results found.</Command.Empty>
|
<Command.Empty data-tauri-drag-region>No results found.</Command.Empty>
|
||||||
{#if $appConfig.extensionsInstallDir && $devStoreExts.length > 0}
|
{#if $devSearchExtCmds.length > 0}
|
||||||
<ExtCmdsGroup
|
<ExtCmds
|
||||||
extensions={$devStoreExts}
|
|
||||||
heading={m.command_group_heading_dev_ext()}
|
heading={m.command_group_heading_dev_ext()}
|
||||||
|
extCmds={$devSearchExtCmds}
|
||||||
|
hmr={$appConfig.hmr}
|
||||||
isDev={true}
|
isDev={true}
|
||||||
onExtCmdSelect={commandLaunchers.onExtCmdSelect}
|
onExtCmdSelect={commandLaunchers.onExtCmdSelect}
|
||||||
hmr={$appConfig.hmr}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $appConfig.extensionsInstallDir && $installedStoreExts.length > 0}
|
{#if $storeSearchExtCmds.length > 0}
|
||||||
<ExtCmdsGroup
|
<ExtCmds
|
||||||
extensions={$installedStoreExts}
|
|
||||||
heading={m.command_group_heading_ext()}
|
heading={m.command_group_heading_ext()}
|
||||||
isDev={false}
|
extCmds={$storeSearchExtCmds}
|
||||||
hmr={false}
|
hmr={false}
|
||||||
|
isDev={false}
|
||||||
onExtCmdSelect={commandLaunchers.onExtCmdSelect}
|
onExtCmdSelect={commandLaunchers.onExtCmdSelect}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<QuickLinks quickLinks={$quickLinks} />
|
{#if $builtinCmds.length > 0}
|
||||||
<BuiltinCmds builtinCmds={$builtinCmds} />
|
<BuiltinCmds builtinCmds={$builtinCmds} />
|
||||||
<SystemCmds {systemCommands} />
|
{/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>
|
</Command.List>
|
||||||
<GlobalCommandPaletteFooter />
|
<GlobalCommandPaletteFooter />
|
||||||
</Command.Root>
|
</Command.Root>
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goBack, goHome } from "@/utils/route"
|
import { hideAndPaste } from "@/utils/hotkey"
|
||||||
import { listenToNewClipboardItem } from "@/utils/tauri-events"
|
import { goHome } from "@/utils/route"
|
||||||
|
import { listenToNewClipboardItem, listenToWindowFocus } from "@/utils/tauri-events"
|
||||||
import Icon from "@iconify/svelte"
|
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 { SearchModeEnum, SQLSortOrderEnum, type ExtData } from "@kksh/api/models"
|
||||||
|
import { db } from "@kksh/drizzle"
|
||||||
import { Button, Command, Resizable } from "@kksh/svelte5"
|
import { Button, Command, Resizable } from "@kksh/svelte5"
|
||||||
import { Constants } from "@kksh/ui"
|
import { Constants } from "@kksh/ui"
|
||||||
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"
|
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"
|
||||||
import type { UnlistenFn } from "@tauri-apps/api/event"
|
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 { ArrowLeft, FileQuestionIcon, ImageIcon, LetterTextIcon } from "lucide-svelte"
|
||||||
import { onDestroy, onMount, type Snippet } from "svelte"
|
import { onDestroy, onMount, type Snippet } from "svelte"
|
||||||
import { toast } from "svelte-sonner"
|
import { toast } from "svelte-sonner"
|
||||||
import clipboard from "tauri-plugin-clipboard-api"
|
import clipboard from "tauri-plugin-clipboard-api"
|
||||||
import ContentPreview from "./content-preview.svelte"
|
import ContentPreview from "./content-preview.svelte"
|
||||||
|
|
||||||
|
const _platform = platform()
|
||||||
|
let inputEle = $state<HTMLInputElement | null>(null)
|
||||||
|
const curWin = getCurrentWebviewWindow()
|
||||||
let searchTerm = $state("")
|
let searchTerm = $state("")
|
||||||
let clipboardHistoryList = $state<ExtData[]>([])
|
let clipboardHistoryList = $state<ExtData[]>([])
|
||||||
let highlightedItemValue = $state<string>("")
|
let highlightedItemValue = $state<string>("")
|
||||||
let highlighted = $state<ExtData | null>(null)
|
let highlighted = $state<ExtData | null>(null)
|
||||||
let unlistenClipboard = $state<UnlistenFn | null>(null)
|
let unlistenClipboard = $state<UnlistenFn | null>(null)
|
||||||
|
let unlistenFocusEvt = $state<UnlistenFn | null>(null)
|
||||||
let isScrolling = $state(false)
|
let isScrolling = $state(false)
|
||||||
let page = $state(1)
|
let page = $state(1)
|
||||||
|
|
||||||
@ -67,10 +75,19 @@
|
|||||||
}).then((unlisten) => {
|
}).then((unlisten) => {
|
||||||
unlistenClipboard = unlisten
|
unlistenClipboard = unlisten
|
||||||
})
|
})
|
||||||
|
|
||||||
|
listenToWindowFocus(async () => {
|
||||||
|
if (inputEle) {
|
||||||
|
inputEle.focus()
|
||||||
|
}
|
||||||
|
}).then((unlisten) => {
|
||||||
|
unlistenFocusEvt = unlisten
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
unlistenClipboard?.()
|
unlistenClipboard?.()
|
||||||
|
unlistenFocusEvt?.()
|
||||||
})
|
})
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@ -155,12 +172,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onItemSelected(dataId: number) {
|
function writeToClipboard(data: ExtData) {
|
||||||
// fetch data from db
|
if (!data.data) {
|
||||||
db.getExtensionDataById(dataId)
|
toast.warning("No data found")
|
||||||
.then((data) => {
|
return Promise.reject(new Error("No data found"))
|
||||||
if (!data || !data.data) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
const dataType = data?.dataType as ClipboardContentType
|
const dataType = data?.dataType as ClipboardContentType
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
@ -169,15 +184,29 @@
|
|||||||
case "Image":
|
case "Image":
|
||||||
return clipboard.writeImageBase64(data.data)
|
return clipboard.writeImageBase64(data.data)
|
||||||
case "Html":
|
case "Html":
|
||||||
return clipboard.writeHtml(data.data)
|
return clipboard.writeHtmlAndText(data.data, data.searchText ?? data.data)
|
||||||
case "Rtf":
|
case "Rtf":
|
||||||
return clipboard.writeRtf(data.data)
|
return clipboard.writeRtf(data.data)
|
||||||
default:
|
default:
|
||||||
return Promise.reject(new Error("Unsupported data type: " + dataType))
|
return Promise.reject(new Error("Unsupported data type: " + dataType))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onItemSelected(dataId: number) {
|
||||||
|
db.getExtensionDataById(dataId)
|
||||||
|
.then((data) => {
|
||||||
|
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"))
|
.then(() => toast.success("Copied to clipboard"))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
toast.error("Failed to fetch data from db", {
|
toast.error("Failed to fetch data from db", {
|
||||||
description: err.message
|
description: err.message
|
||||||
})
|
})
|
||||||
@ -219,6 +248,7 @@
|
|||||||
autofocus
|
autofocus
|
||||||
placeholder="Type a command or search..."
|
placeholder="Type a command or search..."
|
||||||
leftSlot={leftSlot as Snippet}
|
leftSlot={leftSlot as Snippet}
|
||||||
|
bind:ref={inputEle}
|
||||||
bind:value={searchTerm}
|
bind:value={searchTerm}
|
||||||
/>
|
/>
|
||||||
<Resizable.PaneGroup direction="horizontal" class="w-full rounded-lg">
|
<Resizable.PaneGroup direction="horizontal" class="w-full rounded-lg">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn } from "@/utils"
|
import { cn } from "@/utils"
|
||||||
import { db } from "@kksh/api/commands"
|
|
||||||
import type { ExtData } from "@kksh/api/models"
|
import type { ExtData } from "@kksh/api/models"
|
||||||
|
import { db } from "@kksh/drizzle"
|
||||||
import { Resizable, Separator } from "@kksh/svelte5"
|
import { Resizable, Separator } from "@kksh/svelte5"
|
||||||
import { convertFileSrc } from "@tauri-apps/api/core"
|
import { convertFileSrc } from "@tauri-apps/api/core"
|
||||||
import DOMPurify from "dompurify"
|
import DOMPurify from "dompurify"
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
import * as userInput from "tauri-plugin-user-input-api"
|
import * as userInput from "tauri-plugin-user-input-api"
|
||||||
import { type InputEvent } from "tauri-plugin-user-input-api"
|
import { type InputEvent } from "tauri-plugin-user-input-api"
|
||||||
|
|
||||||
|
let { data } = $props()
|
||||||
|
|
||||||
const SymbolMap = {
|
const SymbolMap = {
|
||||||
Alt: "⎇",
|
Alt: "⎇",
|
||||||
AltGr: "⌥",
|
AltGr: "⌥",
|
||||||
@ -97,10 +99,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const win = getCurrentWebviewWindow()
|
data.win?.show().then(() => data.win?.setFocus())
|
||||||
if (win) {
|
|
||||||
win.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
userInput.setEventTypes([userInput.EventTypeEnum.KeyPress, userInput.EventTypeEnum.KeyRelease])
|
userInput.setEventTypes([userInput.EventTypeEnum.KeyPress, userInput.EventTypeEnum.KeyRelease])
|
||||||
userInput.startListening((evt: InputEvent) => {
|
userInput.startListening((evt: InputEvent) => {
|
||||||
|
@ -8,23 +8,24 @@
|
|||||||
import * as clipboard from "tauri-plugin-clipboard-api"
|
import * as clipboard from "tauri-plugin-clipboard-api"
|
||||||
|
|
||||||
let image = $state<string | null>(null)
|
let image = $state<string | null>(null)
|
||||||
const appWin = getCurrentWebviewWindow()
|
let { data } = $props()
|
||||||
let originalSize = $state<{ width: number; height: number } | null>(null)
|
let originalSize = $state<{ width: number; height: number } | null>(null)
|
||||||
let originalScaleFactor = $state<number | null>(null)
|
let originalScaleFactor = $state<number | null>(null)
|
||||||
let scale = $state<number>(1)
|
let scale = $state<number>(1)
|
||||||
let currentSize = $derived(
|
let currentSize = $derived(
|
||||||
originalSize ? { width: originalSize.width * scale, height: originalSize.height * scale } : null
|
originalSize ? { width: originalSize.width * scale, height: originalSize.height * scale } : null
|
||||||
)
|
)
|
||||||
|
const win = getCurrentWebviewWindow()
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (currentSize) {
|
if (currentSize) {
|
||||||
appWin.setSize(new LogicalSize(currentSize.width, currentSize.height))
|
win.setSize(new LogicalSize(currentSize.width, currentSize.height))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getWindowSize() {
|
async function getWindowSize() {
|
||||||
const size = await appWin.outerSize()
|
const size = await win.outerSize()
|
||||||
const scaleFactor = originalScaleFactor ?? (await appWin.scaleFactor())
|
const scaleFactor = originalScaleFactor ?? (await win.scaleFactor())
|
||||||
const logicalSize = size.toLogical(scaleFactor)
|
const logicalSize = size.toLogical(scaleFactor)
|
||||||
return { logicalSize, scaleFactor }
|
return { logicalSize, scaleFactor }
|
||||||
}
|
}
|
||||||
@ -36,7 +37,7 @@
|
|||||||
image = b64
|
image = b64
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
appWin.show()
|
data.win?.show().then(() => data.win?.setFocus())
|
||||||
})
|
})
|
||||||
const { logicalSize, scaleFactor } = await getWindowSize()
|
const { logicalSize, scaleFactor } = await getWindowSize()
|
||||||
originalSize = { width: logicalSize.width, height: logicalSize.height }
|
originalSize = { width: logicalSize.width, height: logicalSize.height }
|
||||||
@ -67,13 +68,13 @@
|
|||||||
<svelte:window
|
<svelte:window
|
||||||
on:keydown={(e) => {
|
on:keydown={(e) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
appWin.close()
|
win.close()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button size="icon" variant="ghost" class="fixed left-2 top-2" onclick={() => appWin.close()}
|
<Button size="icon" variant="ghost" class="fixed left-2 top-2" onclick={() => win.close()}>
|
||||||
><CircleX /></Button
|
<CircleX />
|
||||||
>
|
</Button>
|
||||||
<main class="z-50 h-screen w-screen overflow-hidden" data-tauri-drag-region>
|
<main class="z-50 h-screen w-screen overflow-hidden" data-tauri-drag-region>
|
||||||
{#if image}
|
{#if image}
|
||||||
<img
|
<img
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getExtensionsFolder } from "@/constants"
|
import { getExtensionsFolder } from "@/constants"
|
||||||
import { appState, extensions } from "@/stores"
|
import { appState, extensions } from "@/stores"
|
||||||
import { supabaseAPI } from "@/supabase"
|
import { keys } from "@/stores/keys"
|
||||||
import { goBackOnEscapeClearSearchTerm, goHomeOnEscapeClearSearchTerm } from "@/utils/key"
|
|
||||||
import { goBack, goHome } from "@/utils/route"
|
import { goBack, goHome } from "@/utils/route"
|
||||||
import { SBExt } from "@kksh/supabase/models"
|
import { Action as ActionSchema, ExtensionStoreListItem, ExtPublish } from "@kksh/api/models"
|
||||||
import type { ExtPublishMetadata } from "@kksh/supabase/models"
|
import { Action } from "@kksh/api/ui"
|
||||||
import { type Tables } from "@kksh/supabase/types"
|
import {
|
||||||
|
getExtensionsLatestPublishByIdentifier,
|
||||||
|
postExtensionsIncrementDownloads
|
||||||
|
} from "@kksh/sdk"
|
||||||
import { Button, Command } from "@kksh/svelte5"
|
import { Button, Command } from "@kksh/svelte5"
|
||||||
import { Constants } from "@kksh/ui"
|
import { Constants } from "@kksh/ui"
|
||||||
import { ExtListItem } from "@kksh/ui/extension"
|
import { ExtListItem } from "@kksh/ui/extension"
|
||||||
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"
|
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"
|
||||||
|
import { platform } from "@tauri-apps/plugin-os"
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from "$app/navigation"
|
||||||
import { ArrowLeft } from "lucide-svelte"
|
import { ArrowLeft } from "lucide-svelte"
|
||||||
import type { Snippet } from "svelte"
|
import { onMount, type Snippet } from "svelte"
|
||||||
import { toast } from "svelte-sonner"
|
import { toast } from "svelte-sonner"
|
||||||
|
import type { Action as SvelteAction } from "svelte/action"
|
||||||
import { getInstallExtras } from "./[identifier]/helper.js"
|
import { getInstallExtras } from "./[identifier]/helper.js"
|
||||||
|
|
||||||
let { data } = $props()
|
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 {
|
// function isUpgradeable(item: DbExtItem): boolean {
|
||||||
// if (!item.version) return true // latest extensions always have version, this check should be removed later
|
// 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}`)
|
goto(`./store/${ext.identifier}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onExtItemUpgrade(ext: SBExt) {
|
async function onExtItemUpgrade(ext: ExtensionStoreListItem) {
|
||||||
const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
|
const { data, error, response } = await getExtensionsLatestPublishByIdentifier({
|
||||||
if (res.error)
|
path: {
|
||||||
return toast.error("Fail to get latest extension", {
|
identifier: ext.identifier
|
||||||
description: res.error.message
|
}
|
||||||
})
|
})
|
||||||
const tarballUrl = res.data.tarball_path.startsWith("http")
|
if (error)
|
||||||
? res.data.tarball_path
|
return toast.error("Fail to get latest extension", {
|
||||||
: supabaseAPI.translateExtensionFilePathToUrl(res.data.tarball_path)
|
description: error.error
|
||||||
const installExtras = await getInstallExtras(
|
})
|
||||||
res.data as Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
|
const installExtras = await getInstallExtras(data?.metadata)
|
||||||
)
|
|
||||||
return extensions
|
return extensions
|
||||||
.upgradeStoreExtension(ext.identifier, tarballUrl, installExtras)
|
.upgradeStoreExtension(ext.identifier, data.tarball_path, installExtras)
|
||||||
.then((newExt) => {
|
.then((newExt) => {
|
||||||
toast.success(`${ext.name} Upgraded to ${newExt.version}`)
|
toast.success(`${ext.name} Upgraded to ${newExt.version}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onExtItemInstall(ext: SBExt) {
|
async function onExtItemInstall(ext: ExtensionStoreListItem) {
|
||||||
const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
|
const { data, error, response } = await getExtensionsLatestPublishByIdentifier({
|
||||||
if (res.error)
|
path: {
|
||||||
|
identifier: ext.identifier
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (error)
|
||||||
return toast.error("Fail to get latest extension", {
|
return toast.error("Fail to get latest extension", {
|
||||||
description: res.error.message
|
description: error.error
|
||||||
})
|
})
|
||||||
|
|
||||||
const tarballUrl = res.data.tarball_path.startsWith("http")
|
const installExtras = await getInstallExtras(data?.metadata)
|
||||||
? res.data.tarball_path
|
|
||||||
: supabaseAPI.translateExtensionFilePathToUrl(res.data.tarball_path)
|
|
||||||
const installExtras = await getInstallExtras(
|
|
||||||
res.data as Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
|
|
||||||
)
|
|
||||||
const installDir = await getExtensionsFolder()
|
const installDir = await getExtensionsFolder()
|
||||||
return extensions
|
return extensions
|
||||||
.installFromTarballUrl(tarballUrl, installDir, installExtras)
|
.installFromTarballUrl(data.tarball_path, installDir, installExtras)
|
||||||
.then(() => toast.success(`Plugin ${ext.name} Installed`))
|
.then(() => toast.success(`Plugin ${ext.name} Installed`))
|
||||||
.then(() =>
|
.then(() =>
|
||||||
supabaseAPI.incrementDownloads({
|
postExtensionsIncrementDownloads({
|
||||||
|
body: {
|
||||||
identifier: ext.identifier,
|
identifier: ext.identifier,
|
||||||
version: ext.version
|
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>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={goHomeOnEscapeClearSearchTerm} />
|
<svelte:window {onkeydown} />
|
||||||
{#snippet leftSlot()}
|
{#snippet leftSlot()}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -91,12 +176,27 @@
|
|||||||
<ArrowLeft class="size-4" />
|
<ArrowLeft class="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
{/snippet}
|
{/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
|
<CustomCommandInput
|
||||||
|
bind:ref={listviewInputRef}
|
||||||
autofocus
|
autofocus
|
||||||
placeholder="Type a command or search..."
|
placeholder="Type / to focus"
|
||||||
leftSlot={leftSlot as Snippet}
|
leftSlot={leftSlot as Snippet}
|
||||||
bind:value={$appState.searchTerm}
|
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.List class="max-h-screen grow">
|
||||||
<Command.Empty>No results found.</Command.Empty>
|
<Command.Empty>No results found.</Command.Empty>
|
||||||
@ -105,11 +205,40 @@
|
|||||||
{ext}
|
{ext}
|
||||||
installedVersion={$installedExtsMap[ext.identifier]}
|
installedVersion={$installedExtsMap[ext.identifier]}
|
||||||
isUpgradable={!!$upgradableExpsMap[ext.identifier]}
|
isUpgradable={!!$upgradableExpsMap[ext.identifier]}
|
||||||
onSelect={() => onExtItemSelected(ext)}
|
onSelect={() => {
|
||||||
|
onExtItemSelected(ext)
|
||||||
|
}}
|
||||||
onUpgrade={() => onExtItemUpgrade(ext)}
|
onUpgrade={() => onExtItemUpgrade(ext)}
|
||||||
onInstall={() => onExtItemInstall(ext)}
|
onInstall={() => onExtItemInstall(ext)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</Command.List>
|
</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>
|
</Command.Root>
|
||||||
|
@ -1,43 +1,47 @@
|
|||||||
import { appConfig, extensions, installedStoreExts } from "@/stores"
|
import { appConfig, appState, extensions, installedStoreExts } from "@/stores"
|
||||||
import { supabaseAPI } from "@/supabase"
|
import { goHome } from "@/utils/route"
|
||||||
import type { ExtPackageJsonExtra } from "@kksh/api/models"
|
// import { supabaseAPI } from "@/supabase"
|
||||||
|
import type { ExtensionStoreListItem, ExtPackageJsonExtra } from "@kksh/api/models"
|
||||||
import { isExtPathInDev, isUpgradable } from "@kksh/extension"
|
import { isExtPathInDev, isUpgradable } from "@kksh/extension"
|
||||||
import { SBExt } from "@kksh/supabase/models"
|
import { getExtensionsStoreList } from "@kksh/sdk"
|
||||||
import { error } from "@sveltejs/kit"
|
import { toast } from "svelte-sonner"
|
||||||
import { derived, get, type Readable } from "svelte/store"
|
import { derived, get, type Readable } from "svelte/store"
|
||||||
import type { PageLoad } from "./$types"
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
export const load: PageLoad = async (): Promise<{
|
export const load: PageLoad = (): Promise<{
|
||||||
storeExtList: SBExt[]
|
storeExtList: ExtensionStoreListItem[]
|
||||||
installedStoreExts: Readable<ExtPackageJsonExtra[]>
|
installedStoreExts: Readable<ExtPackageJsonExtra[]>
|
||||||
installedExtsMap: Readable<Record<string, string>>
|
installedExtsMap: Readable<Record<string, string>>
|
||||||
upgradableExpsMap: Readable<Record<string, boolean>>
|
upgradableExpsMap: Readable<Record<string, boolean>>
|
||||||
}> => {
|
}> => {
|
||||||
const storeExtList = await supabaseAPI.getExtList()
|
appState.setFullScreenLoading(true)
|
||||||
// map identifier to extItem
|
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 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) =>
|
const installedExtsMap = derived(installedStoreExts, ($exts) =>
|
||||||
Object.fromEntries($exts.map((ext) => [ext.kunkun.identifier, ext.version]))
|
Object.fromEntries($exts.map((ext) => [ext.kunkun.identifier, ext.version]))
|
||||||
)
|
)
|
||||||
const upgradableExpsMap = derived(installedStoreExts, ($exts) =>
|
const upgradableExpsMap = derived(installedStoreExts, ($exts) =>
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
$exts.map((ext) => {
|
$exts.map((ext) => {
|
||||||
const dbExt: SBExt | undefined = storeExtsMap[ext.kunkun.identifier]
|
const dbExt: ExtensionStoreListItem | undefined = storeExtsMap[ext.kunkun.identifier]
|
||||||
return [ext.kunkun.identifier, dbExt ? isUpgradable(dbExt, ext.version) : false]
|
return [ext.kunkun.identifier, dbExt ? isUpgradable(dbExt, ext.version) : false]
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
storeExtList,
|
storeExtList,
|
||||||
installedStoreExts,
|
installedStoreExts,
|
||||||
installedExtsMap,
|
installedExtsMap,
|
||||||
upgradableExpsMap
|
upgradableExpsMap
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
appState.setFullScreenLoading(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,8 @@
|
|||||||
import { getExtensionsFolder } from "@/constants.js"
|
import { getExtensionsFolder } from "@/constants.js"
|
||||||
import { i18n } from "@/i18n.js"
|
import { i18n } from "@/i18n.js"
|
||||||
import { extensions, installedStoreExts } from "@/stores/extensions.js"
|
import { extensions, installedStoreExts } from "@/stores/extensions.js"
|
||||||
import { supabaseAPI } from "@/supabase"
|
import { DBExtension, ExtensionStoreListItem, ExtPackageJson, ExtPublish } from "@kksh/api/models"
|
||||||
import { goBack } from "@/utils/route.js"
|
import { postExtensionsIncrementDownloads } from "@kksh/sdk"
|
||||||
import { ExtPackageJson } from "@kksh/api/models"
|
|
||||||
import { ExtPublishMetadata } from "@kksh/supabase/models"
|
|
||||||
import type { Tables } from "@kksh/supabase/types"
|
|
||||||
import { Button } from "@kksh/svelte5"
|
import { Button } from "@kksh/svelte5"
|
||||||
import { cn } from "@kksh/svelte5/utils"
|
import { cn } from "@kksh/svelte5/utils"
|
||||||
import { Constants } from "@kksh/ui"
|
import { Constants } from "@kksh/ui"
|
||||||
@ -22,10 +19,8 @@
|
|||||||
import { getInstallExtras } from "./helper"
|
import { getInstallExtras } from "./helper"
|
||||||
|
|
||||||
const { data } = $props()
|
const { data } = $props()
|
||||||
const extPublish: Tables<"ext_publish"> & { metadata: ExtPublishMetadata } = $derived(
|
const extPublish = $derived(data.extPublish)
|
||||||
data.extPublish
|
const ext = $derived(data.ext)
|
||||||
)
|
|
||||||
const ext: Tables<"extensions"> = $derived(data.ext)
|
|
||||||
const manifest = $derived(data.manifest)
|
const manifest = $derived(data.manifest)
|
||||||
const installedExt = storeDerived(installedStoreExts, ($e) => {
|
const installedExt = storeDerived(installedStoreExts, ($e) => {
|
||||||
return $e.find((e) => e.kunkun.identifier === extPublish.identifier)
|
return $e.find((e) => e.kunkun.identifier === extPublish.identifier)
|
||||||
@ -77,26 +72,29 @@
|
|||||||
}, 500)
|
}, 500)
|
||||||
})
|
})
|
||||||
|
|
||||||
const demoImages = $derived(
|
const demoImages = $derived(extPublish.demo_images)
|
||||||
extPublish.demo_images.map((src) => supabaseAPI.translateExtensionFilePathToUrl(src))
|
|
||||||
)
|
|
||||||
|
|
||||||
async function onInstallSelected() {
|
async function onInstallSelected() {
|
||||||
loading.install = true
|
loading.install = true
|
||||||
const tarballUrl = extPublish.tarball_path.startsWith("http")
|
const installExtras = await getInstallExtras(extPublish.metadata)
|
||||||
? extPublish.tarball_path
|
|
||||||
: supabaseAPI.translateExtensionFilePathToUrl(extPublish.tarball_path)
|
|
||||||
const installExtras = await getInstallExtras(extPublish)
|
|
||||||
const installDir = await getExtensionsFolder()
|
const installDir = await getExtensionsFolder()
|
||||||
return extensions
|
return extensions
|
||||||
.installFromTarballUrl(tarballUrl, installDir, installExtras)
|
.installFromTarballUrl(extPublish.tarball_path, installDir, installExtras)
|
||||||
.then(() => toast.success(`Plugin ${extPublish.name} Installed`))
|
.then(() => toast.success(`Plugin ${extPublish.name} Installed`))
|
||||||
.then((loadedExt) => {
|
.then((loadedExt) => {
|
||||||
info(`Successfully installed ${extPublish.name}`)
|
info(`Successfully installed ${extPublish.name}`)
|
||||||
supabaseAPI.incrementDownloads({
|
postExtensionsIncrementDownloads({
|
||||||
|
body: {
|
||||||
identifier: extPublish.identifier,
|
identifier: extPublish.identifier,
|
||||||
version: extPublish.version
|
version: extPublish.version
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
.then(({ error }) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
showBtn.install = false
|
showBtn.install = false
|
||||||
showBtn.uninstall = true
|
showBtn.uninstall = true
|
||||||
})
|
})
|
||||||
@ -111,9 +109,8 @@
|
|||||||
|
|
||||||
function onUpgradeSelected() {
|
function onUpgradeSelected() {
|
||||||
loading.upgrade = true
|
loading.upgrade = true
|
||||||
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(extPublish.tarball_path)
|
|
||||||
return extensions
|
return extensions
|
||||||
.upgradeStoreExtension(extPublish.identifier, tarballUrl)
|
.upgradeStoreExtension(extPublish.identifier, extPublish.tarball_path)
|
||||||
.then((newExt) => {
|
.then((newExt) => {
|
||||||
toast.success(
|
toast.success(
|
||||||
`${extPublish.name} Upgraded from ${$installedExt?.version} to ${newExt.version}`
|
`${extPublish.name} Upgraded from ${$installedExt?.version} to ${newExt.version}`
|
||||||
|
@ -1,53 +1,57 @@
|
|||||||
import { extensions } from "@/stores"
|
import { appState } from "@/stores"
|
||||||
import { supabaseAPI } from "@/supabase"
|
import { DBExtension, ExtPublish, KunkunExtManifest } from "@kksh/api/models"
|
||||||
import { KunkunExtManifest, type ExtPackageJsonExtra } from "@kksh/api/models"
|
import { getExtensionsByIdentifier, getExtensionsLatestPublishByIdentifier } from "@kksh/sdk"
|
||||||
import { ExtPublishMetadata } from "@kksh/supabase/models"
|
|
||||||
import type { Tables } from "@kksh/supabase/types"
|
|
||||||
import { error } from "@sveltejs/kit"
|
import { error } from "@sveltejs/kit"
|
||||||
import { toast } from "svelte-sonner"
|
|
||||||
import * as v from "valibot"
|
import * as v from "valibot"
|
||||||
import type { PageLoad } from "./$types"
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
export const load: PageLoad = async ({
|
export const load: PageLoad = ({
|
||||||
params
|
params
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
extPublish: Tables<"ext_publish"> & { metadata: ExtPublishMetadata }
|
extPublish: ExtPublish
|
||||||
ext: Tables<"extensions">
|
ext: DBExtension
|
||||||
manifest: KunkunExtManifest
|
manifest: KunkunExtManifest
|
||||||
params: {
|
params: {
|
||||||
identifier: string
|
identifier: string
|
||||||
}
|
}
|
||||||
}> => {
|
}> => {
|
||||||
const { error: dbError, data: extPublish } = await supabaseAPI.getLatestExtPublish(
|
appState.setFullScreenLoading(true)
|
||||||
params.identifier
|
return getExtensionsLatestPublishByIdentifier({
|
||||||
)
|
path: {
|
||||||
const metadataParse = v.safeParse(ExtPublishMetadata, extPublish?.metadata ?? {})
|
identifier: params.identifier
|
||||||
if (dbError) {
|
}
|
||||||
return error(400, {
|
})
|
||||||
message: dbError.message
|
.then(async ({ data: extPublish, error: err, response }) => {
|
||||||
|
if (err || !extPublish) {
|
||||||
|
return error(response.status, {
|
||||||
|
message: "Failed to get extension publish"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const metadata = metadataParse.success ? metadataParse.output : {}
|
const {
|
||||||
const parseManifest = v.safeParse(KunkunExtManifest, extPublish.manifest)
|
data: ext,
|
||||||
if (!parseManifest.success) {
|
error: extError,
|
||||||
const errMsg = "Invalid extension manifest, you may need to upgrade your app."
|
response: extRes
|
||||||
toast.error(errMsg)
|
} = await getExtensionsByIdentifier({
|
||||||
throw error(400, errMsg)
|
path: {
|
||||||
|
identifier: params.identifier
|
||||||
}
|
}
|
||||||
|
})
|
||||||
const { data: ext, error: extError } = await supabaseAPI.getExtension(params.identifier)
|
if (extError || !ext) {
|
||||||
if (extError) {
|
console.error(extError)
|
||||||
return error(400, {
|
return error(extRes.status, {
|
||||||
message: extError.message
|
message: extError.error || "Failed to get extension"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extPublish: { ...extPublish, metadata },
|
extPublish: v.parse(ExtPublish, extPublish),
|
||||||
ext,
|
ext,
|
||||||
params,
|
manifest: v.parse(KunkunExtManifest, extPublish.manifest),
|
||||||
manifest: parseManifest.output
|
params
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
appState.setFullScreenLoading(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const csr = true
|
export const csr = true
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import type { ExtPublishMetadata } from "@kksh/supabase/models"
|
import type { ExtPublishMetadata } from "@kunkunapi/src/models"
|
||||||
import type { Tables } from "@kksh/supabase/types"
|
|
||||||
|
|
||||||
export async function getInstallExtras(
|
export async function getInstallExtras(extMetadata?: {
|
||||||
ext: Tables<"ext_publish"> & { metadata?: ExtPublishMetadata }
|
sourceType?: string
|
||||||
): Promise<{ overwritePackageJson?: string }> {
|
source?: string
|
||||||
|
}): Promise<{ overwritePackageJson?: string }> {
|
||||||
const extras: { overwritePackageJson?: string } = {}
|
const extras: { overwritePackageJson?: string } = {}
|
||||||
if (ext.metadata?.sourceType) {
|
if (extMetadata?.sourceType) {
|
||||||
if (ext.metadata?.sourceType === "jsr") {
|
if (extMetadata?.sourceType === "jsr") {
|
||||||
if (ext.metadata?.source) {
|
if (extMetadata?.source) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${ext.metadata.source}/package.json`)
|
const res = await fetch(`${extMetadata.source}/package.json`)
|
||||||
const pkgJsonContent = await res.text()
|
const pkgJsonContent = await res.text()
|
||||||
extras.overwritePackageJson = pkgJsonContent
|
extras.overwritePackageJson = pkgJsonContent
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,24 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import DanceTransition from "@/components/dance/dance-transition.svelte"
|
import DanceTransition from "@/components/dance/dance-transition.svelte"
|
||||||
import { i18n } from "@/i18n"
|
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 { goBackOnEscape } from "@/utils/key"
|
||||||
|
import { decideKkrpcSerialization } from "@/utils/kkrpc"
|
||||||
import { goHome } from "@/utils/route"
|
import { goHome } from "@/utils/route"
|
||||||
import { positionToCssStyleString, positionToTailwindClasses } from "@/utils/style"
|
import { positionToCssStyleString, positionToTailwindClasses } from "@/utils/style"
|
||||||
|
import { sleep } from "@/utils/time"
|
||||||
import { isInMainWindow } from "@/utils/window"
|
import { isInMainWindow } from "@/utils/window"
|
||||||
import { db } from "@kksh/api/commands"
|
|
||||||
import { CustomPosition, ThemeColor, type Position } from "@kksh/api/models"
|
import { CustomPosition, ThemeColor, type Position } from "@kksh/api/models"
|
||||||
import {
|
import {
|
||||||
constructJarvisServerAPIWithPermissions,
|
constructJarvisServerAPIWithPermissions,
|
||||||
// exposeApiToWindow,
|
// exposeApiToWindow,
|
||||||
type IApp,
|
type IApp,
|
||||||
type IUiIframe
|
type IUiCustom
|
||||||
} from "@kksh/api/ui"
|
} 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 { Button } from "@kksh/svelte5"
|
||||||
import { cn } from "@kksh/ui/utils"
|
import { cn } from "@kksh/ui/utils"
|
||||||
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
|
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 { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
|
import { info } from "@tauri-apps/plugin-log"
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from "$app/navigation"
|
||||||
import { IframeParentIO, RPCChannel } from "kkrpc/browser"
|
import { IframeParentIO, RPCChannel } from "kkrpc/browser"
|
||||||
import { ArrowLeftIcon, MoveIcon, RefreshCcwIcon, XIcon } from "lucide-svelte"
|
import { ArrowLeftIcon, MoveIcon, RefreshCcwIcon, XIcon } from "lucide-svelte"
|
||||||
@ -27,7 +37,7 @@
|
|||||||
|
|
||||||
let { data }: { data: PageData } = $props()
|
let { data }: { data: PageData } = $props()
|
||||||
const { loadedExt, url, extPath, extInfoInDB } = data
|
const { loadedExt, url, extPath, extInfoInDB } = data
|
||||||
const appWin = getCurrentWindow()
|
let extSpawnedProcesses = $state<number[]>([])
|
||||||
let iframeRef: HTMLIFrameElement
|
let iframeRef: HTMLIFrameElement
|
||||||
let uiControl = $state<{
|
let uiControl = $state<{
|
||||||
iframeLoaded: boolean
|
iframeLoaded: boolean
|
||||||
@ -49,12 +59,12 @@
|
|||||||
transparentBg: false
|
transparentBg: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const iframeUiAPI: IUiIframeServer2 = {
|
const iframeUiAPI: IUiCustomServer2 = {
|
||||||
goBack: async () => {
|
goBack: async () => {
|
||||||
if (isInMainWindow()) {
|
if (isInMainWindow()) {
|
||||||
goto(i18n.resolveRoute("/app/"))
|
goto(i18n.resolveRoute("/app/"))
|
||||||
} else {
|
} else {
|
||||||
appWin.close()
|
data.win?.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hideBackButton: async () => {
|
hideBackButton: async () => {
|
||||||
@ -106,14 +116,33 @@
|
|||||||
|
|
||||||
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
|
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
|
||||||
loadedExt.kunkun.permissions,
|
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 = {
|
const serverAPI2 = {
|
||||||
...serverAPI,
|
...serverAPI,
|
||||||
iframeUi: {
|
iframeUi: {
|
||||||
...serverAPI.iframeUi,
|
...serverAPI.iframeUi,
|
||||||
...iframeUiAPI
|
...iframeUiAPI
|
||||||
} satisfies IUiIframeServer1 & IUiIframeServer2,
|
} satisfies IUiCustomServer1 & IUiCustomServer2,
|
||||||
|
helper: helperAPI,
|
||||||
db: new db.JarvisExtDB(extInfoInDB.extId),
|
db: new db.JarvisExtDB(extInfoInDB.extId),
|
||||||
kv: new db.KV(extInfoInDB.extId),
|
kv: new db.KV(extInfoInDB.extId),
|
||||||
app: {
|
app: {
|
||||||
@ -125,7 +154,7 @@
|
|||||||
if (isInMainWindow()) {
|
if (isInMainWindow()) {
|
||||||
goHome()
|
goHome()
|
||||||
} else {
|
} else {
|
||||||
appWin.close()
|
data.win?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,16 +162,27 @@
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
iframeRef.focus()
|
iframeRef.focus()
|
||||||
uiControl.iframeLoaded = true
|
uiControl.iframeLoaded = true
|
||||||
|
appState.setFullScreenLoading(false)
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
appState.setFullScreenLoading(true)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
appWin.show()
|
data.win?.setFocus()
|
||||||
}, 200)
|
}, 200)
|
||||||
if (iframeRef?.contentWindow) {
|
if (iframeRef?.contentWindow) {
|
||||||
const io = new IframeParentIO(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 {
|
} else {
|
||||||
toast.warning("iframeRef.contentWindow not available")
|
toast.warning("iframeRef.contentWindow not available")
|
||||||
}
|
}
|
||||||
@ -155,7 +195,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
winExtMap.unregisterExtensionFromWindow(appWin.label)
|
winExtMap.unregisterExtensionFromWindow(data.win?.label ?? "")
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -168,7 +208,7 @@
|
|||||||
onclick={onBackBtnClicked}
|
onclick={onBackBtnClicked}
|
||||||
style={`${positionToCssStyleString(uiControl.backBtnPosition)}`}
|
style={`${positionToCssStyleString(uiControl.backBtnPosition)}`}
|
||||||
>
|
>
|
||||||
{#if appWin.label === "main"}
|
{#if data.win?.label === "main"}
|
||||||
<ArrowLeftIcon class="w-4" />
|
<ArrowLeftIcon class="w-4" />
|
||||||
{:else}
|
{:else}
|
||||||
<XIcon class="w-4" />
|
<XIcon class="w-4" />
|
||||||
@ -199,7 +239,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<main class="h-screen">
|
<main class="h-screen">
|
||||||
<DanceTransition delay={300} autoHide={false} show={!uiControl.iframeLoaded} />
|
|
||||||
<iframe
|
<iframe
|
||||||
bind:this={iframeRef}
|
bind:this={iframeRef}
|
||||||
class={cn("h-full", {
|
class={cn("h-full", {
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
|
import { KunkunIframeExtParams } from "@/cmds/ext"
|
||||||
import { i18n } from "@/i18n"
|
import { i18n } from "@/i18n"
|
||||||
import { db, unregisterExtensionWindow } from "@kksh/api/commands"
|
|
||||||
import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models"
|
import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models"
|
||||||
|
import { db } from "@kksh/drizzle"
|
||||||
import { loadExtensionManifestFromDisk } from "@kksh/extension"
|
import { loadExtensionManifestFromDisk } from "@kksh/extension"
|
||||||
|
import { error as svError } from "@sveltejs/kit"
|
||||||
import { join } from "@tauri-apps/api/path"
|
import { join } from "@tauri-apps/api/path"
|
||||||
import { error } from "@tauri-apps/plugin-log"
|
import { error } from "@tauri-apps/plugin-log"
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from "$app/navigation"
|
||||||
import { toast } from "svelte-sonner"
|
import { toast } from "svelte-sonner"
|
||||||
|
import * as v from "valibot"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import type { PageLoad } from "./$types"
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
@ -20,15 +23,35 @@ export const load: PageLoad = async ({
|
|||||||
extInfoInDB: ExtInfoInDB
|
extInfoInDB: ExtInfoInDB
|
||||||
}> => {
|
}> => {
|
||||||
// both query parameter must exist
|
// 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 _extPath = url.searchParams.get("extPath")
|
||||||
const _extUrl = url.searchParams.get("url")
|
const _extUrl = url.searchParams.get("url")
|
||||||
if (!_extPath || !_extUrl) {
|
console.log("_extPath", _extPath)
|
||||||
toast.error("Invalid extension path or url")
|
console.log("_extUrl", _extUrl)
|
||||||
error("Invalid extension path or url")
|
// if (!_extPath || !_extUrl) {
|
||||||
goto(i18n.resolveRoute("/app/"))
|
// toast.error("Invalid extension path or url", {
|
||||||
}
|
// description: `_extPath: ${_extPath}; _extUrl: ${_extUrl}`
|
||||||
const extPath = z.string().parse(_extPath)
|
// })
|
||||||
const extUrl = z.string().parse(_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
|
let _loadedExt: ExtPackageJsonExtra | undefined
|
||||||
try {
|
try {
|
||||||
_loadedExt = await loadExtensionManifestFromDisk(await join(extPath, "package.json"))
|
_loadedExt = await loadExtensionManifestFromDisk(await join(extPath, "package.json"))
|
||||||
|
@ -3,10 +3,21 @@
|
|||||||
import { appState } from "@/stores/appState.js"
|
import { appState } from "@/stores/appState.js"
|
||||||
import { keys } from "@/stores/keys"
|
import { keys } from "@/stores/keys"
|
||||||
import { winExtMap } from "@/stores/winExtMap.js"
|
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 { isInMainWindow } from "@/utils/window.js"
|
||||||
import { db } from "@kksh/api/commands"
|
import {
|
||||||
import { constructJarvisServerAPIWithPermissions, type IApp, type IUiWorker } from "@kksh/api/ui"
|
constructJarvisServerAPIWithPermissions,
|
||||||
|
type IApp,
|
||||||
|
type IUiTemplate
|
||||||
|
} from "@kksh/api/ui"
|
||||||
import {
|
import {
|
||||||
FormNodeNameEnum,
|
FormNodeNameEnum,
|
||||||
FormSchema,
|
FormSchema,
|
||||||
@ -15,44 +26,57 @@
|
|||||||
NodeNameEnum,
|
NodeNameEnum,
|
||||||
toast,
|
toast,
|
||||||
type IComponent,
|
type IComponent,
|
||||||
type WorkerExtension
|
type TemplateUiCommand
|
||||||
} from "@kksh/api/ui/worker"
|
} from "@kksh/api/ui/template"
|
||||||
|
import { db } from "@kksh/drizzle"
|
||||||
|
import { Button, Form } from "@kksh/svelte5"
|
||||||
import { LoadingBar } from "@kksh/ui"
|
import { LoadingBar } from "@kksh/ui"
|
||||||
import { Templates } from "@kksh/ui/extension"
|
import { Templates } from "@kksh/ui/extension"
|
||||||
import { GlobalCommandPaletteFooter } from "@kksh/ui/main"
|
import { GlobalCommandPaletteFooter } from "@kksh/ui/main"
|
||||||
import type { IKunkunFullServerAPI } from "@kunkunapi/src/api/server"
|
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 { 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 { 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 { platform } from "@tauri-apps/plugin-os"
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from "$app/navigation"
|
||||||
import { RPCChannel, WorkerParentIO } from "kkrpc/browser"
|
import { RPCChannel, WorkerParentIO } from "kkrpc/browser"
|
||||||
import { onDestroy, onMount, tick } from "svelte"
|
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"
|
import * as v from "valibot"
|
||||||
|
|
||||||
const { data } = $props()
|
const { data } = $props()
|
||||||
let listviewInputRef = $state<HTMLInputElement | null>(null)
|
let listviewInputRef = $state<HTMLInputElement | null>(null)
|
||||||
let { loadedExt, scriptPath, extInfoInDB } = $derived(data)
|
let { loadedExt, scriptPath, extInfoInDB } = $derived(data)
|
||||||
let actionPanelOpen = $state(false)
|
let actionPanelOpen = $state(false)
|
||||||
let workerAPI: WorkerExtension | undefined = undefined
|
let workerAPI: TemplateUiCommand | undefined = undefined
|
||||||
let unlistenRefreshWorkerExt: UnlistenFn | undefined
|
let unlistenRefreshWorkerExt: UnlistenFn | undefined
|
||||||
let unlistenFileDrop: UnlistenFn | undefined
|
let unlistenFileDrop: UnlistenFn | undefined
|
||||||
let worker: Worker | undefined
|
let worker: Worker | undefined
|
||||||
let listViewContent = $state<ListSchema.List>()
|
let listViewContent = $state<ListSchema.List | null>(null)
|
||||||
let formViewContent = $state<FormSchema.Form>()
|
let formViewContent = $state<FormSchema.Form | null>(null)
|
||||||
let markdownViewContent = $state<MarkdownSchema>()
|
let markdownViewContent = $state<MarkdownSchema | null>(null)
|
||||||
let extensionLoadingBar = $state(false) // whether extension called showLoadingBar
|
let extensionLoadingBar = $state(false) // whether extension called showLoadingBar
|
||||||
let pbar = $state<number | null>(null)
|
let pbar = $state<number | null>(null)
|
||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
let searchTerm = $state("")
|
let searchTerm = $state("")
|
||||||
let searchBarPlaceholder = $state("")
|
let searchBarPlaceholder = $state("")
|
||||||
|
let extSpawnedProcesses = $state<number[]>([])
|
||||||
const appWin = getCurrentWebviewWindow()
|
const appWin = getCurrentWebviewWindow()
|
||||||
const loadingBar = $derived($appState.loadingBar || extensionLoadingBar)
|
const loadingBar = $derived($appState.loadingBar || extensionLoadingBar)
|
||||||
let loaded = $state(false)
|
let loaded = $state(false)
|
||||||
let listview: Templates.ListView | undefined = $state(undefined)
|
let listview: Templates.ListView | undefined = $state(undefined)
|
||||||
const _platform = platform()
|
const _platform = platform()
|
||||||
|
let unlistenPkgJsonWatch: UnlistenFn | undefined
|
||||||
|
let curViewNodeName = $state<NodeNameEnum | FormNodeNameEnum | null>(null)
|
||||||
async function goBack() {
|
async function goBack() {
|
||||||
if (isInMainWindow()) {
|
if (isInMainWindow()) {
|
||||||
goto(i18n.resolveRoute("/app/"))
|
goto(i18n.resolveRoute("/app/"))
|
||||||
@ -61,23 +85,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearViewContent(keep?: "list" | "form" | "markdown") {
|
async function clearViewContent(keep?: "list" | "form" | "markdown") {
|
||||||
if (keep !== "list") {
|
if (keep !== "list") {
|
||||||
listViewContent = undefined
|
listViewContent = null
|
||||||
}
|
}
|
||||||
if (keep !== "form") {
|
if (keep !== "form") {
|
||||||
formViewContent = undefined
|
formViewContent = null
|
||||||
}
|
}
|
||||||
if (keep !== "markdown") {
|
if (keep !== "markdown") {
|
||||||
markdownViewContent = undefined
|
markdownViewContent = null
|
||||||
}
|
}
|
||||||
|
await tick()
|
||||||
|
// await sleep(3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const extUiAPI: IUiWorker = {
|
const extUiAPI: IUiTemplate = {
|
||||||
async render(view: IComponent<ListSchema.List | FormSchema.Form | MarkdownSchema>) {
|
async render(_view: IComponent<ListSchema.List | FormSchema.Form | MarkdownSchema>) {
|
||||||
if (view.nodeName === NodeNameEnum.List) {
|
// console.log("render nodeName", _view.nodeName)
|
||||||
clearViewContent("list")
|
// console.log("render", _view)
|
||||||
const parsedListViewRes = v.safeParse(ListSchema.List, view)
|
curViewNodeName = _view.nodeName
|
||||||
|
if (_view.nodeName === NodeNameEnum.List) {
|
||||||
|
await clearViewContent("list")
|
||||||
|
const parsedListViewRes = v.safeParse(ListSchema.List, _view)
|
||||||
if (!parsedListViewRes.success) {
|
if (!parsedListViewRes.success) {
|
||||||
toast.error("Invalid List View", {
|
toast.error("Invalid List View", {
|
||||||
description: "See console for details"
|
description: "See console for details"
|
||||||
@ -157,20 +186,22 @@
|
|||||||
// } else {
|
// } else {
|
||||||
// listViewContent = parsedListView
|
// listViewContent = parsedListView
|
||||||
// }
|
// }
|
||||||
} else if (view.nodeName === FormNodeNameEnum.Form) {
|
} else if (_view.nodeName === FormNodeNameEnum.Form) {
|
||||||
listViewContent = undefined
|
listViewContent = null
|
||||||
clearViewContent("form")
|
// await clearViewContent("form")
|
||||||
const parsedForm = v.parse(FormSchema.Form, view)
|
// await tick()
|
||||||
|
const parsedForm = v.parse(FormSchema.Form, _view)
|
||||||
formViewContent = parsedForm
|
formViewContent = parsedForm
|
||||||
// TODO: convert form to zod schema
|
// TODO: convert form to zod schema
|
||||||
// const zodSchema = convertFormToZod(parsedForm)
|
// const zodSchema = convertFormToZod(parsedForm)
|
||||||
// formViewZodSchema = zodSchema
|
// formViewZodSchema = zodSchema
|
||||||
// formFieldConfig = buildFieldConfig(parsedForm)
|
// formFieldConfig = buildFieldConfig(parsedForm)
|
||||||
} else if (view.nodeName === NodeNameEnum.Markdown) {
|
} else if (_view.nodeName === NodeNameEnum.Markdown) {
|
||||||
clearViewContent("markdown")
|
await clearViewContent("markdown")
|
||||||
markdownViewContent = v.parse(MarkdownSchema, view)
|
await tick()
|
||||||
|
markdownViewContent = v.parse(MarkdownSchema, _view)
|
||||||
} else {
|
} else {
|
||||||
toast.error(`Unsupported view type: ${view.nodeName}`)
|
toast.error(`Unsupported view type: ${_view.nodeName}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async showLoadingBar(loading: boolean) {
|
async showLoadingBar(loading: boolean) {
|
||||||
@ -187,7 +218,6 @@
|
|||||||
searchTerm = term
|
searchTerm = term
|
||||||
},
|
},
|
||||||
async setSearchBarPlaceholder(placeholder: string) {
|
async setSearchBarPlaceholder(placeholder: string) {
|
||||||
console.log("setSearchBarPlaceholder", placeholder)
|
|
||||||
searchBarPlaceholder = placeholder
|
searchBarPlaceholder = placeholder
|
||||||
},
|
},
|
||||||
async goBack() {
|
async goBack() {
|
||||||
@ -209,22 +239,47 @@
|
|||||||
worker = new Worker(blobURL)
|
worker = new Worker(blobURL)
|
||||||
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
|
const serverAPI: IKunkunFullServerAPI = constructJarvisServerAPIWithPermissions(
|
||||||
loadedExt.kunkun.permissions,
|
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 = {
|
const serverAPI2 = {
|
||||||
...serverAPI,
|
...serverAPI,
|
||||||
iframeUi: undefined,
|
iframeUi: undefined,
|
||||||
workerUi: extUiAPI,
|
workerUi: extUiAPI,
|
||||||
|
helper: helperAPI,
|
||||||
db: new db.JarvisExtDB(extInfoInDB.extId),
|
db: new db.JarvisExtDB(extInfoInDB.extId),
|
||||||
kv: new db.KV(extInfoInDB.extId),
|
kv: new db.KV(extInfoInDB.extId),
|
||||||
app: {
|
app: {
|
||||||
language: () => Promise.resolve("en")
|
language: () => Promise.resolve("en")
|
||||||
} satisfies IApp
|
} satisfies IApp
|
||||||
}
|
}
|
||||||
|
|
||||||
const io = new WorkerParentIO(worker)
|
const io = new WorkerParentIO(worker)
|
||||||
const rpc = new RPCChannel<typeof serverAPI2, WorkerExtension>(io, {
|
const kkrpcSerialization = decideKkrpcSerialization(loadedExt)
|
||||||
expose: serverAPI2
|
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()
|
workerAPI = rpc.getAPI()
|
||||||
await workerAPI.load()
|
await workerAPI.load()
|
||||||
@ -236,10 +291,26 @@
|
|||||||
worker?.terminate()
|
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 () => {
|
onMount(async () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
appState.setLoadingBar(true)
|
appState.setLoadingBar(true)
|
||||||
appWin.show()
|
appWin.show().then(() => appWin.setFocus())
|
||||||
}, 100)
|
}, 100)
|
||||||
unlistenRefreshWorkerExt = await listenToRefreshDevExt(() => {
|
unlistenRefreshWorkerExt = await listenToRefreshDevExt(() => {
|
||||||
debug("Refreshing Worker Extension")
|
debug("Refreshing Worker Extension")
|
||||||
@ -255,14 +326,20 @@
|
|||||||
appState.setLoadingBar(false)
|
appState.setLoadingBar(false)
|
||||||
loaded = true
|
loaded = true
|
||||||
}, 500)
|
}, 500)
|
||||||
|
// fs.watch(data.pkgJsonPath, onPkgJsonChange).then((unlisten) => {
|
||||||
|
// unlistenPkgJsonWatch = unlisten
|
||||||
|
// })
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
unlistenRefreshWorkerExt?.()
|
unlistenRefreshWorkerExt?.()
|
||||||
unlistenFileDrop?.()
|
unlistenFileDrop?.()
|
||||||
|
unlistenPkgJsonWatch?.()
|
||||||
winExtMap.unregisterExtensionFromWindow(appWin.label)
|
winExtMap.unregisterExtensionFromWindow(appWin.label)
|
||||||
extensionLoadingBar = false
|
extensionLoadingBar = false
|
||||||
appState.setActionPanel(undefined)
|
appState.setActionPanel(undefined)
|
||||||
|
appState.setDefaultAction(null)
|
||||||
|
appState.setActionPanel(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@ -273,8 +350,13 @@
|
|||||||
keySet.has(_platform === "macos" ? "Meta" : "Control") &&
|
keySet.has(_platform === "macos" ? "Meta" : "Control") &&
|
||||||
keySet.has("k")
|
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) {
|
function onkeydown(e: KeyboardEvent) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
console.log(document.activeElement)
|
|
||||||
console.log(document.activeElement?.nodeName)
|
|
||||||
if (document.activeElement?.nodeName === "INPUT") {
|
if (document.activeElement?.nodeName === "INPUT") {
|
||||||
console.log("input")
|
console.log("input")
|
||||||
}
|
}
|
||||||
@ -299,7 +379,8 @@
|
|||||||
{#if loadingBar}
|
{#if loadingBar}
|
||||||
<LoadingBar class="fixed left-0 top-0 w-full" color="white" />
|
<LoadingBar class="fixed left-0 top-0 w-full" color="white" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if loaded && listViewContent !== undefined}
|
|
||||||
|
{#if curViewNodeName === NodeNameEnum.List && listViewContent}
|
||||||
<Templates.ListView
|
<Templates.ListView
|
||||||
bind:inputRef={listviewInputRef}
|
bind:inputRef={listviewInputRef}
|
||||||
bind:searchTerm
|
bind:searchTerm
|
||||||
@ -321,26 +402,18 @@
|
|||||||
onSearchTermChange={(searchTerm: string) => {
|
onSearchTermChange={(searchTerm: string) => {
|
||||||
workerAPI?.onSearchTermChange(searchTerm)
|
workerAPI?.onSearchTermChange(searchTerm)
|
||||||
}}
|
}}
|
||||||
onHighlightedItemChanged={(value: string) => {
|
onHighlightedItemChanged={(item: ListSchema.Item) => {
|
||||||
// workerAPI?.onHighlightedListItemChanged(value)
|
if (item.defaultAction) {
|
||||||
// if (listViewContent?.defaultAction) {
|
appState.setDefaultAction(item.defaultAction)
|
||||||
// appState.setDefaultAction(listViewContent.defaultAction)
|
} else 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) {
|
if (item.actions) {
|
||||||
appState.setActionPanel(parsedItem.actions)
|
appState.setActionPanel(item.actions)
|
||||||
}
|
} else if (listViewContent?.actions) {
|
||||||
workerAPI?.onHighlightedListItemChanged(parsedItem.value)
|
appState.setActionPanel(listViewContent.actions)
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
}
|
||||||
|
workerAPI?.onHighlightedListItemChanged(item.value)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#snippet footer()}
|
{#snippet footer()}
|
||||||
@ -358,15 +431,18 @@
|
|||||||
/>
|
/>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Templates.ListView>
|
</Templates.ListView>
|
||||||
{:else if loaded && formViewContent !== undefined}
|
{/if}
|
||||||
|
{#if curViewNodeName === FormNodeNameEnum.Form && formViewContent}
|
||||||
<Templates.FormView
|
<Templates.FormView
|
||||||
{formViewContent}
|
{formViewContent}
|
||||||
|
{pbar}
|
||||||
onGoBack={goBack}
|
onGoBack={goBack}
|
||||||
onSubmit={(formData: Record<string, string | number | boolean>) => {
|
onSubmit={(formData: Record<string, string | number | boolean>) => {
|
||||||
console.log("formData", formData)
|
console.log("Submit formData", formData)
|
||||||
workerAPI?.onFormSubmit(formData)
|
workerAPI?.onFormSubmit(formData)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{:else if loaded && markdownViewContent !== undefined}
|
{/if}
|
||||||
|
{#if curViewNodeName === NodeNameEnum.Markdown && markdownViewContent}
|
||||||
<Templates.MarkdownView {markdownViewContent} onGoBack={goBack} />
|
<Templates.MarkdownView {markdownViewContent} onGoBack={goBack} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,25 +1,36 @@
|
|||||||
|
import { KunkunTemplateExtParams } from "@/cmds/ext"
|
||||||
import { i18n } from "@/i18n"
|
import { i18n } from "@/i18n"
|
||||||
import { db, unregisterExtensionWindow } from "@kksh/api/commands"
|
import type { ExtPackageJsonExtra } from "@kksh/api/models"
|
||||||
import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models"
|
import { db } from "@kksh/drizzle"
|
||||||
import { loadExtensionManifestFromDisk } from "@kksh/extension"
|
import { loadExtensionManifestFromDisk } from "@kksh/extension"
|
||||||
import { error as sbError } from "@sveltejs/kit"
|
import { error as sbError, error as svError } from "@sveltejs/kit"
|
||||||
import { join } from "@tauri-apps/api/path"
|
import { join } from "@tauri-apps/api/path"
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
import { exists, readTextFile } from "@tauri-apps/plugin-fs"
|
import { exists, readTextFile } from "@tauri-apps/plugin-fs"
|
||||||
import { error } from "@tauri-apps/plugin-log"
|
import { error } from "@tauri-apps/plugin-log"
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from "$app/navigation"
|
||||||
import { toast } from "svelte-sonner"
|
import { toast } from "svelte-sonner"
|
||||||
|
import * as v from "valibot"
|
||||||
import type { PageLoad } from "./$types"
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
export const load: PageLoad = async ({ url }) => {
|
export const load: PageLoad = async ({ url }) => {
|
||||||
// both query parameter must exist
|
// both query parameter must exist
|
||||||
|
const rawKunkunTemplateExtParams = localStorage.getItem("kunkun-template-ext-params")
|
||||||
const extPath = url.searchParams.get("extPath")
|
if (!rawKunkunTemplateExtParams) {
|
||||||
const cmdName = url.searchParams.get("cmdName")
|
|
||||||
if (!extPath || !cmdName) {
|
|
||||||
toast.error("Invalid extension path or url")
|
toast.error("Invalid extension path or url")
|
||||||
error("Invalid extension path or url")
|
return svError(404, "Invalid extension path or url")
|
||||||
goto(i18n.resolveRoute("/app/"))
|
|
||||||
}
|
}
|
||||||
|
const json = JSON.parse(rawKunkunTemplateExtParams)
|
||||||
|
const parsed = v.safeParse(KunkunTemplateExtParams, json)
|
||||||
|
if (!parsed.success) {
|
||||||
|
getCurrentWindow().show()
|
||||||
|
console.error(v.flatten<typeof KunkunTemplateExtParams>(parsed.issues))
|
||||||
|
toast.error("Fail to parse extension params from local storage", {
|
||||||
|
description: `${v.flatten<typeof KunkunTemplateExtParams>(parsed.issues)}`
|
||||||
|
})
|
||||||
|
return svError(404, "Fail to parse extension params from local storage")
|
||||||
|
}
|
||||||
|
const { cmdName, extPath } = parsed.output
|
||||||
|
|
||||||
let _loadedExt: ExtPackageJsonExtra | undefined
|
let _loadedExt: ExtPackageJsonExtra | undefined
|
||||||
try {
|
try {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user