Compare commits

...

246 commits

Author SHA1 Message Date
anatawa12
9a1044df51
Merge pull request #2941 from nekobako/feature/project-sorting-by-creation-date
プロジェクトの登録順ソート
2026-06-18 22:40:23 +09:00
anatawa12
e71c48141b
chore: Apply localization suggestions from code review
Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>
2026-06-18 22:34:31 +09:00
nekobako
467434903a docs(changelog-gui): update changelog 2026-06-17 23:20:01 +09:00
nekobako
ba8606d312 chore(gui): adjust project list spacing 2026-06-17 23:20:01 +09:00
nekobako
ed42cdfca4 feat(gui): implement project sorting by creation date 2026-06-17 23:20:01 +09:00
anatawa12
dbd479fbc9
Merge pull request #2976 from vrc-get/dependabot/cargo/uuid-1.23.3
chore(deps): bump uuid from 1.23.2 to 1.23.3
2026-06-15 19:44:33 +09:00
anatawa12
db54f0c8ae
Merge pull request #2970 from vrc-get/native-package
build: use package manager native toolchain to build linux packages
2026-06-15 17:46:38 +09:00
dependabot[bot]
da419a0374
chore(deps): bump uuid from 1.23.2 to 1.23.3
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.23.2 to 1.23.3.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.2...v1.23.3)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-15 07:43:41 +00:00
anatawa12
8351270552
chore!: remove deb / rpm build command 2026-06-15 16:01:18 +09:00
anatawa12
50fccc9a50
ci: migrate publish workflow to package manager native building toolchain 2026-06-15 09:56:40 +09:00
anatawa12
cf7075a6d5
fix: version name in bult binary is separated by ~ 2026-06-15 09:25:05 +09:00
anatawa12
5e4709209f
ci: do not patch source code in spec file, in source tar instead 2026-06-15 08:08:52 +09:00
anatawa12
269cb8aeda
ci: use jammy for building debian package 2026-06-15 00:29:45 +09:00
anatawa12
ba9ad1f59c
ci: add debian package and build on ci 2026-06-14 02:16:31 +09:00
anatawa12
23b1bb2515
ci: add rpm spec file and build on ci 2026-06-11 15:09:19 +09:00
github-actions[bot]
2c36473e6e chore: prepare for next version: gui 1.1.7 2026-06-02 10:44:59 +00:00
github-actions[bot]
791e50d94f gui v1.1.6 2026-06-02 10:29:07 +00:00
anatawa12
a77ab4a0a3
chore: better url handling 2026-06-02 19:26:07 +09:00
anatawa12
aa14673468
Merge pull request #2951 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/vite-8.0.16
chore(deps-dev): bump vite from 8.0.14 to 8.0.16 in /vrc-get-gui
2026-06-02 16:21:30 +09:00
anatawa12
36aa59b304
Merge pull request #2949 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/biomejs/biome-2.4.16
chore(deps-dev): bump @biomejs/biome from 2.4.15 to 2.4.16 in /vrc-get-gui
2026-06-02 16:10:27 +09:00
anatawa12
b61c44c64a
Merge pull request #2950 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/lucide-react-1.17.0
chore(deps): bump lucide-react from 1.16.0 to 1.17.0 in /vrc-get-gui
2026-06-02 16:10:11 +09:00
anatawa12
0a5ab490b8
Merge pull request #2947 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/i18next-f873b873ff
chore(deps): bump i18next from 26.2.0 to 26.3.0 in /vrc-get-gui in the i18next group across 1 directory
2026-06-02 16:09:50 +09:00
dependabot[bot]
cfc1b627eb
chore(deps-dev): bump vite from 8.0.14 to 8.0.16 in /vrc-get-gui
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.14 to 8.0.16.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 01:32:47 +00:00
dependabot[bot]
1b2065afe0
chore(deps-dev): bump @biomejs/biome in /vrc-get-gui
Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.4.15 to 2.4.16.
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.16/packages/@biomejs/biome)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.4.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 01:32:23 +00:00
anatawa12
4634c42410
Merge pull request #2946 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/tanstack-router-116e156dcc
chore(deps): bump the tanstack-router group across 1 directory with 2 updates
2026-06-02 10:30:13 +09:00
dependabot[bot]
fd8b735ac9
chore(deps): bump the tanstack-router group across 1 directory with 2 updates
Bumps the tanstack-router group with 2 updates in the /vrc-get-gui directory: [@tanstack/react-router](https://github.com/TanStack/router/tree/HEAD/packages/react-router) and [@tanstack/router-plugin](https://github.com/TanStack/router/tree/HEAD/packages/router-plugin).


Updates `@tanstack/react-router` from 1.170.8 to 1.170.10
- [Release notes](https://github.com/TanStack/router/releases)
- [Changelog](https://github.com/TanStack/router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/TanStack/router/commits/@tanstack/react-router@1.170.10/packages/react-router)

Updates `@tanstack/router-plugin` from 1.168.11 to 1.168.13
- [Release notes](https://github.com/TanStack/router/releases)
- [Changelog](https://github.com/TanStack/router/blob/main/packages/router-plugin/CHANGELOG.md)
- [Commits](https://github.com/TanStack/router/commits/@tanstack/router-plugin@1.168.13/packages/router-plugin)

---
updated-dependencies:
- dependency-name: "@tanstack/react-router"
  dependency-version: 1.170.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: tanstack-router
- dependency-name: "@tanstack/router-plugin"
  dependency-version: 1.168.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: tanstack-router
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 01:13:54 +00:00
anatawa12
580c8da047
Merge pull request #2948 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/rollup/pluginutils-5.4.0
chore(deps-dev): bump @rollup/pluginutils from 5.3.0 to 5.4.0 in /vrc-get-gui
2026-06-02 10:07:13 +09:00
dependabot[bot]
0ebfb25263
chore(deps): bump lucide-react from 1.16.0 to 1.17.0 in /vrc-get-gui
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 1.16.0 to 1.17.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.17.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 01:06:55 +00:00
dependabot[bot]
9eaf99b3d1
chore(deps): bump i18next
Bumps the i18next group with 1 update in the /vrc-get-gui directory: [i18next](https://github.com/i18next/i18next).


Updates `i18next` from 26.2.0 to 26.3.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.2.0...v26.3.0)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 26.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: i18next
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 01:06:08 +00:00
anatawa12
87753ceffa
Merge pull request #2945 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/react-cd43639a56
chore(deps): bump the react group in /vrc-get-gui with 3 updates
2026-06-02 10:02:18 +09:00
anatawa12
0fa954cef0
Merge pull request #2944 from vrc-get/dependabot/cargo/uuid-1.23.2
chore(deps): bump uuid from 1.23.1 to 1.23.2
2026-06-02 10:00:32 +09:00
anatawa12
41e25d4e86
Merge pull request #2943 from vrc-get/dependabot/cargo/sysinfo-0.39.3
chore(deps): bump sysinfo from 0.39.2 to 0.39.3
2026-06-02 10:00:15 +09:00
anatawa12
3c499f9d6a
Merge pull request #2942 from vrc-get/dependabot/cargo/reqwest-0.13.4
chore(deps): bump reqwest from 0.13.3 to 0.13.4
2026-06-02 09:59:47 +09:00
dependabot[bot]
65e37ff7af
chore(deps-dev): bump @rollup/pluginutils in /vrc-get-gui
Bumps [@rollup/pluginutils](https://github.com/rollup/plugins/tree/HEAD/packages/pluginutils) from 5.3.0 to 5.4.0.
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/pluginutils/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/pluginutils-v5.4.0/packages/pluginutils)

---
updated-dependencies:
- dependency-name: "@rollup/pluginutils"
  dependency-version: 5.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 00:15:03 +00:00
dependabot[bot]
48e1866ff3
chore(deps): bump the react group in /vrc-get-gui with 3 updates
Bumps the react group in /vrc-get-gui with 3 updates: [react](https://github.com/facebook/react/tree/HEAD/packages/react), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) and [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom).


Updates `react` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react)

Updates `@types/react` from 19.2.15 to 19.2.16
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-dom` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react-dom)

Updates `@types/react` from 19.2.15 to 19.2.16
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: "@types/react"
  dependency-version: 19.2.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: react-dom
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: "@types/react"
  dependency-version: 19.2.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 00:14:30 +00:00
dependabot[bot]
968ae63a4c
chore(deps): bump uuid from 1.23.1 to 1.23.2
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.23.1 to 1.23.2.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.2)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.23.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 00:13:19 +00:00
dependabot[bot]
805be35ac6
chore(deps): bump sysinfo from 0.39.2 to 0.39.3
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.39.2 to 0.39.3.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.39.2...v0.39.3)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-version: 0.39.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 00:12:47 +00:00
dependabot[bot]
901368c9bf
chore(deps): bump reqwest from 0.13.3 to 0.13.4
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.13.3 to 0.13.4.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.13.3...v0.13.4)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-version: 0.13.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 00:12:33 +00:00
github-actions[bot]
09c8190634 v1.9.2-rc.0 2026-05-30 16:11:35 +00:00
anatawa12
5eed047fa4
Merge pull request #2940 from vrc-get/publish-workflow-crash
build: check-static-link for aarch64 linux is broken
2026-05-31 01:10:13 +09:00
anatawa12
056073834c
build: check-static-link for aarch64 linux is broken 2026-05-31 00:58:26 +09:00
anatawa12
c4117c7457
ci: allow dry-run for non-master branch 2026-05-31 00:37:37 +09:00
anatawa12
3bf6b96b93
ci: dry-run for cli build 2026-05-31 00:33:40 +09:00
anatawa12
00fb4d8546
ci: upload artifacts regardless build failure 2026-05-31 00:28:53 +09:00
github-actions[bot]
4d4c0c27d3 gui v1.1.6-rc.0 2026-05-30 14:43:02 +00:00
anatawa12
a31c947c1a
Merge pull request #2935 from nekochanfood/feat/reorder-user-repos
feat: リポジトリ一覧で並び替えができるようにする
2026-05-30 21:48:44 +09:00
nekochanfood
f6e677909c fix(gui): reject remove/reorder when settings.json drifted externally
remove/reorder targeted userRepos entries by array position only. An
external write to settings.json (another ALCOM instance, VCC, vrc-get
CLI, manual edit) between the frontend's last fetch and the call
could silently delete or reorder the wrong repo.

The frontend already has the expected id (part of TauriUserRepository),
so send it alongside the index and have the backend verify the repo at
that index still has the expected id before mutating. On mismatch,
reject with an `unrecoverable_str` error; the frontend's existing
onError + onSettled refetch rolls back optimistic state and shows the
on-disk truth.

- environment_remove_repository takes (index, expected_id)
- environment_reorder_repositories takes Vec<TauriUserRepositoryRef>;
  all pairs verified up front, no partial reorder on mismatch
- verify_repo_at_index helper centralises the lookup + comparison
2026-05-30 20:50:24 +09:00
nekochanfood
8b58270235 chore: move the order of the change 2026-05-30 20:27:04 +09:00
nekochanfood
c1b297e02a chore: clean up changelog entries for reorder/remove repo changes 2026-05-30 20:21:02 +09:00
Kissa Ruokanen
a0ec779fab
Merge pull request #1 from nekochanfood/feat/improve-duplicate-repo-handling
Feat/improve duplicate repo handling
2026-05-30 14:56:51 +09:00
nekochanfood
800278a0c3 style(vpm): satisfy fmt and clippy on reorder_user_repos_by_indices
Auto-applied cargo fmt for the collect-chain split, then folded the
nested `if let` into a let-chain and replaced the manual `if let Some`
loop with `into_iter().flatten()`.
2026-05-30 14:55:43 +09:00
nekochanfood
334492e5c6 refactor: clean up issues found in branch review
- vpm: drop unused id-based reorder_user_repos; reorder_user_repos_by_indices is the only caller
- CHANGELOG: reflect the actual public API surface (reorder_user_repos_by_indices, remove_repo_at_index)
- gui: rollback optimistic delete on error in RemoveRepositoryDialog, matching setHideRepository
- gui: demote per-reorder log from info to debug to reduce noise
2026-05-30 14:45:42 +09:00
nekochanfood
1a947b7a6c fix(gui): preserve listId identity for duplicate repos across refetch
Repos with duplicate (id, url) pairs had their per-row React keys
regenerated as `crypto.randomUUID()` on every refetch, causing scroll
reset and remount on the entire list. Stabilizing by `${id} ${url}`
key with an iteration-order counter would normally fix this, but for
duplicate-keyed rows the counter assignment depends on iteration
order: when two such repos swap positions, their slot keys swap, and
the lookup returns each other's UUID — React reconciles by tearing
both rows down (visible as a flicker on the row the dragged row
crossed over).

- Stabilize listId across refetch via listIdMapRef keyed on slot key
- Extract computeSlotKey helper for reuse
- Pin listIds to the new optimistic order in reorderMutation.onMutate
  so the post-refetch augmentation lookup returns the same listIds
- Snapshot and restore the previous map in onError so a failed reorder
  rolls back cleanly
2026-05-30 14:45:13 +09:00
nekochanfood
514f52a419 Revert "fix(gui): fix DnD drop artifact and item displacement flicker"
This reverts commit 6dfdd9c5f3.
2026-05-30 12:53:59 +09:00
nekochanfood
3e42f0de4e Revert "fix(gui): fix double-row artifact and scroll reset on dnd drop"
This reverts commit 21a0e19722.
2026-05-30 12:53:54 +09:00
nekochanfood
6dfdd9c5f3 fix(gui): fix DnD drop artifact and item displacement flicker
Replaced sideEffects-based opacity hack (broken by React re-renders) with
a droppingListId state that keeps the source row hidden for the duration of
the drop animation via React's own style prop.

Removed the visualIndex estimate (rowIndex ± 1 based on transform.y) that
caused continuous className toggling during drag; rows now use their stable
rowIndex for stripe coloring, eliminating the flicker.
2026-05-30 12:50:06 +09:00
nekochanfood
21a0e19722 fix(gui): fix double-row artifact and scroll reset on dnd drop
Two regressions introduced by the listId/UUID change:

- Scroll reset: crypto.randomUUID() in useMemo regenerated all listIds
  on every re-fetch, causing React to unmount/remount all rows and lose
  scroll position. Fixed by using String(r.index) instead — index is
  stable across re-fetches for items that haven't moved, so React can
  reconcile rows in place.

- Double-row artifact: on drop, isDragging becomes false before the
  DragOverlay drop animation completes, making the real row visible at
  the destination while the overlay is still animating there. Fixed by
  using defaultDropAnimationSideEffects to force opacity:0 on the
  original element for the duration of the drop animation.
2026-05-30 12:12:24 +09:00
nekochanfood
6ab9c49c94 fix(gui): stabilize repo list against duplicate IDs in userRepos
Repositories with duplicate `id` values in settings.json caused the
frontend list to break: React keys were non-unique, dnd-kit's
SortableContext misbehaved, and the Map lookup silently discarded
duplicate entries.

- Add `index` field to `TauriUserRepository` (position in the
  userRepos array, never persisted) so operations can target a
  specific entry regardless of ID uniqueness
- Replace ID-based remove/reorder commands with index-based variants
  (`remove_repo_at_index`, `reorder_user_repos_by_indices`) in both
  vpm_settings and the Tauri command layer
- Augment each repo with a frontend-only `listId` (crypto.randomUUID)
  used exclusively as React key and dnd-kit sortable item ID
2026-05-30 11:37:11 +09:00
Kissa Ruokanen
04adf285b5 style(gui): fix CSS selector formatting to satisfy Biome formatter 2026-05-30 09:41:43 +09:00
anatawa12
665513d0fc
Merge branch 'master' into feat/reorder-user-repos 2026-05-30 01:12:28 +09:00
anatawa12
6f8cd53159
Merge pull request #2939 from vrc-get/fix-buildx-devtools-not-working
build: cargo xtask build-alcom --devtools not working
2026-05-30 01:12:17 +09:00
anatawa12
ec6454b0d4
fix: cargo xtask build-alcom --devtools not working 2026-05-30 01:04:29 +09:00
nekochanfood
533a40b1eb fix(gui): account for vertical scrollbar width in ScrollableCardTable
When the vertical scrollbar is visible, its overlay covers the rightmost
10px of the table content. Add pe-2.5 to the content wrapper via CSS
so all ScrollableCardTable instances reserve space for the scrollbar.
2026-05-29 22:19:31 +09:00
nekochanfood
90c7e249cd fix(gui): fix scroll stuttering when dragging repository list items
- Move guiAnimation query and setHideRepository mutation from each
  RepositoryRow to PageBody, passing them down as props. Because
  useSortable subscribes all rows to DnD context, every pointermove
  during drag re-rendered every row. With O(N) expensive hooks per row
  this blocked the main thread for ~130ms per event (~7fps).

- Disable dnd-kit's built-in autoScroll (autoScroll={false}) and replace
  it with a useDragAutoScroll hook. dnd-kit's AutoScroller caused jitter
  due to wrong scroll-container detection in Radix UI ScrollArea and
  double-smoothing from the viewport's scroll-behavior: smooth. The
  custom hook targets the viewport directly via viewportRef and uses
  behavior: instant to avoid CSS smooth-scroll interference.

- Suppress background-color transition on rows during active drag to
  reduce paint operations when rows shift position.

- Replace orderedIds.includes() with a Set lookup in collisionDetection.
2026-05-29 20:56:30 +09:00
Kissa Ruokanen
e057838795 docs(changelog-gui): add entry for drag-and-drop repository reordering 2026-05-29 16:04:20 +09:00
Kissa Ruokanen
437a63bcc6 fix: sort @dnd-kit/core imports to satisfy biome lint 2026-05-29 16:02:18 +09:00
Kissa Ruokanen
836bf82351
Merge branch 'master' into feat/reorder-user-repos 2026-05-29 15:47:06 +09:00
Kissa Ruokanen
316c73a6b6 feat: animate drag overlay row color based on current drop position
Track the hovered droppable via onDragOver and compute the overlay's
visual index so its background color updates in real-time as it moves
between rows, matching the same bg-secondary/30 / transition spec used
by the sortable rows.
2026-05-29 15:38:35 +09:00
Kissa Ruokanen
ec04492af8 Merge branch 'feat/reorder-user-repos' of https://github.com/nekochanfood/vrc-get into feat/reorder-user-repos 2026-05-29 15:26:38 +09:00
Kissa Ruokanen
ebd117afd2 feat: animate row colors during drag with O(k) re-renders
Replace useDndContext() + arrayMove approach with transform.y sign
detection to compute visual row index. Previously all n rows subscribed
to the dnd context and re-rendered on every collision change, each
doing an O(n) arrayMove+indexOf -- O(n^2) total.

Now only rows whose transform changes re-render, and each computes its
visual index in O(1) from the sign of transform.y from useSortable.
Total cost is O(k) where k is the number of displaced rows (typically 1-2).

Also adds background-color to the inline transition string so the
200ms color animation works correctly alongside the transform animation,
and removes guiAnimation prop drilling through RepositoryTableBody.
2026-05-29 15:26:29 +09:00
Kissa Ruokanen
253a0266e6 fix: vertically center checkboxes in repository list
Wrap checkbox in flex container to eliminate inline-block descender
space, which caused the button to align to the text baseline rather
than the vertical center of the cell.
2026-05-29 15:14:27 +09:00
Kissa Ruokanen
0cf635e892 fix: vertically center checkboxes in repository list
Add align-middle to CELL_CLASS so all table cells use vertical-align: middle.
2026-05-29 14:56:48 +09:00
anatawa12
f8923dce1b
Merge pull request #2936 from vrc-get/funding-yml
docs: add FUNDING.yml
2026-05-29 14:52:50 +09:00
anatawa12
9fc228eed0
docs: add FUNDING.yml 2026-05-29 14:46:45 +09:00
Kissa Ruokanen
54113144a8 refactor: clean up animation-related code in repository list
- Remove redundant isDragging state; derive from activeId !== null
- Move TABLE_HEAD and DRAG_OVERLAY_MODIFIERS to module level to avoid
  per-render allocations
- Remove unnecessary ?? true fallback on initialData: true query result
- Eliminate guiAnimation prop drilling through RepositoryTableBody;
  RepositoryRow now reads directly from the shared React Query cache
2026-05-29 14:37:10 +09:00
Kissa Ruokanen
0ab6c9434b feat: add smooth drop animation for repository reordering
Use DragOverlay from @dnd-kit/core to show a floating row clone while
dragging, giving a smooth drop animation on release. The overlay uses
colgroup-based fixed column widths measured from the thead at drag start,
so columns align pixel-perfectly with the underlying table. Movement is
restricted to the vertical axis via a custom modifier.

Animation respects the existing gui-animation setting: dropAnimation is
disabled when animations are turned off, and non-active rows lose their
transition as well.

Extract shared RepositoryRowCells component so the table row and the drag
overlay render identical structure and styling from a single definition.
2026-05-29 14:33:52 +09:00
Kissa Ruokanen
9d94d8b929 refactor: memoize dragStyle and remove redundant index checks in handleDragEnd 2026-05-29 14:14:14 +09:00
Kissa Ruokanen
d1887eb467 fix: eliminate drag stutter by using custom collision detection
Replace live orderedIds updates during onDragOver with a custom
collision detection that filters droppable targets to user repos only.
This ensures `over` is always a valid user repo (clamping to the
boundary when the cursor leaves the list), so dnd-kit handles all
visual movement through CSS transforms without DOM reordering.
Eliminates the position warp that occurred when items swapped.
2026-05-29 14:06:13 +09:00
Kissa Ruokanen
45ed34f573 fix: preserve drag position when cursor leaves list bounds
Track last valid sort order via dragOverOrderRef during onDragOver so
that when the pointer leaves the sortable region (triggering cancel or
landing on a non-user-repo row), the item stays at its last reached
position instead of snapping back or jumping to the end of the list.
Also disable pointer-events on HNavBar while dragging to prevent Radix
UI from stealing pointer capture.
2026-05-29 13:39:11 +09:00
Kissa Ruokanen
930715960e fix: prevent snap-back when dragging outside list bounds
Use closestCenter collision detection so over never becomes null
when the cursor moves beyond the top or bottom of the sortable list.
2026-05-29 13:21:26 +09:00
Kissa Ruokanen
8db24b7b7c feat: implement drag-and-drop reordering of user repositories
Use @dnd-kit/core and @dnd-kit/sortable to wire up the existing drag
handles so users can reorder repositories by dragging. DndContext is
placed at the page level so pointer tracking is not lost when the cursor
moves outside the list area. Adds an info log in the Rust command so the
new order is visible in the app log viewer.
2026-05-29 13:19:33 +09:00
Kissa Ruokanen
44ed683c23 feat: dim drag handle and show not-allowed cursor for non-reorderable repositories
Official and curated repositories cannot be reordered, so their drag
handles are now rendered with opacity-50 and cursor-not-allowed to
visually indicate they are not interactive, consistent with how their
remove buttons are already disabled.
2026-05-29 12:40:56 +09:00
Kissa Ruokanen
c248b0ed55 chore: regenerate bindings.ts with environmentReorderRepositories 2026-05-29 10:57:51 +09:00
Kissa Ruokanen
9d11a5e816
Merge branch 'master' into feat/reorder-user-repos 2026-05-29 10:48:40 +09:00
Kissa Ruokanen
81d66143c9 feat: add environment_reorder_repositories Tauri command
Add `reorder_user_repos` to `VpmSettings` and `Settings`, and expose it
as the `environment_reorder_repositories` Tauri command to allow
reordering `userRepos` in `settings.json`.
2026-05-29 10:36:49 +09:00
anatawa12
6f143ac5e3
Merge pull request #2933 from vrc-get/settings-json-corruption-recover
recover from settings.json corruption with backup file
2026-05-29 10:29:58 +09:00
anatawa12
f44633a2ad
fix: toast are not shown because of delay in webview2 initialization 2026-05-29 09:30:19 +09:00
anatawa12
3fb8281a47
Merge pull request #2934 from JustBuddy/german-translation
chore(l10n): [de_DE] update locale
2026-05-29 08:53:35 +09:00
JustBuddy
244946c234
chore(l10n): [de_DE] update locale 2026-05-28 22:57:28 +02:00
anatawa12
ccd252f8aa
docs(changelog-gui): We also added a backup file to recover from settings.json corruption 2026-05-28 23:31:10 +09:00
anatawa12
c2b2b9b491
feat: show warning toast when settings.json is recovered from corruption 2026-05-28 23:27:23 +09:00
anatawa12
9decfe400c
feat: create backup of settings.json to recover from corruption caused by VCC
VCC sometimes leaves settings.json corrupted and filled with NUL bytes,
likely because file contents are not fully synced to disk before or during writing.
vrc-get already uses fsynced temp-file replacement when writing settings.json,
but this does not protect against corruption caused by external writes.
This commit adds a backup file to recover from such corruption.
2026-05-28 23:21:41 +09:00
anatawa12
35e7537a90
Merge pull request #2932 from CirnoV/locale/ko-KR
chore(l10n): [ko] update locale
2026-05-28 20:09:32 +09:00
CirnoV
1800951c12 chore(l10n): [ko] update locale 2026-05-28 19:52:48 +09:00
anatawa12
76b0f05651
feat: recover project list from settings.json when settings json is corrupted 2026-05-28 19:46:03 +09:00
anatawa12
f45ee6514e
Merge pull request #2911 from Spokeek/french-translation
Update FR locales for 1.1.6
2026-05-28 10:23:12 +09:00
Spokeek
d421ee04b2
remove === prefix 2026-05-27 18:51:40 +02:00
Spokeek
bd92649248
Fix json5 2026-05-27 18:46:13 +02:00
Spokeek
68be14aa90
Update FR locales for 1.1.6 2026-05-27 18:20:57 +02:00
anatawa12
dd9673c2cb
Merge pull request #2924 from vrc-community-assets-tc-translators/master
chore(l10n): [zh_hant] update locale
2026-05-27 19:11:15 +09:00
anatawa12
73cfa96f45
Merge pull request #2930 from vrc-get/support-empty-string-for-optional-url-field
chore: allow empty `changelogUrl` and `documentationUrl`
2026-05-26 22:58:20 +09:00
anatawa12
fa22fc3611
docs(changelog): Empty string for documentationUrl and changelogUrl are now allowed and ignored 2026-05-26 21:13:24 +09:00
anatawa12
8b61a0c806
chore: allow blank changelogUrl and documentationUrl 2026-05-26 20:13:55 +09:00
anatawa12
c37758d4c1
chore: allow empty changelogUrl and documentationUrl 2026-05-26 19:49:26 +09:00
夜嵐蝶Alma
37688599ae chore(l10n): [zh_hant] update locale 2026-05-25 21:40:04 +08:00
夜嵐蝶Alma
fcb0c5263d
Merge branch 'vrc-get:master' into master 2026-05-25 21:37:32 +08:00
github-actions[bot]
a7e19a6479 gui v1.1.6-beta.2 2026-05-25 11:36:25 +00:00
anatawa12
f0a7cbd050
Merge pull request #2926 from vrc-get/dependabot/cargo/log-0.4.30
chore(deps): bump log from 0.4.29 to 0.4.30
2026-05-25 20:15:45 +09:00
anatawa12
a9322f4d64
Merge pull request #2927 from vrc-get/dependabot/cargo/tar-0.4.46
chore(deps): bump tar from 0.4.45 to 0.4.46
2026-05-25 20:14:54 +09:00
dependabot[bot]
0c895bf4a5
chore(deps): bump tar from 0.4.45 to 0.4.46
Bumps [tar](https://github.com/composefs/tar-rs) from 0.4.45 to 0.4.46.
- [Release notes](https://github.com/composefs/tar-rs/releases)
- [Commits](https://github.com/composefs/tar-rs/compare/0.4.45...0.4.46)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 0.4.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 11:07:42 +00:00
dependabot[bot]
07e7279c31
chore(deps): bump log from 0.4.29 to 0.4.30
Bumps [log](https://github.com/rust-lang/log) from 0.4.29 to 0.4.30.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.29...0.4.30)

---
updated-dependencies:
- dependency-name: log
  dependency-version: 0.4.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 11:07:29 +00:00
anatawa12
a9e135b940
Merge pull request #2862 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/tauri-apps/api-2.11.0
chore(deps): bump @tauri-apps/api from 2.10.1 to 2.11.0 in /vrc-get-gui
2026-05-25 19:51:23 +09:00
anatawa12
d2aca1554c
Merge pull request #2890 from vrc-get/dependabot/cargo/tauri-347ba455b6
chore(deps): bump the tauri group across 1 directory with 2 updates
2026-05-25 19:48:59 +09:00
anatawa12
5be2c13984
chore: support LIBDBUS_1_3 versioned symbol 2026-05-25 19:40:21 +09:00
anatawa12
1b1a0eefae
Merge pull request #2923 from vrc-get/dependabot/cargo/rpm-0.24.0
chore(deps): bump rpm from 0.22.0 to 0.24.0
2026-05-25 17:34:11 +09:00
copilot-swe-agent[bot]
3e94514b13
fix(xtask): handle non-numeric ELF symbol versions in linux bundling 2026-05-25 08:05:17 +00:00
copilot-swe-agent[bot]
56a32bad04
fix: add payload feature to rpm dependency to fix compilation errors 2026-05-25 07:59:08 +00:00
dependabot[bot]
fb6a6538d7
chore(deps): bump @tauri-apps/api from 2.10.1 to 2.11.0 in /vrc-get-gui
Bumps [@tauri-apps/api](https://github.com/tauri-apps/tauri) from 2.10.1 to 2.11.0.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/@tauri-apps/api-v2.10.1...@tauri-apps/api-v2.11.0)

---
updated-dependencies:
- dependency-name: "@tauri-apps/api"
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 07:52:24 +00:00
anatawa12
ab64f403a5
Merge pull request #2910 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/tanstack-router-fc9df4cbb5
chore(deps): bump the tanstack-router group across 1 directory with 3 updates
2026-05-25 16:50:04 +09:00
dependabot[bot]
96d8ced3bb
chore(deps): bump rpm from 0.22.0 to 0.24.0
Bumps [rpm](https://github.com/rpm-rs/rpm) from 0.22.0 to 0.24.0.
- [Changelog](https://github.com/rpm-rs/rpm-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rpm-rs/rpm/compare/v0.22.0...v0.24.0)

---
updated-dependencies:
- dependency-name: rpm
  dependency-version: 0.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 07:43:39 +00:00
anatawa12
bde10abd3b
Merge pull request #2896 from vrc-get/dependabot/cargo/object-0.39.1
chore(deps): bump object from 0.39.0 to 0.39.1
2026-05-25 16:41:19 +09:00
dependabot[bot]
e3313f0327
chore(deps): bump the tanstack-router group across 1 directory with 3 updates
Bumps the tanstack-router group with 3 updates in the /vrc-get-gui directory: [@tanstack/react-router](https://github.com/TanStack/router/tree/HEAD/packages/react-router), [@tanstack/router-devtools](https://github.com/TanStack/router/tree/HEAD/packages/router-devtools) and [@tanstack/router-plugin](https://github.com/TanStack/router/tree/HEAD/packages/router-plugin).


Updates `@tanstack/react-router` from 1.168.25 to 1.170.8
- [Release notes](https://github.com/TanStack/router/releases)
- [Changelog](https://github.com/TanStack/router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/TanStack/router/commits/@tanstack/react-router@1.170.8/packages/react-router)

Updates `@tanstack/router-devtools` from 1.166.13 to 1.167.0
- [Release notes](https://github.com/TanStack/router/releases)
- [Changelog](https://github.com/TanStack/router/blob/main/packages/router-devtools/CHANGELOG.md)
- [Commits](https://github.com/TanStack/router/commits/@tanstack/router-devtools@1.167.0/packages/router-devtools)

Updates `@tanstack/router-plugin` from 1.167.28 to 1.168.11
- [Release notes](https://github.com/TanStack/router/releases)
- [Changelog](https://github.com/TanStack/router/blob/main/packages/router-plugin/CHANGELOG.md)
- [Commits](https://github.com/TanStack/router/commits/@tanstack/router-plugin@1.168.11/packages/router-plugin)

---
updated-dependencies:
- dependency-name: "@tanstack/react-router"
  dependency-version: 1.170.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: tanstack-router
- dependency-name: "@tanstack/router-devtools"
  dependency-version: 1.167.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: tanstack-router
- dependency-name: "@tanstack/router-plugin"
  dependency-version: 1.168.6
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: tanstack-router
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 07:40:08 +00:00
anatawa12
bb0b8cda55
Merge pull request #2908 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/tauri-apps/cli-2.11.2
chore(deps-dev): bump @tauri-apps/cli from 2.10.1 to 2.11.2 in /vrc-get-gui
2026-05-25 16:37:44 +09:00
anatawa12
11b6b1cad4
Merge pull request #2877 from vrc-get/dependabot/cargo/rpassword-7.5.2
chore(deps): bump rpassword from 7.4.0 to 7.5.3
2026-05-25 15:29:41 +09:00
dependabot[bot]
92cff628c2
chore(deps): bump rpassword from 7.4.0 to 7.5.3
Bumps [rpassword](https://github.com/conradkleinespel/rpassword) from 7.4.0 to 7.5.3.
- [Release notes](https://github.com/conradkleinespel/rpassword/releases)
- [Commits](https://github.com/conradkleinespel/rpassword/compare/v7.4.0...v7.5.3)

---
updated-dependencies:
- dependency-name: rpassword
  dependency-version: 7.5.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 05:49:03 +00:00
dependabot[bot]
3f3465d74b
chore(deps-dev): bump @tauri-apps/cli in /vrc-get-gui
Bumps [@tauri-apps/cli](https://github.com/tauri-apps/tauri) from 2.10.1 to 2.11.2.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/@tauri-apps/cli-v2.10.1...@tauri-apps/cli-v2.11.2)

---
updated-dependencies:
- dependency-name: "@tauri-apps/cli"
  dependency-version: 2.11.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 05:44:08 +00:00
dependabot[bot]
d1c28f6479
chore(deps): bump the tauri group across 1 directory with 2 updates
Bumps the tauri group with 1 update in the / directory: [tauri](https://github.com/tauri-apps/tauri).


Updates `tauri` from 2.10.3 to 2.11.2
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v2.10.3...tauri-v2.11.2)

Updates `tauri-build` from 2.5.6 to 2.6.2
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-build-v2.5.6...tauri-build-v2.6.2)

---
updated-dependencies:
- dependency-name: tauri
  dependency-version: 2.11.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: tauri
- dependency-name: tauri-build
  dependency-version: 2.6.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: tauri
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 05:34:01 +00:00
anatawa12
089427cbfa
Merge pull request #2907 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/lucide-react-1.16.0
chore(deps): bump lucide-react from 1.11.0 to 1.16.0 in /vrc-get-gui
2026-05-25 14:31:21 +09:00
anatawa12
375894c6c3
Merge pull request #2894 from vrc-get/dependabot/cargo/tauri-plugin-single-instance-2.4.2
chore(deps): bump tauri-plugin-single-instance from 2.4.1 to 2.4.2
2026-05-25 14:29:15 +09:00
anatawa12
eed54886b8
Merge pull request #2895 from vrc-get/dependabot/cargo/serde_with-3.20.0
chore(deps): bump serde_with from 3.18.0 to 3.20.0
2026-05-25 14:28:31 +09:00
anatawa12
11c1b7048b
Merge pull request #2914 from vrc-get/dependabot/cargo/openssl-0.10.80
chore(deps): bump openssl from 0.10.77 to 0.10.80
2026-05-25 14:28:08 +09:00
anatawa12
b613f834ea
Merge pull request #2925 from vrc-get/dependabot/cargo/rustls-webpki-0.103.13
chore(deps): bump rustls-webpki from 0.103.12 to 0.103.13
2026-05-25 14:27:17 +09:00
anatawa12
5d9c6ce802
Merge pull request #2870 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/tailwindcss-8cf3a4f383
chore(deps-dev): bump @tailwindcss/vite from 4.2.4 to 4.3.0 in /vrc-get-gui in the tailwindcss group across 1 directory
2026-05-25 14:26:24 +09:00
anatawa12
f15e489515
Merge pull request #2868 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/i18next-50f7a7efee
chore(deps): bump the i18next group across 1 directory with 2 updates
2026-05-25 14:25:56 +09:00
anatawa12
687904918d
Merge pull request #2891 from vrc-get/dependabot/cargo/clap-4445c6f202
chore(deps): bump clap_complete from 4.6.2 to 4.6.5 in the clap group across 1 directory
2026-05-25 14:25:30 +09:00
anatawa12
c5d38a1f6e
Merge pull request #2892 from vrc-get/dependabot/cargo/tauri-plugin-dialog-2.7.1
chore(deps): bump tauri-plugin-dialog from 2.7.0 to 2.7.1
2026-05-25 14:24:45 +09:00
anatawa12
55635bbe9c
Merge pull request #2893 from vrc-get/dependabot/cargo/nix-0.31.3
chore(deps): bump nix from 0.31.2 to 0.31.3
2026-05-25 14:24:27 +09:00
anatawa12
ca30b889f1
Merge pull request #2906 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/vitejs/plugin-react-6.0.2
chore(deps-dev): bump @vitejs/plugin-react from 6.0.1 to 6.0.2 in /vrc-get-gui
2026-05-25 14:23:55 +09:00
anatawa12
0a17c9a1cd
Merge pull request #2897 from vrc-get/dependabot/cargo/tokio-1.52.3
chore(deps): bump tokio from 1.52.1 to 1.52.3
2026-05-25 14:22:42 +09:00
anatawa12
50aba0530e
Merge pull request #2899 from vrc-get/dependabot/cargo/plist-1.9.0
chore(deps): bump plist from 1.8.0 to 1.9.0
2026-05-25 14:22:14 +09:00
anatawa12
33be110c7a
Merge pull request #2900 from vrc-get/dependabot/cargo/async-compression-0.4.42
chore(deps): bump async-compression from 0.4.41 to 0.4.42
2026-05-25 14:21:54 +09:00
anatawa12
2aad318387
Merge pull request #2871 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/biomejs/biome-2.4.15
chore(deps-dev): bump @biomejs/biome from 2.4.13 to 2.4.15 in /vrc-get-gui
2026-05-25 14:20:26 +09:00
anatawa12
43ca6755ff
Merge pull request #2872 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/tailwind-merge-3.6.0
chore(deps): bump tailwind-merge from 3.5.0 to 3.6.0 in /vrc-get-gui
2026-05-25 14:19:38 +09:00
anatawa12
92bc6cf38e
Merge pull request #2901 from vrc-get/dependabot/cargo/reqwest-0.13.3
chore(deps): bump reqwest from 0.13.2 to 0.13.3
2026-05-25 14:18:28 +09:00
anatawa12
8d293e5e69
Merge pull request #2902 from vrc-get/dependabot/cargo/open-5.3.5
chore(deps): bump open from 5.3.3 to 5.3.5
2026-05-25 14:16:31 +09:00
anatawa12
1e9a100d9c
Merge pull request #2903 from vrc-get/dependabot/cargo/sysinfo-0.39.2
chore(deps): bump sysinfo from 0.38.4 to 0.39.2
2026-05-25 14:14:20 +09:00
anatawa12
8624950ca5
Merge pull request #2904 from vrc-get/dependabot/cargo/trash-5.2.6
chore(deps): bump trash from 5.2.5 to 5.2.6
2026-05-25 14:12:10 +09:00
anatawa12
57c9a54d37
Merge pull request #2918 from vrc-get/dependabot/cargo/either-1.16.0
chore(deps): bump either from 1.15.0 to 1.16.0
2026-05-25 14:10:28 +09:00
dependabot[bot]
6470169cf1
chore(deps): bump object from 0.39.0 to 0.39.1
Bumps [object](https://github.com/gimli-rs/object) from 0.39.0 to 0.39.1.
- [Changelog](https://github.com/gimli-rs/object/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gimli-rs/object/compare/v0.39.0...v0.39.1)

---
updated-dependencies:
- dependency-name: object
  dependency-version: 0.39.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 05:09:18 +00:00
dependabot[bot]
795699baf0
chore(deps): bump lucide-react from 1.11.0 to 1.16.0 in /vrc-get-gui
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 1.11.0 to 1.16.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.16.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 05:07:04 +00:00
dependabot[bot]
7fbf8c60d1
chore(deps-dev): bump @vitejs/plugin-react in /vrc-get-gui
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.2/packages/plugin-react)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 05:02:33 +00:00
anatawa12
a5c5d6e128
Merge pull request #2921 from vrc-get/dependabot/cargo/serde_json-1.0.150
chore(deps): bump serde_json from 1.0.149 to 1.0.150
2026-05-25 14:01:58 +09:00
dependabot[bot]
a3aa50256f
chore(deps): bump the i18next group across 1 directory with 2 updates
Bumps the i18next group with 2 updates in the /vrc-get-gui directory: [i18next](https://github.com/i18next/i18next) and [react-i18next](https://github.com/i18next/react-i18next).


Updates `i18next` from 26.0.8 to 26.2.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.0.8...v26.2.0)

Updates `react-i18next` from 17.0.4 to 17.0.8
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.4...v17.0.8)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 26.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: i18next
- dependency-name: react-i18next
  dependency-version: 17.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: i18next
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 05:01:37 +00:00
dependabot[bot]
498eedc1d6
chore(deps): bump tailwind-merge from 3.5.0 to 3.6.0 in /vrc-get-gui
Bumps [tailwind-merge](https://github.com/dcastil/tailwind-merge) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/dcastil/tailwind-merge/releases)
- [Commits](https://github.com/dcastil/tailwind-merge/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: tailwind-merge
  dependency-version: 3.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 05:01:32 +00:00
anatawa12
c863bc4461
Merge pull request #2919 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/tanstack/react-query-5.100.14
chore(deps): bump @tanstack/react-query from 5.100.5 to 5.100.14 in /vrc-get-gui
2026-05-25 13:58:58 +09:00
anatawa12
8fd964fa83
Merge pull request #2920 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/react-388b784d7d
chore(deps): bump the react group across 1 directory with 3 updates
2026-05-25 13:57:55 +09:00
dependabot[bot]
c7012c5e81
chore(deps): bump rustls-webpki from 0.103.12 to 0.103.13
Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.12 to 0.103.13.
- [Release notes](https://github.com/rustls/webpki/releases)
- [Commits](https://github.com/rustls/webpki/compare/v/0.103.12...v/0.103.13)

---
updated-dependencies:
- dependency-name: rustls-webpki
  dependency-version: 0.103.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 04:57:40 +00:00
anatawa12
cf6c82adbd
Merge pull request #2922 from vrc-get/dependabot/npm_and_yarn/vrc-get-gui/vite-8.0.14
chore(deps-dev): bump vite from 8.0.10 to 8.0.14 in /vrc-get-gui
2026-05-25 13:56:38 +09:00
夜嵐蝶Alma
e0931be79a chore(l10n): [zh_hant] update locale 2026-05-25 11:08:29 +08:00
dependabot[bot]
e0dce679d7
chore(deps): bump serde_json from 1.0.149 to 1.0.150
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.149 to 1.0.150.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 02:32:04 +00:00
dependabot[bot]
f9ca613378
chore(deps-dev): bump vite from 8.0.10 to 8.0.14 in /vrc-get-gui
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.10 to 8.0.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 02:32:04 +00:00
dependabot[bot]
0fa0c7ca00
chore(deps): bump clap_complete in the clap group across 1 directory
Bumps the clap group with 1 update in the / directory: [clap_complete](https://github.com/clap-rs/clap).


Updates `clap_complete` from 4.6.2 to 4.6.5
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.2...clap_complete-v4.6.5)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-version: 4.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: clap
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 02:31:57 +00:00
dependabot[bot]
9c1465868e
chore(deps): bump the react group across 1 directory with 3 updates
Bumps the react group with 3 updates in the /vrc-get-gui directory: [react](https://github.com/facebook/react/tree/HEAD/packages/react), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) and [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom).


Updates `react` from 19.2.5 to 19.2.6
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.6/packages/react)

Updates `@types/react` from 19.2.14 to 19.2.15
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-dom` from 19.2.5 to 19.2.6
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.6/packages/react-dom)

Updates `@types/react` from 19.2.14 to 19.2.15
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: "@types/react"
  dependency-version: 19.2.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: react-dom
  dependency-version: 19.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: "@types/react"
  dependency-version: 19.2.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 02:31:52 +00:00
dependabot[bot]
874492f3fc
chore(deps): bump @tanstack/react-query in /vrc-get-gui
Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.100.5 to 5.100.14.
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.100.14/packages/react-query)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.100.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 02:31:50 +00:00
dependabot[bot]
8588a7276c
chore(deps-dev): bump @tailwindcss/vite
Bumps the tailwindcss group with 1 update in the /vrc-get-gui directory: [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite).


Updates `@tailwindcss/vite` from 4.2.4 to 4.3.0
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/@tailwindcss-vite)

---
updated-dependencies:
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: tailwindcss
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 02:31:47 +00:00
dependabot[bot]
0a5b9c38eb
chore(deps): bump either from 1.15.0 to 1.16.0
Bumps [either](https://github.com/rayon-rs/either) from 1.15.0 to 1.16.0.
- [Commits](https://github.com/rayon-rs/either/compare/1.15.0...1.16.0)

---
updated-dependencies:
- dependency-name: either
  dependency-version: 1.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 02:31:40 +00:00
anatawa12
17a28fa609
Merge pull request #2916 from vrc-get/workaround-world-sdk
chore: workaround VRCDefaultWorldScene generation issue
2026-05-24 02:09:01 +09:00
anatawa12
099883a33d
docs(changelog): Added workaround for VRCDefaultWorldScene generation issue in SDK 3.10.2 or later 2026-05-24 01:49:55 +09:00
anatawa12
5cb60c4002
chore: workaround VRCDefaultWorldScene generation issue
workarounds https://feedback.vrchat.com/sdk-bug-reports/p/3102-3103-vrcscenetemplateinitializer-does-not-create-sample-scene-if-udon-prepr
2026-05-24 01:45:01 +09:00
anatawa12
cdb1b8294d
Merge pull request #2915 from vrc-get/update_locale_ja
chore(l10n): [ja] update locale
2026-05-23 23:43:50 +09:00
Sayamame-beans
0de963736f chore(l10n): [ja] update locale 2026-05-23 02:33:25 +09:00
anatawa12
0026f5029c
Merge pull request #2731 from lonelyicer/feat/show-hidden-packages
feat: show hidden packages
2026-05-22 18:26:18 +09:00
dependabot[bot]
987357354e
chore(deps): bump openssl from 0.10.77 to 0.10.80
Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.77 to 0.10.80.
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.77...openssl-v0.10.80)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.80
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 06:41:01 +00:00
anatawa12
f13add3409
Merge pull request #2888 from vrc-get/fix-updater
Fix updater.exe
2026-05-22 15:39:37 +09:00
anatawa12
2d7fefb34c
fix: shlwapi.dll is not part of static link checker 2026-05-22 09:19:44 +09:00
RingLo_
e625630856
feat: show list of sources that we should enable 2026-05-20 05:01:37 +08:00
anatawa12
4943a671e0
fix: compile error 2026-05-19 00:31:16 +09:00
dependabot[bot]
6e63cdba25
chore(deps): bump trash from 5.2.5 to 5.2.6
Bumps [trash](https://github.com/ArturKovacs/trash) from 5.2.5 to 5.2.6.
- [Release notes](https://github.com/ArturKovacs/trash/releases)
- [Changelog](https://github.com/Byron/trash-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ArturKovacs/trash/compare/v5.2.5...v5.2.6)

---
updated-dependencies:
- dependency-name: trash
  dependency-version: 5.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:26:43 +00:00
dependabot[bot]
e04d1ee919
chore(deps): bump sysinfo from 0.38.4 to 0.39.2
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.38.4 to 0.39.2.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.38.4...v0.39.2)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-version: 0.39.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:26:23 +00:00
dependabot[bot]
a4992e3973
chore(deps): bump open from 5.3.3 to 5.3.5
Bumps [open](https://github.com/Byron/open-rs) from 5.3.3 to 5.3.5.
- [Release notes](https://github.com/Byron/open-rs/releases)
- [Changelog](https://github.com/Byron/open-rs/blob/main/changelog.md)
- [Commits](https://github.com/Byron/open-rs/compare/v5.3.3...v5.3.5)

---
updated-dependencies:
- dependency-name: open
  dependency-version: 5.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:26:01 +00:00
dependabot[bot]
185b7aa8b7
chore(deps): bump reqwest from 0.13.2 to 0.13.3
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.13.2 to 0.13.3.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.13.2...v0.13.3)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-version: 0.13.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:25:48 +00:00
dependabot[bot]
24ae2b33de
chore(deps): bump async-compression from 0.4.41 to 0.4.42
Bumps [async-compression](https://github.com/Nullus157/async-compression) from 0.4.41 to 0.4.42.
- [Release notes](https://github.com/Nullus157/async-compression/releases)
- [Commits](https://github.com/Nullus157/async-compression/compare/async-compression-v0.4.41...async-compression-v0.4.42)

---
updated-dependencies:
- dependency-name: async-compression
  dependency-version: 0.4.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:25:32 +00:00
dependabot[bot]
1b928b150b
chore(deps): bump plist from 1.8.0 to 1.9.0
Bumps [plist](https://github.com/ebarnard/rust-plist) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/ebarnard/rust-plist/releases)
- [Changelog](https://github.com/ebarnard/rust-plist/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ebarnard/rust-plist/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: plist
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:25:19 +00:00
dependabot[bot]
2d859d993e
chore(deps): bump tokio from 1.52.1 to 1.52.3
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.1 to 1.52.3.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.1...tokio-1.52.3)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.52.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:24:49 +00:00
dependabot[bot]
4018f718a7
chore(deps): bump serde_with from 3.18.0 to 3.20.0
Bumps [serde_with](https://github.com/jonasbb/serde_with) from 3.18.0 to 3.20.0.
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.18.0...v3.20.0)

---
updated-dependencies:
- dependency-name: serde_with
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:24:21 +00:00
dependabot[bot]
de73063b98
chore(deps): bump tauri-plugin-single-instance from 2.4.1 to 2.4.2
Bumps [tauri-plugin-single-instance](https://github.com/tauri-apps/plugins-workspace) from 2.4.1 to 2.4.2.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](https://github.com/tauri-apps/plugins-workspace/compare/fs-v2.4.1...fs-v2.4.2)

---
updated-dependencies:
- dependency-name: tauri-plugin-single-instance
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:24:08 +00:00
dependabot[bot]
d6ea9b3079
chore(deps): bump nix from 0.31.2 to 0.31.3
Bumps [nix](https://github.com/nix-rust/nix) from 0.31.2 to 0.31.3.
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.31.2...v0.31.3)

---
updated-dependencies:
- dependency-name: nix
  dependency-version: 0.31.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:23:53 +00:00
dependabot[bot]
eaf09ecb6a
chore(deps): bump tauri-plugin-dialog from 2.7.0 to 2.7.1
Bumps [tauri-plugin-dialog](https://github.com/tauri-apps/plugins-workspace) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](https://github.com/tauri-apps/plugins-workspace/compare/log-v2.7.0...log-v2.7.1)

---
updated-dependencies:
- dependency-name: tauri-plugin-dialog
  dependency-version: 2.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 12:23:37 +00:00
RingLo_
d74c7b16b8
chore: use visibleSources in package list and add tooltip 2026-05-18 12:04:38 +08:00
RingLo_
6c46983cae
Merge branch 'master' into feat/show-hidden-packages 2026-05-18 11:40:16 +08:00
anatawa12
36b76933fc
docs(changelog-gui): add pr number to changelog entry 2026-05-17 03:11:52 +09:00
anatawa12
ad7efa259e
Merge pull request #2887 from fivezett/fix-toastify-dark-theme
fix(style): keep react-toastify toasts in sync with dark mode
2026-05-17 03:08:19 +09:00
anatawa12
97042ebc6a
chore: add windows manifest resource to disable Installer detection technology 2026-05-17 03:03:34 +09:00
anatawa12
e135092745
fix: installer file should always have .exe extension 2026-05-17 02:58:19 +09:00
fivezett
5607daf7b5 fix(style): keep react-toastify toasts in sync with dark mode
react-toastify 11.1.0 changed its CSS injection to append the <style>
to the end of <head> on mount, so its `:root` variable defaults
started overriding ours by source order, and toasts stopped following
the dark theme.

Declaring the `--toastify-*` overrides on `body` instead of `:root`
makes toasts inherit them regardless of stylesheet order, since
react-toastify only declares these variables on `:root`.
2026-05-17 01:51:08 +09:00
github-actions[bot]
2861fcaa68 gui v1.1.6-beta.1 2026-05-15 15:15:00 +00:00
anatawa12
8a71e426d6
Merge pull request #2882 from vrc-get/additional-headers-for-future-extension
feat: include version number and os in the header for future extension
2026-05-15 22:25:20 +09:00
anatawa12
cd242e389b
docs(changelog): add pr number to changelog 2026-05-15 22:14:36 +09:00
anatawa12
95dc46666b
feat: include version number and os in the header for future extension 2026-05-15 22:12:32 +09:00
anatawa12
f0c4de7d78
Merge pull request #2881 from vrc-get/updater-windows-fixes
windows updater fixes
2026-05-15 22:06:32 +09:00
anatawa12
e9a994a932
Merge pull request #2880 from fivezett/createProject1RowLoading
style: align creating project dialog spinner and label on one row
2026-05-15 19:28:46 +09:00
anatawa12
803fbf3395
fix: non-windows platform build failure 2026-05-15 19:22:05 +09:00
anatawa12
20d24b496b
docs(changelog): add pr number to changelog entry 2026-05-15 19:21:52 +09:00
anatawa12
a1ff52fed4
fix: updater json does not include args 2026-05-15 19:18:56 +09:00
anatawa12
4a99cfe4c9
Merge branch 'master' into createProject1RowLoading 2026-05-15 17:31:17 +09:00
fivezett
8b13244b74 style: align creating project dialog spinner and label on one row 2026-05-15 17:24:50 +09:00
anatawa12
875a989d52
fix: failure from ShellExecuteW is not handled 2026-05-15 15:19:05 +09:00
anatawa12
973c91c9c0
fix: close file before launching installer 2026-05-15 15:16:45 +09:00
anatawa12
3d2dd2e058
Merge pull request #2875 from vrc-get/setup-exe-in-zip
fix: setup.exe.zip is unexpectedly unavailable
2026-05-15 03:50:00 +09:00
anatawa12
16d8a787ec
Merge pull request #2876 from vrc-get/debug-only-updater-override
feat: debug-only updater overrides
2026-05-15 03:49:46 +09:00
anatawa12
4ccc7d7bc8
feat: debug-only updater overrides 2026-05-14 14:58:29 +09:00
anatawa12
8c8315f57d
fix: setup.exe.zip is unexpectedly unavailable 2026-05-14 14:47:04 +09:00
anatawa12
4cac964713
Merge pull request #2867 from vrc-get/copy-parallelism-limit
fix: too many open files when copying project
2026-05-14 00:54:16 +09:00
anatawa12
fb32bd97cd
docs(changelog-gui): Too many open files when copying project 2026-05-14 00:33:41 +09:00
anatawa12
1a70d2db82
Merge pull request #2851 from vrc-get/datailed-error-message
chore: much detailed error message
2026-05-14 00:32:27 +09:00
dependabot[bot]
cacb9a5e9a
chore(deps-dev): bump @biomejs/biome in /vrc-get-gui
Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.4.13 to 2.4.15.
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.15/packages/@biomejs/biome)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.4.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 09:56:18 +00:00
anatawa12
4700bfa2c0
fix: too many open files when copying project 2026-05-08 20:41:53 +09:00
anatawa12
223be38368
Merge pull request #2850 from vrc-get/unity-hub
fix: Unity from editors-v2.json may not be recognized
2026-05-02 14:54:02 +09:00
anatawa12
523ede22ea
docs(changelog): New Unity Hub loading method fixes 2026-05-02 14:47:58 +09:00
anatawa12
38a7649fbd
Merge pull request #2852 from vrc-get/copilot/fix-typo-in-documentation
Fix typo in SHA256 hash mismatch error message
2026-05-02 03:09:38 +09:00
anatawa12
d61b46ded8
fix: incorrect configuration dir for unity hub on linux 2026-05-02 00:54:33 +09:00
copilot-swe-agent[bot]
4756f6c115
fix: fix typo in SHA256 hash error message (his -> This)
Agent-Logs-Url: https://github.com/vrc-get/vrc-get/sessions/bea04e30-e11e-4933-9ea9-2b563af74d47

Co-authored-by: anatawa12 <22656849+anatawa12@users.noreply.github.com>
2026-05-01 02:19:14 +00:00
anatawa12
e14806c72c
chore: much detailed error message 2026-05-01 09:56:02 +09:00
anatawa12
8324a4ec04
fix: Unity from editors-v2.json may not be recognized 2026-05-01 09:29:07 +09:00
anatawa12
a9f2a9713f
Merge pull request #2849 from vrc-get/enforce-zip-hash
chore!: package hash check is now enforced when installing package
2026-04-30 19:22:18 +09:00
anatawa12
40cd4487d5
Merge pull request #2848 from vrc-get/improve-error-messages
fix: fix "[object Object]" appearing as an error message
2026-04-30 10:33:55 +09:00
anatawa12
56a2139df6
docs(changelog): Package hash checks are now enforced when installing packages 2026-04-30 10:00:48 +09:00
anatawa12
175008956c
style: biome format fix 2026-04-30 09:58:55 +09:00
anatawa12
00a928e58e
chore!: package hash check is now enforced when installing package 2026-04-30 09:56:17 +09:00
anatawa12
7037a758bc
docs(changelog): Uninformative [object Object] appearing as an error message 2026-04-30 09:39:27 +09:00
anatawa12
f457ffcc69
fix: fix "[object Object]" appearing as an error message
We show JSON instead
2026-04-30 09:24:48 +09:00
anatawa12
9291853c19
Merge pull request #2847 from JustBuddy/german-translation
chore(l10n): [de_DE] update locale
2026-04-30 01:08:27 +09:00
JustBuddy
0e6d011ee5
chore(l10n): [de_DE] update locale 2026-04-29 17:34:45 +02:00
anatawa12
be5c9aa765
Merge pull request #2846 from vrc-get/copilot/fix-template-export-extension-issue
Fix missing file extensions in save dialog default names
2026-04-29 23:11:06 +09:00
anatawa12
b090c440ac
Merge pull request #2845 from vrc-get/backslash-in-path
feat: replace backslashes with slash extracting zip on unix
2026-04-29 22:54:29 +09:00
copilot-swe-agent[bot]
3c51a2de31
docs: add changelog entry for PR #2846
Agent-Logs-Url: https://github.com/vrc-get/vrc-get/sessions/379a1c4c-6953-48f9-86f7-1c0134e08af9

Co-authored-by: anatawa12 <22656849+anatawa12@users.noreply.github.com>
2026-04-29 13:51:37 +00:00
copilot-swe-agent[bot]
04fa1f8244
fix: append file extension to default file names in save dialogs
Agent-Logs-Url: https://github.com/vrc-get/vrc-get/sessions/379a1c4c-6953-48f9-86f7-1c0134e08af9

Co-authored-by: anatawa12 <22656849+anatawa12@users.noreply.github.com>
2026-04-29 13:51:17 +00:00
anatawa12
35b26d9322
docs(changelog): Backslashes in path in zip file are now treated as path separator on unix 2026-04-29 22:47:45 +09:00
copilot-swe-agent[bot]
478c25b03a
Initial plan 2026-04-29 13:47:25 +00:00
anatawa12
20e5836985
feat: replace backslashes with slash extracting zip on unix 2026-04-29 22:32:17 +09:00
RingLo_
c8b0cf89c8
chore: fix type 2026-04-20 11:31:22 +08:00
RingLo_
911c0a5e51
feat: always show installed packages
also hide hidden sources
2026-04-20 11:22:51 +08:00
RingLo_
8773f3d3e6
Merge branch 'master' into feat/show-hidden-packages 2026-04-20 11:01:27 +08:00
RingLo_
40d0786b19
chore: clean code 2026-03-20 08:02:05 +08:00
RingLo_
7fdd1192e1
docs(changelog): update for #2731 2026-03-20 07:54:27 +08:00
RingLo_
d09244291f
feat: show hide packages 2026-03-20 07:47:40 +08:00
80 changed files with 3027 additions and 1647 deletions

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
github: [anatawa12]
custom: [https://booth.pm/ja/items/6448396]

View file

@ -16,7 +16,7 @@ jobs:
include:
- triple: x86_64-unknown-linux-gnu
on: ubuntu-22.04
bundles: appimage,appimage-updater,deb,rpm
bundles: appimage,appimage-updater
setup: |
sudo apt update && sudo apt install -y lld
ld.lld --version
@ -26,7 +26,7 @@ jobs:
- triple: x86_64-pc-windows-msvc
on: windows-latest
bundles: setup-exe,exe-updater
bundles: setup-exe,setup-exe-zip,exe-updater
- triple: universal-apple-darwin
on: macos-14
@ -122,6 +122,195 @@ jobs:
target/${{ matrix.triple }}/release/bundle/*/ALCOM*
target/${{ matrix.triple }}/release/bundle/*/alcom*
build-rpm:
strategy:
fail-fast: false
matrix:
include:
- install_rust: false
- no_dist: false
- mock-env: fedora-40-x86_64
install_rust: true
no_dist: true
mock-env:
- fedora-40-x86_64
- fedora-rawhide-x86_64
runs-on: ubuntu-latest
container:
image: 'fedora:latest'
options: --privileged
env:
MOCK_ENV: ${{ matrix.mock-env }}
RPMBUILD_OPTS: ${{ case(matrix.no_dist, '-D "dist %{nil}"', '') }} ${{ case(matrix.install_rust, '-D "install_rust 1"', '') }}
steps:
- name: Install CI dependencies
run: dnf install -y git tar curl
- uses: actions/checkout@v6
with:
submodules: recursive
# https://github.com/actions/checkout/issues/1169
- run: git config --system --add safe.directory $GITHUB_WORKSPACE
- name: install dependencies
run: dnf install -y mock rpmbuild
- name: prepare rpm build environment
run: mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
- name: update version name in spec file
run: |
COMMIT_HASH="$(git rev-parse HEAD)"
SHORT_HASH="$(git rev-parse --short HEAD)"
PKG_VERSION="$(<vrc-get-gui/Cargo.toml sed -En -e '/^version/{s/.*"(.*)".*/\1/p;}')+${SHORT_HASH}"
echo "PKG_VERSION=$PKG_VERSION" >> $GITHUB_ENV
cp vrc-get-gui/Cargo.toml vrc-get-gui/Cargo.toml.bak
sed -E "/^version/s/\"$/+$(git rev-parse --short HEAD)\"/" < vrc-get-gui/Cargo.toml.bak > vrc-get-gui/Cargo.toml
rm vrc-get-gui/Cargo.toml.bak
git add vrc-get-gui/Cargo.toml
sed -i vrc-get-gui/bundle/alcom.spec -e "/^Version:/c\Version: ${PKG_VERSION//-/\~}"
- name: build source rpm package
run: |
git archive --format=tar --prefix=vrc-get-gui-v$PKG_VERSION/ $(git write-tree) | gzip > ~/rpmbuild/SOURCES/gui-v$PKG_VERSION.tar.gz
eval "rpmbuild -bs vrc-get-gui/bundle/alcom.spec $RPMBUILD_OPTS"
- name: build rpm package
run: eval "mock -v -r '$(ls -1 /etc/mock{/eol,}/$MOCK_ENV.cfg 2>/dev/null)' --enable-network $RPMBUILD_OPTS rebuild ~/rpmbuild/SRPMS/alcom-${PKG_VERSION//-/\~}-1*.src.rpm"
- name: copy built binaries
run: |
mkdir -p artifacts
cp ~/rpmbuild/SRPMS/alcom-${PKG_VERSION//-/\~}-1*.src.rpm artifacts/
cp /var/lib/mock/$MOCK_ENV/result/alcom-${PKG_VERSION//-/\~}-1*.${MOCK_ENV##*-}.rpm artifacts/
- name: Upload built binary
uses: actions/upload-artifact@v7
with:
name: rpm-${{ matrix.mock-env }}
path: artifacts/*
build-deb:
strategy:
fail-fast: false
matrix:
include:
- install_rust: false
- install_nodejs: false
- apt-components: main
- apt-with-updates: false
# Old distributions have older tools than we need. Download tools in build process
- pbuilder-distribution: bookworm
install_rust: true
install_nodejs: true
- pbuilder-distribution: jammy
install_rust: true
install_nodejs: true
# Debian uses mirror from debian-archive.trafficmanager.net which is managed by microsoft on azure
- pbuilder-distribution: bookworm
mirror: http://debian-archive.trafficmanager.net/debian/
keyring: /usr/share/keyrings/debian-archive-keyring.gpg
- pbuilder-distribution: sid
mirror: http://debian-archive.trafficmanager.net/debian/
keyring: /usr/share/keyrings/debian-archive-keyring.gpg
# Ubuntu legacy release
- pbuilder-distribution: jammy
mirror: http://archive.ubuntu.com/ubuntu/
apt-components: main universe
apt-with-updates: true
keyring: /usr/share/keyrings/ubuntu-archive-keyring.gpg
# For note,
# bookworm: libc6@2.36
# sid: libc6@2.39 as of 2026/06/14
# jammy: libc6@2.35
pbuilder-distribution:
# debian distribution
- bookworm
- sid
# ubuntu distribution
- jammy # jammy is the oldest ubuntu release with libwebkit2gtk-4.1 >= 2.41 (but requires -updates and universe)
target-arch:
- amd64
runs-on: ubuntu-latest
env:
TARGET_ARCH: ${{ matrix.target-arch }}
PBUILDER_DISTRIBUTION: ${{ matrix.pbuilder-distribution }}
PBUILDER_MIRROR: ${{ matrix.mirror }}
PBUILDER_KEYRING: ${{ matrix.keyring }}
PBUILDER_COMPONENTS: ${{ matrix.apt-components }}
PBUILDER_OTHERMIRROR: ${{ case(matrix.apt-with-updates, format('deb {0} {1}-updates {2}', matrix.mirror, matrix.pbuilder-distribution, matrix.apt-components), '') }}
INSTALL_RUST: ${{ case(matrix.install_rust, '1', '0') }}
INSTALL_NODEJS: ${{ case(matrix.install_nodejs, '1', '0') }}
steps:
- uses: actions/checkout@v6
with:
path: vrc-get
submodules: recursive
- name: install dependencies
run: sudo apt update && sudo apt install -y pbuilder debian-archive-keyring debhelper-compat=13
- name: prepare deb build environment
working-directory: vrc-get
run: |
cp -r vrc-get-gui/bundle/debian debian
sudo pbuilder create \
--architecture "$TARGET_ARCH" \
--keyring "$PBUILDER_KEYRING" \
--mirror "$PBUILDER_MIRROR" \
--distribution "$PBUILDER_DISTRIBUTION" \
--components "$PBUILDER_COMPONENTS" \
--othermirror "$PBUILDER_OTHERMIRROR"
- name: update changelog
working-directory: vrc-get
run: |
COMMIT_HASH="$(git rev-parse HEAD)"
SHORT_HASH="$(git rev-parse --short HEAD)"
PKG_VERSION="$(<vrc-get-gui/Cargo.toml sed -En -e '/^version/{s/.*"(.*)".*/\1/p;}')+${SHORT_HASH}"
echo "PKG_VERSION=$PKG_VERSION" >> $GITHUB_ENV
cp debian/changelog debian/changelog.bak
cat - debian/changelog.bak <<CHANGELOG > debian/changelog
alcom (${PKG_VERSION//-/\~}-1) experimental;
* Upgraded version to ${PKG_VERSION}
-- anatawa12 <i@anatawa12.com> $(date -u +"%a, %d %b %Y %H:%M:%S +0000")
CHANGELOG
rm debian/changelog.bak
cp vrc-get-gui/Cargo.toml vrc-get-gui/Cargo.toml.bak
sed -E "/^version/s/\"$/+$(git rev-parse --short HEAD)\"/" < vrc-get-gui/Cargo.toml.bak > vrc-get-gui/Cargo.toml
rm vrc-get-gui/Cargo.toml.bak
git add vrc-get-gui/Cargo.toml
echo cat debian/changelog
cat debian/changelog
dpkg-parsechangelog
- name: build source deb package
working-directory: vrc-get
run: |
git archive --format=tar $(git write-tree) | xz > ../alcom_${PKG_VERSION//-/\~}.orig.tar.xz
dpkg-buildpackage -d -S
- name: build deb package
working-directory: vrc-get
run: |
sudo --preserve-env=INSTALL_RUST,INSTALL_NODEJS pbuilder build --use-network yes ../alcom_${PKG_VERSION//-/\~}-1.dsc
- name: copy built binaries
run: |
mkdir -p artifacts
cp vrc-get/debian/changelog artifacts/
cp /var/cache/pbuilder/result/alcom_${PKG_VERSION//-/\~}* artifacts/
ls artifacts
- name: Print information about built package
run: dpkg-deb -I artifacts/alcom_${PKG_VERSION//-/\~}-1_$TARGET_ARCH.deb
- name: Upload built binary
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: deb-${{ matrix.pbuilder-distribution }}-${{ matrix.target-arch }}
path: artifacts/*
conclude-gui:
runs-on: ubuntu-latest
if: ${{ always() }}

View file

@ -67,6 +67,21 @@ jobs:
;;
esac
GUI_VERSION="$(get-version -t gui)"
# update debian package
cp vrc-get-gui/bundle/debian/changelog vrc-get-gui/bundle/debian/changelog.bak
cat - vrc-get-gui/bundle/debian/changelog.bak <<CHANGELOG > vrc-get-gui/bundle/debian/changelog
alcom (${GUI_VERSION//-/\~}-1) stable;
* Upgraded version to ${GUI_VERSION}
-- anatawa12 <i@anatawa12.com> $(date -u +"%a, %d %b %Y %H:%M:%S +0000")
CHANGELOG
rm vrc-get-gui/bundle/debian/changelog.bak
# update rpm package
sed -i vrc-get-gui/bundle/alcom.spec -e "/^Version:/c\Version: ${GUI_VERSION//-/\~}"
case "$GITHUB_REF_NAME" in
master | master-* )
echo "head is master or master-*"
@ -81,7 +96,7 @@ jobs:
;;
esac
gh-export-variable GUI_VERSION "$(get-version -t gui)"
gh-export-variable GUI_VERSION "${GUI_VERSION}"
env:
RELEASE_KIND_IN: ${{ inputs.release_kind }}
DRY_RUN: ${{ inputs.dry-run }}
@ -152,29 +167,15 @@ jobs:
bundle/appimage/ALCOM_${GUI_VERSION}_x86_64.AppImage.tar.gz:alcom-${GUI_VERSION}-x86_64.AppImage.tar.gz
bundle/appimage/ALCOM_${GUI_VERSION}_x86_64.AppImage.tar.gz.sig:alcom-${GUI_VERSION}-x86_64.AppImage.tar.gz.sig
- name: x86_64-linux-package
triple: x86_64-unknown-linux-gnu
on: ubuntu-22.04
setup: |
sudo apt update && sudo apt install -y lld
ld.lld --version
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
rustflags: "-C link-arg=-fuse-ld=lld"
alcom-build-options: --no-self-updater --updater-instruction-message "en=You can download newer package from official website."
last-bundles: deb,rpm
dist-path: |
bundle/deb/alcom_${GUI_VERSION}-1_amd64.deb:alcom_${GUI_VERSION}-1_amd64.deb
bundle/rpm/alcom-${GUI_VERSION}-1.x86_64.rpm:alcom-${GUI_VERSION}-1.x86_64.rpm
- name: x86_64-windows-all
triple: x86_64-pc-windows-msvc
on: windows-2022
last-bundles: exe-updater
last-bundles: setup-exe-zip,exe-updater
updater-bundle: bundle/setup/alcom-updater.exe
dist-path: |
ALCOM.exe:ALCOM-${GUI_VERSION}-x86_64.exe
bundle/setup/alcom-setup.exe:ALCOM-${GUI_VERSION}-x86_64-setup.exe
bundle/setup/alcom-setup.exe.zip:ALCOM-${GUI_VERSION}-x86_64-setup.exe.zip
bundle/setup/alcom-updater.exe:ALCOM-${GUI_VERSION}-x86_64-updater.exe
bundle/setup/alcom-updater.exe.sig:ALCOM-${GUI_VERSION}-x86_64-updater.exe.sig
@ -195,8 +196,6 @@ jobs:
name:
- x86_64-linux-appimage
#- aarch64-unknown-linux-musl
- x86_64-linux-package
#- aarch64-linux-package
- x86_64-windows-all
#- aarch64-windows-all
- universal-macos-all
@ -273,11 +272,6 @@ jobs:
- name: Bundle ALCOM (${{ matrix.last-bundles }})
run: cargo xtask bundle-alcom --target ${{ matrix.triple }} --release --bundles ${{ matrix.last-bundles }}
- name: Bundle ALCOM Updater (Windows)
if: ${{ contains(matrix.name, 'windows') }}
shell: bash
run: cargo xtask bundle-alcom --target ${{ matrix.triple }} --release --bundles exe-updater
- name: Sign updater artifacts (All Platforms)
shell: bash
if: ${{ matrix.updater-bundle }}
@ -291,6 +285,7 @@ jobs:
cargo xtask sign-alcom-updater "target/${{ matrix.triple }}/release/${UPDATER_BUNDLE}"
- name: Move artifacts
if: ${{ !cancelled() }}
shell: bash
env:
GUI_VERSION: ${{ needs.pre-build.outputs.gui-version }}
@ -309,10 +304,154 @@ jobs:
done
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: artifacts-${{ matrix.name }}
path: artifacts/*
build-rpm:
needs: [ pre-build ]
strategy:
fail-fast: false
matrix:
include:
- install_rust: false
- no_dist: false
- mock-env: fedora-40-x86_64
install_rust: true
no_dist: true
mock-env:
- fedora-40-x86_64
runs-on: ubuntu-latest
container:
image: 'fedora:latest'
options: --privileged
env:
MOCK_ENV: ${{ matrix.mock-env }}
RPMBUILD_OPTS: ${{ case(matrix.no_dist, '-D "dist %{nil}"', '') }} ${{ case(matrix.install_rust, '-D "install_rust 1"', '') }}
PKG_VERSION: ${{ needs.pre-build.outputs.gui-version }}
steps:
- name: Install CI dependencies
run: dnf install -y git tar curl
- uses: actions/checkout@v6
with:
ref: 'releasing'
submodules: recursive
# https://github.com/actions/checkout/issues/1169
- run: git config --system --add safe.directory $GITHUB_WORKSPACE
- name: install dependencies
run: dnf install -y mock rpmbuild
- name: prepare rpm build environment
run: mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
- name: build source rpm package
run: |
git archive --format=tar --prefix=vrc-get-gui-v$PKG_VERSION/ $(git write-tree) | gzip > ~/rpmbuild/SOURCES/gui-v$PKG_VERSION.tar.gz
eval "rpmbuild -bs vrc-get-gui/bundle/alcom.spec $RPMBUILD_OPTS"
- name: build rpm package
run: eval "mock -v -r '$(ls -1 /etc/mock{/eol,}/$MOCK_ENV.cfg 2>/dev/null)' --enable-network $RPMBUILD_OPTS rebuild ~/rpmbuild/SRPMS/alcom-${PKG_VERSION//-/\~}-1*.src.rpm"
- name: copy built binaries
run: |
mkdir -p artifacts
cp ~/rpmbuild/SRPMS/alcom-${PKG_VERSION//-/\~}-1*.src.rpm artifacts/
cp /var/lib/mock/$MOCK_ENV/result/alcom-${PKG_VERSION//-/\~}-1*.${MOCK_ENV##*-}.rpm artifacts/
- name: Upload built binary
uses: actions/upload-artifact@v7
with:
name: artifacts-rpm-${{ matrix.mock-env }}
path: artifacts/*
build-deb:
needs: [ pre-build ]
strategy:
fail-fast: false
matrix:
include:
- install_rust: false
- install_nodejs: false
- apt-components: main
- apt-with-updates: false
# Old distributions have older tools than we need. Download tools in build process
- pbuilder-distribution: jammy
install_rust: true
install_nodejs: true
# Debian uses mirror from debian-archive.trafficmanager.net which is managed by microsoft on azure
- pbuilder-distribution: jammy
mirror: http://archive.ubuntu.com/ubuntu/
apt-components: main universe
apt-with-updates: true
keyring: /usr/share/keyrings/ubuntu-archive-keyring.gpg
# We build on jammy since it's the distribution with a) libwebkit2gtk-4.1 >= 2.41 and 2) oldest libc version required.
# bookworm: libc6@2.36
# sid: libc6@2.39 as of 2026/06/14
# jammy: libc6@2.35
pbuilder-distribution:
- jammy # jammy is the oldest ubuntu release with libwebkit2gtk-4.1 >= 2.41 (but requires -updates and universe)
target-arch:
- amd64
runs-on: ubuntu-latest
env:
TARGET_ARCH: ${{ matrix.target-arch }}
PBUILDER_DISTRIBUTION: ${{ matrix.pbuilder-distribution }}
PBUILDER_MIRROR: ${{ matrix.mirror }}
PBUILDER_KEYRING: ${{ matrix.keyring }}
PBUILDER_COMPONENTS: ${{ matrix.apt-components }}
PBUILDER_OTHERMIRROR: ${{ case(matrix.apt-with-updates, format('deb {0} {1}-updates {2}', matrix.mirror, matrix.pbuilder-distribution, matrix.apt-components), '') }}
INSTALL_RUST: ${{ case(matrix.install_rust, '1', '0') }}
INSTALL_NODEJS: ${{ case(matrix.install_nodejs, '1', '0') }}
PKG_VERSION: ${{ needs.pre-build.outputs.gui-version }}
steps:
- uses: actions/checkout@v6
with:
ref: 'releasing'
path: vrc-get
submodules: recursive
- name: install dependencies
run: sudo apt update && sudo apt install -y pbuilder debian-archive-keyring debhelper-compat=13
- name: prepare deb build environment
working-directory: vrc-get
run: |
( mkdir debian && cd debian && ln -s ../vrc-get-gui/bundle/debian/* . )
sudo pbuilder create \
--architecture "$TARGET_ARCH" \
--keyring "$PBUILDER_KEYRING" \
--mirror "$PBUILDER_MIRROR" \
--distribution "$PBUILDER_DISTRIBUTION" \
--components "$PBUILDER_COMPONENTS" \
--othermirror "$PBUILDER_OTHERMIRROR"
- name: build source deb package
working-directory: vrc-get
run: |
git archive --format=tar HEAD | xz > ../alcom_${PKG_VERSION//-/\~}.orig.tar.xz
dpkg-buildpackage -d -S
- name: build deb package
working-directory: vrc-get
run: |
sudo --preserve-env=INSTALL_RUST,INSTALL_NODEJS pbuilder build --use-network yes ../alcom_${PKG_VERSION//-/\~}-1.dsc
- name: copy built binaries
run: |
mkdir -p artifacts
cp /var/cache/pbuilder/result/alcom_${PKG_VERSION//-/\~}-1_$TARGET_ARCH.deb artifacts/
cp /var/cache/pbuilder/result/alcom_${PKG_VERSION//-/\~}-1_$TARGET_ARCH.buildinfo artifacts/
cp /var/cache/pbuilder/result/alcom_${PKG_VERSION//-/\~}-1_$TARGET_ARCH.changes artifacts/
cp /var/cache/pbuilder/result/alcom_${PKG_VERSION//-/\~}-1.debian.tar.xz artifacts/
cp /var/cache/pbuilder/result/alcom_${PKG_VERSION//-/\~}-1.dsc artifacts/
ls artifacts
- name: Print information about built package
run: dpkg-deb -I artifacts/alcom_${PKG_VERSION//-/\~}-1_$TARGET_ARCH.deb
- name: Upload built binary
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: artifacts-deb-${{ matrix.pbuilder-distribution }}-${{ matrix.target-arch }}
path: artifacts/*
build-updater-json:
runs-on: ubuntu-latest
needs: [ pre-build, build-rust ]
@ -349,7 +488,7 @@ jobs:
permissions:
contents: write
runs-on: ubuntu-latest
needs: [ pre-build, build-rust, build-updater-json ]
needs: [ pre-build, build-rust, build-rpm, build-deb, build-updater-json ]
env:
GUI_VERSION: ${{ needs.pre-build.outputs.gui-version }}
steps:

View file

@ -12,6 +12,11 @@ on:
- prerelease
- start-rc
- stable
dry-run:
type: boolean
description: Dry Run, If true, do not publish release to GitHub.
default: true
required: false
concurrency:
group: releasing
@ -70,8 +75,12 @@ jobs:
echo "head is master, master-*, or hotfix-*"
;;
* )
echo "invalid release kind: $RELEASE_KIND_IN is not allowd for $GITHUB_REF_NAME"
exit 255
if [ "$DRY_RUN" = "true" ]; then
echo "head is not master, but DRY_RUN is true"
else
echo "head is not master, but DRY_RUN is false"
exit 255
fi
;;
esac
@ -79,6 +88,7 @@ jobs:
gh-export-variable VPM_VERSION "$(get-version -t vpm)"
env:
RELEASE_KIND_IN: ${{ github.event.inputs.release_kind }}
DRY_RUN: ${{ inputs.dry-run }}
# region changelog
- name: Create Changelog
@ -189,10 +199,13 @@ jobs:
run: cargo build --target ${{ matrix.triple }} --release --verbose
- name: Check binary is statically linked
shell: bash
env:
RUSTFLAGS: ''
run: |
cargo xtask check-static-link target/${{ matrix.triple }}/release/vrc-get${WINDIR:+.exe}
- name: Move artifacts
if: ${{ !cancelled() }}
shell: bash
run: |-
mkdir artifacts
@ -203,12 +216,14 @@ jobs:
popd
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: artifacts-${{ matrix.triple }}
path: artifacts/*
publish-crates-io:
name: Publish to crates.io
if: ${{ !inputs.dry-run }}
environment:
name: crates.io
url: https://crates.io/crates/vrc-get
@ -233,6 +248,7 @@ jobs:
publish-to-github:
name: Publish to GitHub
if: ${{ !inputs.dry-run }}
environment:
name: actions-github-app
url: https://github.com/anatawa12/vrc-get/releases/v${{ needs.pre-build.outputs.cli-version }}
@ -337,7 +353,7 @@ jobs:
publish-to-homebrew:
name: Publish to homebrew
# vrc-get is on autobump list https://github.com/Homebrew/homebrew-core/blame/master/.github/autobump.txt
if: false # ${{ !needs.pre-build.outputs.prerelease }}
if: false # ${{ !inputs.dry-run && !needs.pre-build.outputs.prerelease }}
environment:
name: homebrew-core
url: https://github.com/homebrew/homebrew-core
@ -351,7 +367,7 @@ jobs:
publish-to-winget:
name: Publish to winget
if: ${{ !needs.pre-build.outputs.prerelease }}
if: ${{ !inputs.dry-run && !needs.pre-build.outputs.prerelease }}
needs: [ pre-build, publish-to-github ]
uses: vrc-get/vrc-get/.github/workflows/publish-cli-winget.yml@master

View file

@ -8,47 +8,79 @@ The format is based on [Keep a Changelog].
## [Unreleased]
### Added
- Build-time option to disable auto updater `#2759`
- Please read README for new build instruction.
- Implement project sorting by creation date `#2941`
### Changed
- The "Clear Selection" button in the package management screen is now red (destructive style) to distinguish it from the "Install Selected" button [`#2803`](https://github.com/vrc-get/vrc-get/pull/2803)
- File filled with '\0' or whitespace will be treated as empty file `#2710`
- This should prevent `syntax error loading settings.json: expected value at line 1 column 1` if settings.json is broken
- Completely changed how do we build ALCOM and how do we self-update ALCOM `#2759` `#2828`
- This fixes few problems relates to auto update
- Please read README for new build instruction.
- Improved backup speed by parallelizing the process [`#2746`](https://github.com/vrc-get/vrc-get/pull/2746)
- Along with this change, the default compression level has been changed to `zip-fast`
- We added dialog on enabling "Show Prerelease Packages" `#2795`
- I hope this prevents users unexpectedly adding prerelease packages
- Path for unitypackage on Template Editor now can be reselected `#2635`
- ALCOM now refuses launching project if project is on noexec mount points `#2814`
- This would cause problems with several native plugins
- Already-added packages are now excluded from the package name suggestions in the Template Editor `#2828`
- Extended some timeouts to 1 minute `#2826`
- Prevents timeouts in slow DNS environments
- Improved robustness for package installation errors `#2844`
- It is now unlikely that vrc-get will leave the project directory corrupted if an I/O error occurs while installing a package
### Deprecated
### Removed
### Fixed
- Fixed an issue where the progress bar flickered and did not display correct progress in environments using WebKit as the renderer. [`#2641`](https://github.com/vrc-get/vrc-get/pull/2641)
- Fails to import UnityPackages with files in `Packages` directory `#2679`
- null as vpmDependencies value is not allowed `#2709`
- It's not recommended, but we allow null for `vpmDependencies` as a alias of `{}`
- ALCOM cannot detect per-user flatpak installation of unity hub `#2812`
- Unabled to import some untypackages `#2821`
- It's hard to say but some older unitypackages ware unsupported.
- Panic when resolving projects where dependency packages depend on newer versions of locked packages `#2822`
- Missing glibc and libgcc_s dependency notation in .deb / .rpm distributon `#2828`
- Unclear error message for invalid version name or version range `#2842`
### Security
## [1.1.6] - 2026-06-02
### Added
- The package list can show hidden packages. [`#2731`](https://github.com/vrc-get/vrc-get/pull/2731)
- Build-time option to disable auto updater [`#2759`](https://github.com/vrc-get/vrc-get/pull/2759)
- Please read README for new build instruction.
- User repositories can now be reordered by drag and drop [`#2935`](https://github.com/vrc-get/vrc-get/pull/2935)
### Changed
- The "Clear Selection" button in the package management screen is now red (destructive style) to distinguish it from the "Install Selected" button [`#2803`](https://github.com/vrc-get/vrc-get/pull/2803)
- File filled with '\0' or whitespace will be treated as empty file [`#2710`](https://github.com/vrc-get/vrc-get/pull/2710)
- This should prevent `syntax error loading settings.json: expected value at line 1 column 1` if settings.json is broken
- We also added a backup file to recover from settings.json corruption [`#2933`](https://github.com/vrc-get/vrc-get/pull/2933)
- Completely changed how do we build ALCOM and how do we self-update ALCOM [`#2759`](https://github.com/vrc-get/vrc-get/pull/2759) [`#2828`](https://github.com/vrc-get/vrc-get/pull/2828) [`#2881`](https://github.com/vrc-get/vrc-get/pull/2881) [`#2882`](https://github.com/vrc-get/vrc-get/pull/2882) [`#2885`](https://github.com/vrc-get/vrc-get/pull/2885)
- This fixes few problems relates to auto update
- Please read README for new build instruction.
- Improved backup speed by parallelizing the process [`#2746`](https://github.com/vrc-get/vrc-get/pull/2746)
- Along with this change, the default compression level has been changed to `zip-fast`
- We added dialog on enabling "Show Prerelease Packages" [`#2795`](https://github.com/vrc-get/vrc-get/pull/2795)
- I hope this prevents users unexpectedly adding prerelease packages
- Path for unitypackage on Template Editor now can be reselected [`#2635`](https://github.com/vrc-get/vrc-get/pull/2635)
- ALCOM now refuses launching project if project is on noexec mount points [`#2814`](https://github.com/vrc-get/vrc-get/pull/2814)
- This would cause problems with several native plugins
- Already-added packages are now excluded from the package name suggestions in the Template Editor [`#2828`](https://github.com/vrc-get/vrc-get/pull/2828)
- Extended some timeouts to 1 minute [`#2826`](https://github.com/vrc-get/vrc-get/pull/2826)
- Prevents timeouts in slow DNS environments
- Improved robustness for package installation errors [`#2844`](https://github.com/vrc-get/vrc-get/pull/2844)
- It is now unlikely that vrc-get will leave the project directory corrupted if an I/O error occurs while installing a package
- Backslashes in path in zip file are now treated as path separator on unix [`#2845`](https://github.com/vrc-get/vrc-get/pull/2845)
- This fixes problem with Gesture Manager 3.9.7
- Empty string for `documentationUrl` and `changelogUrl` are now allowed and ignored [`#2930`](https://github.com/vrc-get/vrc-get/pull/2930)
- They are formerly rejected as invalid url
### Fixed
- Fixed an issue where the progress bar flickered and did not display correct progress in environments using WebKit as the renderer. [`#2641`](https://github.com/vrc-get/vrc-get/pull/2641)
- Fails to import UnityPackages with files in `Packages` directory [`#2679`](https://github.com/vrc-get/vrc-get/pull/2679)
- null as vpmDependencies value is not allowed [`#2709`](https://github.com/vrc-get/vrc-get/pull/2709)
- It's not recommended, but we allow null for `vpmDependencies` as a alias of `{}`
- ALCOM cannot detect per-user flatpak installation of unity hub [`#2812`](https://github.com/vrc-get/vrc-get/pull/2812)
- Unabled to import some untypackages [`#2821`](https://github.com/vrc-get/vrc-get/pull/2821)
- It's hard to say but some older unitypackages ware unsupported.
- Panic when resolving projects where dependency packages depend on newer versions of locked packages [`#2822`](https://github.com/vrc-get/vrc-get/pull/2822)
- Missing glibc and libgcc_s dependency notation in .deb / .rpm distributon [`#2828`](https://github.com/vrc-get/vrc-get/pull/2828)
- Unclear error message for invalid version name or version range [`#2842`](https://github.com/vrc-get/vrc-get/pull/2842)
- Default file names in save dialogs now include the appropriate file extension [`#2846`](https://github.com/vrc-get/vrc-get/pull/2846)
- Template export now defaults to `{template name}.alcomtemplate`
- Repository list export now defaults to `repositories.txt`
- Uninformative `[object Object]` appearing as an error message [`#2848`](https://github.com/vrc-get/vrc-get/pull/2848)
- New Unity Hub loading method may not load manually added Unity Editors [`#2850`](https://github.com/vrc-get/vrc-get/pull/2850)
- New Unity Hub loading method does load unity hub configuration on Linux [`#2850`](https://github.com/vrc-get/vrc-get/pull/2850)
- Too many open files when copying project `#2867
- Added workaround for VRCDefaultWorldScene generation issue in SDK 3.10.2 or later [`#2916`](https://github.com/vrc-get/vrc-get/pull/2916)
- See [this][default-scene-canny] canny for bug in VRCSDK and issue [#2913][issue-2913] for our decision.
### Security
- Package hash checks are now enforced when installing packages [`#2849`](https://github.com/vrc-get/vrc-get/pull/2849)
- It has been about two years since the error message for package hash mismatches was introduced.
- It is now enforced for security.
[default-scene-canny]: https://feedback.vrchat.com/sdk-bug-reports/p/3102-3103-vrcscenetemplateinitializer-does-not-create-sample-scene-if-udon-prepr
[issue-2913]: https://github.com/vrc-get/vrc-get/issues/2913
## [1.1.5] - 2025-11-16
- Fix package version selector dropdown exceeding window height [`#2589`](https://github.com/vrc-get/vrc-get/pull/2589)
- The dropdown list now has a maximum height of 50% of the viewport or 24rem, whichever is smaller
@ -671,7 +703,8 @@ Release pipeline fixes
- Apple code signing [`#422`](https://github.com/anatawa12/vrc-get/pull/422)
- Migrate vpm 2019 project to 2022 [`#435`](https://github.com/anatawa12/vrc-get/pull/435)
[Unreleased]: https://github.com/vrc-get/vrc-get/compare/gui-v1.1.5...HEAD
[Unreleased]: https://github.com/vrc-get/vrc-get/compare/gui-v1.1.6...HEAD
[1.1.6]: https://github.com/vrc-get/vrc-get/compare/gui-v1.1.5...gui-v1.1.6
[1.1.5]: https://github.com/vrc-get/vrc-get/compare/gui-v1.1.4...gui-v1.1.5
[1.1.4]: https://github.com/vrc-get/vrc-get/compare/gui-v1.1.3...gui-v1.1.4
[1.1.3]: https://github.com/vrc-get/vrc-get/compare/gui-v1.1.2...gui-v1.1.3

View file

@ -17,6 +17,8 @@ The format is based on [Keep a Changelog].
- It's not recommended, but we allow null for `vpmDependencies` as a alias of `{}`
- Improved robustness for package installation errors `#2844`
- It is now unlikely that vrc-get will leave the project directory corrupted if an I/O error occurs while installing a package
- Backslashes in path in zip file are now treated as path separator on unix `#2845`
- This fixes problem with Gesture Manager 3.9.7
### Deprecated
@ -27,8 +29,13 @@ The format is based on [Keep a Changelog].
- Panic when resolving projects where dependency packages depend on newer versions of locked packages `#2822`
- Warning for backup/project path in AppData folder not shown when path is in Roaming or LocalLow [`#2827`](https://github.com/vrc-get/vrc-get/pull/2827)
- Unclear error message for invalid version name or version range `#2842`
- Empty string for `documentationUrl` and `changelogUrl` are now allowed and ignored `#2930`
- They are formerly rejected as invalid url
### Security
- Package hash checks are now enforced when installing packages `#2849`
- It has been about two years since the error message for package hash mismatches was introduced.
- It is now enforced for security.
## [1.9.1] - 2025-07-28
### Changed

543
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "vrc-get-gui"
version = "1.1.6-beta.0"
version = "1.1.7-beta.0"
description = "A fast open-source alternative of VRChat Creator Companion"
homepage.workspace = true
@ -22,7 +22,7 @@ tauri-build = { version = "2", features = [ "config-toml" ] }
serde_json = "1"
serde = { version = "1", features = ["derive"] }
serde_with = { version = "3", features = ["base64"] }
tauri = { version = "=2.10.3", features = [ "config-toml" ] } # = for sync version between npm and cargo
tauri = { version = "=2.11.2", features = [ "config-toml" ] } # = for sync version between npm and cargo
vrc-get-vpm = { path = "../vrc-get-vpm", features = ["experimental-project-management", "experimental-unity-management"] }
reqwest = { version = "0.13", features = ["gzip", "brotli", "json"] }
specta = { version = "2.0.0-rc.24", features = [ "chrono", "url", "indexmap" ] }
@ -61,7 +61,7 @@ yoke = { version = "0.8", features = ["derive"] }
atomicbox = "0.4"
stable_deref_trait = "1"
itertools = "0.14"
sysinfo = "0.38.4"
sysinfo = "0.39.3"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.62", features = ["Win32_Storage_FileSystem", "Win32_System_IO", "Win32_NetworkManagement_IpHelper", "Wdk_System_SystemServices", "Win32_System_SystemInformation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }

View file

@ -6,15 +6,20 @@ import globalInfo from "@/lib/global-info";
export default function ErrorPage({
error,
}: {
error: Error;
error: object;
reset?: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
const errorMessage = `${error}`;
const errorStack = `${error.stack}`;
// When there is overridden toString, use it. if not, use stringify
const errorMessage =
error.toString === Object.prototype.toString
? JSON.stringify(error)
: error.toString();
const errorStack =
"stack" in error ? `${error.stack}` : "No stacktrace provided";
const openIssue = () => {
try {

View file

@ -18,7 +18,13 @@ import {
SelectValue,
} from "@/components/ui/select";
import { tc } from "@/lib/i18n";
import { toastError, toastInfo, toastNormal, toastSuccess } from "@/lib/toast";
import {
toastError,
toastInfo,
toastNormal,
toastSuccess,
toastWarning,
} from "@/lib/toast";
export const Route = createFileRoute("/_main/dev-palette/")({
component: Page,
@ -113,6 +119,12 @@ function Page() {
>
Error
</Button>
<Button
variant={"warning"}
onClick={() => toastWarning("Warning Toast Body")}
>
Warning
</Button>
<Button
variant={"success"}
onClick={() => toastSuccess("Success Toast Body")}

View file

@ -1,5 +1,26 @@
"use client";
import {
type CollisionDetection,
closestCenter,
DndContext,
type DragEndEvent,
type DragOverEvent,
DragOverlay,
type DragStartEvent,
defaultDropAnimation,
defaultDropAnimationSideEffects,
type Modifier,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import {
queryOptions,
useMutation,
@ -7,8 +28,16 @@ import {
useQueryClient,
} from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
import { ChevronDown, CircleX } from "lucide-react";
import { Suspense, useCallback, useEffect, useId, useMemo } from "react";
import { ChevronDown, CircleX, GripVertical } from "lucide-react";
import {
Suspense,
useCallback,
useEffect,
useId,
useMemo,
useRef,
useState,
} from "react";
import { HNavBar, VStack } from "@/components/layout";
import { ScrollableCardTable } from "@/components/ScrollableCardTable";
import { Button } from "@/components/ui/button";
@ -41,6 +70,8 @@ export const Route = createFileRoute("/_main/packages/repositories/")({
component: Page,
});
type UserRepoWithListId = TauriUserRepository & { listId: string };
function Page() {
return (
<Suspense>
@ -49,11 +80,99 @@ function Page() {
);
}
const restrictToVerticalAxis: Modifier = ({ transform }) => ({
...transform,
x: 0,
});
const DRAG_OVERLAY_MODIFIERS = [restrictToVerticalAxis];
const customDropAnimation: typeof defaultDropAnimation = {
...defaultDropAnimation,
sideEffects: defaultDropAnimationSideEffects({
styles: {
active: { opacity: "0" },
},
}),
};
const TABLE_HEAD = [
"", // checkbox
"general:name",
"vpm repositories:url",
"", // actions
"", // grip handle
] as const;
const environmentRepositoriesInfo = queryOptions({
queryKey: ["environmentRepositoriesInfo"],
queryFn: commands.environmentRepositoriesInfo,
});
// Scrolls the given viewport element when the pointer is near the top or bottom
// edge during drag. dnd-kit's built-in autoscroll is disabled because it causes
// jitter with Radix UI ScrollArea (wrong container detection + double-smoothing).
function useDragAutoScroll(
viewportRef: React.RefObject<HTMLElement | null>,
isActive: boolean,
): void {
useEffect(() => {
if (!isActive) return;
const THRESHOLD = 80; // px from edge to begin scrolling
const MAX_SPEED = 15; // px/frame at the very edge
let pointerY = 0;
const onPointerMove = (e: PointerEvent) => {
pointerY = e.clientY;
};
window.addEventListener("pointermove", onPointerMove, { passive: true });
let rafId: number;
const tick = () => {
const viewport = viewportRef.current;
if (viewport) {
const { top, bottom } = viewport.getBoundingClientRect();
const distFromTop = pointerY - top;
const distFromBottom = bottom - pointerY;
let delta = 0;
if (distFromTop >= 0 && distFromTop < THRESHOLD) {
delta = -MAX_SPEED * (1 - distFromTop / THRESHOLD);
} else if (distFromBottom >= 0 && distFromBottom < THRESHOLD) {
delta = MAX_SPEED * (1 - distFromBottom / THRESHOLD);
}
if (delta !== 0) {
viewport.scrollTo({
top: viewport.scrollTop + delta,
behavior: "instant",
});
}
}
rafId = requestAnimationFrame(tick);
};
rafId = requestAnimationFrame(tick);
return () => {
window.removeEventListener("pointermove", onPointerMove);
cancelAnimationFrame(rafId);
};
}, [isActive, viewportRef]);
}
function computeSlotKey(repo: TauriUserRepository, used: Set<string>): string {
const base = `${repo.id} ${repo.url ?? ""}`;
let key = base;
let counter = 0;
while (used.has(key)) {
counter++;
key = `${base} ${counter}`;
}
used.add(key);
return key;
}
function PageBody() {
const result = useQuery(environmentRepositoriesInfo);
@ -95,140 +214,110 @@ function PageBody() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const bodyAnimation = usePrevPathName().startsWith("/packages")
? "slide-right"
: "";
const guiAnimation = useQuery({
queryKey: ["environmentGuiAnimation"],
queryFn: commands.environmentGuiAnimation,
initialData: true,
}).data;
return (
<VStack>
<HNavBar
className="shrink-0"
leading={<HeadingPageName pageType={"/packages/repositories"} />}
trailing={
<DropdownMenu>
<div className={"flex divide-x"}>
<Button
className={"rounded-r-none compact:h-10"}
onClick={() => openAddRepositoryDialog()}
>
{tc("vpm repositories:button:add repository")}
</Button>
<DropdownMenuTrigger
asChild
className={"rounded-l-none pl-2 pr-2 compact:h-10"}
>
<Button>
<ChevronDown className={"w-4 h-4"} />
</Button>
</DropdownMenuTrigger>
</div>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() => importRepositoriesMutation.mutate()}
>
{tc("vpm repositories:button:import repositories")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => exportRepositories.mutate()}>
{tc("vpm repositories:button:export repositories")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
}
/>
<main
className={`shrink overflow-hidden flex w-full h-full ${bodyAnimation}`}
>
<ScrollableCardTable className={"h-full w-full"}>
<RepositoryTableBody
userRepos={result.data?.user_repositories || []}
hiddenUserRepos={hiddenUserRepos}
/>
</ScrollableCardTable>
</main>
</VStack>
const userRepos = result.data?.user_repositories;
const listIdMapRef = useRef<Map<string, string>>(new Map());
const augmentedUserRepos = useMemo<UserRepoWithListId[]>(() => {
if (!userRepos) {
listIdMapRef.current = new Map();
return [];
}
const prev = listIdMapRef.current;
const next = new Map<string, string>();
const usedKeys = new Set<string>();
const result: UserRepoWithListId[] = [];
for (const r of userRepos) {
const key = computeSlotKey(r, usedKeys);
const listId = prev.get(key) ?? crypto.randomUUID();
next.set(key, listId);
result.push({ ...r, listId });
}
listIdMapRef.current = next;
return result;
}, [userRepos]);
const [orderedListIds, setOrderedListIds] = useState<string[]>(() =>
augmentedUserRepos.map((r) => r.listId),
);
}
function RepositoryTableBody({
userRepos,
hiddenUserRepos,
}: {
userRepos: TauriUserRepository[];
hiddenUserRepos: Set<string>;
}) {
const TABLE_HEAD = [
"", // checkbox
"general:name",
"vpm repositories:url",
"", // actions
];
useEffect(() => {
setOrderedListIds(augmentedUserRepos.map((r) => r.listId));
}, [augmentedUserRepos]);
return (
<>
<thead>
<tr>
{TABLE_HEAD.map((head, index) => (
<th
// biome-ignore lint/suspicious/noArrayIndexKey: static array
key={index}
className={
"sticky top-0 z-10 border-b border-primary bg-secondary text-secondary-foreground px-2.5 py-1.5"
}
>
<small className="font-normal leading-none">{tc(head)}</small>
</th>
))}
</tr>
</thead>
<tbody>
<RepositoryRow
repoId={"com.vrchat.repos.official"}
url={"https://packages.vrchat.com/official?download"}
displayName={tt("vpm repositories:source:official")}
hiddenUserRepos={hiddenUserRepos}
canRemove={false}
/>
<RepositoryRow
repoId={"com.vrchat.repos.curated"}
url={"https://packages.vrchat.com/curated?download"}
displayName={tt("vpm repositories:source:curated")}
hiddenUserRepos={hiddenUserRepos}
className={"border-b border-primary/10"}
canRemove={false}
/>
{userRepos.map((repo) => (
<RepositoryRow
key={repo.id}
repoId={repo.id}
displayName={repo.display_name}
url={repo.url}
hiddenUserRepos={hiddenUserRepos}
/>
))}
</tbody>
</>
const userRepoByListId = useMemo(
() => new Map(augmentedUserRepos.map((r) => [r.listId, r])),
[augmentedUserRepos],
);
}
function RepositoryRow({
repoId,
displayName,
url,
hiddenUserRepos,
className,
canRemove = true,
}: {
repoId: TauriUserRepository["id"];
displayName: TauriUserRepository["display_name"];
url: TauriUserRepository["url"];
hiddenUserRepos: Set<string>;
className?: string;
canRemove?: boolean;
}) {
const cellClass = "p-2.5 compact:py-1";
const id = useId();
const userRepoByListIdRef =
useRef<Map<string, UserRepoWithListId>>(userRepoByListId);
useEffect(() => {
userRepoByListIdRef.current = userRepoByListId;
}, [userRepoByListId]);
const [activeId, setActiveId] = useState<string | null>(null);
const [overId, setOverId] = useState<string | null>(null);
const [columnWidths, setColumnWidths] = useState<number[]>([]);
const theadRowRef = useRef<HTMLTableRowElement>(null);
const scrollViewportRef = useRef<HTMLDivElement>(null);
const sensors = useSensors(useSensor(PointerSensor));
const orderedListIdsSet = useMemo(
() => new Set(orderedListIds),
[orderedListIds],
);
const collisionDetection = useCallback<CollisionDetection>(
(args) =>
closestCenter({
...args,
droppableContainers: args.droppableContainers.filter((c) =>
orderedListIdsSet.has(c.id as string),
),
}),
[orderedListIdsSet],
);
const queryClient = useQueryClient();
const reorderMutation = useMutation({
mutationFn: (listIds: string[]) => {
const repos = listIds
.map((lid) => userRepoByListId.get(lid))
.filter((r): r is UserRepoWithListId => r !== undefined)
.map((r) => ({ index: r.index, id: r.id }));
return commands.environmentReorderRepositories(repos);
},
// Pin listIds to the new positions so duplicate-keyed rows don't swap their listIds on refetch.
onMutate: (newListIds: string[]) => {
const prevMap = new Map(listIdMapRef.current);
const rebuilt = new Map<string, string>();
const usedKeys = new Set<string>();
for (const lid of newListIds) {
const repo = userRepoByListIdRef.current.get(lid);
if (!repo) continue;
const key = computeSlotKey(repo, usedKeys);
rebuilt.set(key, lid);
}
listIdMapRef.current = rebuilt;
return { prevMap };
},
onSettled: () => queryClient.invalidateQueries(environmentRepositoriesInfo),
onError: (e, _newListIds, ctx) => {
if (ctx?.prevMap) listIdMapRef.current = ctx.prevMap;
toastThrownError(e);
},
});
const setHideRepository = useMutation({
mutationFn: async ({ id, shown }: { id: string; shown: boolean }) => {
if (shown) {
@ -273,72 +362,483 @@ function RepositoryRow({
},
});
const activeVisualIndex = useMemo(() => {
if (!activeId) return 0;
const effectiveId = overId ?? activeId;
return orderedListIds.indexOf(effectiveId) + 2; // +2 for the 2 fixed rows
}, [activeId, overId, orderedListIds]);
function handleDragStart(event: DragStartEvent) {
setActiveId(event.active.id as string);
if (theadRowRef.current) {
const widths = Array.from(
theadRowRef.current.querySelectorAll("th"),
(th) => th.getBoundingClientRect().width,
);
setColumnWidths(widths);
}
}
function handleDragOver(event: DragOverEvent) {
setOverId((event.over?.id as string | null) ?? null);
}
function handleDragEnd(event: DragEndEvent) {
setActiveId(null);
setOverId(null);
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = orderedListIds.indexOf(active.id as string);
const newIndex = orderedListIds.indexOf(over.id as string);
const newListIds = arrayMove(orderedListIds, oldIndex, newIndex);
setOrderedListIds(newListIds);
reorderMutation.mutate(newListIds);
}
}
function handleDragCancel() {
setActiveId(null);
setOverId(null);
}
useDragAutoScroll(scrollViewportRef, activeId !== null);
const bodyAnimation = usePrevPathName().startsWith("/packages")
? "slide-right"
: "";
return (
<DndContext
sensors={sensors}
collisionDetection={collisionDetection}
autoScroll={false}
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<VStack>
<div style={activeId !== null ? { pointerEvents: "none" } : undefined}>
<HNavBar
className="shrink-0"
leading={<HeadingPageName pageType={"/packages/repositories"} />}
trailing={
<DropdownMenu>
<div className={"flex divide-x"}>
<Button
className={"rounded-r-none compact:h-10"}
onClick={() => openAddRepositoryDialog()}
>
{tc("vpm repositories:button:add repository")}
</Button>
<DropdownMenuTrigger
asChild
className={"rounded-l-none pl-2 pr-2 compact:h-10"}
>
<Button>
<ChevronDown className={"w-4 h-4"} />
</Button>
</DropdownMenuTrigger>
</div>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() => importRepositoriesMutation.mutate()}
>
{tc("vpm repositories:button:import repositories")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => exportRepositories.mutate()}>
{tc("vpm repositories:button:export repositories")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
}
/>
</div>
<main
className={`shrink overflow-hidden flex w-full h-full ${bodyAnimation}`}
>
<ScrollableCardTable
className={"h-full w-full"}
viewportRef={scrollViewportRef}
>
<RepositoryTableBody
orderedListIds={orderedListIds}
userRepoByListId={userRepoByListId}
hiddenUserRepos={hiddenUserRepos}
theadRowRef={theadRowRef}
guiAnimation={guiAnimation}
onToggleVisibility={(id, shown) =>
setHideRepository.mutate({ id, shown })
}
isDragActive={activeId !== null}
/>
</ScrollableCardTable>
</main>
</VStack>
<DragOverlay
modifiers={DRAG_OVERLAY_MODIFIERS}
dropAnimation={guiAnimation ? customDropAnimation : null}
>
{activeId ? (
<RepositoryDragOverlay
repo={userRepoByListId.get(activeId)}
selected={
!hiddenUserRepos.has(userRepoByListId.get(activeId)?.id ?? "")
}
columnWidths={columnWidths}
visualIndex={activeVisualIndex}
guiAnimation={guiAnimation}
/>
) : null}
</DragOverlay>
</DndContext>
);
}
function RepositoryTableBody({
orderedListIds,
userRepoByListId,
hiddenUserRepos,
theadRowRef,
guiAnimation,
onToggleVisibility,
isDragActive,
}: {
orderedListIds: string[];
userRepoByListId: Map<string, UserRepoWithListId>;
hiddenUserRepos: Set<string>;
theadRowRef: React.RefObject<HTMLTableRowElement | null>;
guiAnimation: boolean;
onToggleVisibility: (id: string, shown: boolean) => void;
isDragActive: boolean;
}) {
return (
<>
<thead>
<tr ref={theadRowRef}>
{TABLE_HEAD.map((head, index) => (
<th
// biome-ignore lint/suspicious/noArrayIndexKey: static array
key={index}
className={
"sticky top-0 z-10 border-b border-primary bg-secondary text-secondary-foreground px-2.5 py-1.5"
}
>
<small className="font-normal leading-none">{tc(head)}</small>
</th>
))}
</tr>
</thead>
<tbody>
<RepositoryRow
repoId={"com.vrchat.repos.official"}
url={"https://packages.vrchat.com/official?download"}
displayName={tt("vpm repositories:source:official")}
hiddenUserRepos={hiddenUserRepos}
canRemove={false}
rowIndex={0}
guiAnimation={guiAnimation}
onToggleVisibility={onToggleVisibility}
isDragActive={isDragActive}
/>
<RepositoryRow
repoId={"com.vrchat.repos.curated"}
url={"https://packages.vrchat.com/curated?download"}
displayName={tt("vpm repositories:source:curated")}
hiddenUserRepos={hiddenUserRepos}
className={"border-b border-primary/10"}
canRemove={false}
rowIndex={1}
guiAnimation={guiAnimation}
onToggleVisibility={onToggleVisibility}
isDragActive={isDragActive}
/>
<SortableContext
items={orderedListIds}
strategy={verticalListSortingStrategy}
>
{orderedListIds.map((listId, index) => {
const repo = userRepoByListId.get(listId);
if (!repo) return null;
return (
<RepositoryRow
key={listId}
listId={listId}
repoId={repo.id}
repoIndex={repo.index}
displayName={repo.display_name}
url={repo.url}
hiddenUserRepos={hiddenUserRepos}
rowIndex={2 + index}
guiAnimation={guiAnimation}
onToggleVisibility={onToggleVisibility}
isDragActive={isDragActive}
/>
);
})}
</SortableContext>
</tbody>
</>
);
}
const CELL_CLASS = "p-2.5 compact:py-1 align-middle";
function RepositoryRowCells({
labelId,
displayName,
url,
canRemove,
selected,
onCheckedChange,
onRemove,
dragListeners,
dragAttributes,
}: {
labelId?: string;
displayName: string;
url: string | null | undefined;
canRemove: boolean;
selected: boolean;
onCheckedChange?: (shown: boolean) => void;
onRemove?: () => void;
dragListeners?: ReturnType<typeof useSortable>["listeners"];
dragAttributes?: ReturnType<typeof useSortable>["attributes"];
}) {
const interactive = onCheckedChange !== undefined;
return (
<>
<td className={CELL_CLASS}>
{interactive ? (
<div className="flex">
<Checkbox
id={labelId}
checked={selected}
onCheckedChange={(x) => onCheckedChange(x === true)}
/>
</div>
) : (
<div className="pointer-events-none flex">
<Checkbox checked={selected} />
</div>
)}
</td>
<td className={CELL_CLASS}>
{interactive ? (
<label htmlFor={labelId}>
<p className="font-normal">{displayName}</p>
</label>
) : (
<p className="font-normal">{displayName}</p>
)}
</td>
<td className={CELL_CLASS}>
<p className="font-normal">{url}</p>
</td>
<td className={`${CELL_CLASS} w-0`}>
{interactive ? (
<Tooltip>
<TooltipTrigger asChild={canRemove}>
<Button
disabled={!canRemove}
onClick={onRemove}
variant={"ghost"}
size={"icon"}
>
<CircleX className={"size-5 text-destructive"} />
</Button>
</TooltipTrigger>
<TooltipContent>
{canRemove
? tc("vpm repositories:remove repository")
: tc(
"vpm repositories:tooltip:remove curated or official repository",
)}
</TooltipContent>
</Tooltip>
) : (
<Button variant={"ghost"} size={"icon"} disabled>
<CircleX className={"size-5 text-destructive"} />
</Button>
)}
</td>
<td
className={cn(
CELL_CLASS,
"w-0",
canRemove ? "cursor-move" : "cursor-not-allowed",
)}
{...(canRemove ? dragListeners : undefined)}
{...(canRemove ? dragAttributes : undefined)}
>
<GripVertical
className={cn(
"size-5 text-muted-foreground",
!canRemove && "opacity-50",
)}
/>
</td>
</>
);
}
function RepositoryRow({
listId,
repoId,
repoIndex,
displayName,
url,
hiddenUserRepos,
className,
canRemove = true,
rowIndex,
guiAnimation,
onToggleVisibility,
isDragActive,
}: {
listId?: string;
repoId: TauriUserRepository["id"];
repoIndex?: number;
displayName: TauriUserRepository["display_name"];
url: TauriUserRepository["url"];
hiddenUserRepos: Set<string>;
className?: string;
canRemove?: boolean;
rowIndex: number;
guiAnimation: boolean;
onToggleVisibility: (id: string, shown: boolean) => void;
isDragActive: boolean;
}) {
const labelId = useId();
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: listId ?? repoId, disabled: !canRemove });
const visualIndex = useMemo(() => {
if (isDragging) return rowIndex;
const dy = transform?.y ?? 0;
if (dy < 0) return rowIndex - 1;
if (dy > 0) return rowIndex + 1;
return rowIndex;
}, [rowIndex, transform?.y, isDragging]);
const dragStyle = useMemo<React.CSSProperties>(
() => ({
transform: transform ? `translateY(${transform.y}px)` : undefined,
transition: guiAnimation
? [transition, isDragActive ? undefined : "background-color 200ms ease"]
.filter(Boolean)
.join(", ") || undefined
: undefined,
opacity: isDragging ? 0 : 1,
position: "relative",
}),
[transform, transition, isDragging, guiAnimation, isDragActive],
);
const selected = !hiddenUserRepos.has(repoId);
return (
<tr className={cn("even:bg-secondary/30", className)}>
<td className={cellClass}>
<Checkbox
id={id}
checked={selected}
onCheckedChange={(x) =>
setHideRepository.mutate({ id: repoId, shown: x === true })
}
/>
</td>
<td className={cellClass}>
<label htmlFor={id}>
<p className="font-normal">{displayName}</p>
</label>
</td>
<td className={cellClass}>
<p className="font-normal">{url}</p>
</td>
<td className={`${cellClass} w-0`}>
<Tooltip>
<TooltipTrigger asChild={canRemove}>
<Button
disabled={!canRemove}
onClick={() => {
void openSingleDialog(RemoveRepositoryDialog, {
displayName,
id: repoId,
});
}}
variant={"ghost"}
size={"icon"}
>
<CircleX className={"size-5 text-destructive"} />
</Button>
</TooltipTrigger>
<TooltipContent>
{canRemove
? tc("vpm repositories:remove repository")
: tc(
"vpm repositories:tooltip:remove curated or official repository",
)}
</TooltipContent>
</Tooltip>
</td>
<tr
ref={setNodeRef}
style={dragStyle}
className={cn(visualIndex % 2 === 1 ? "bg-secondary/30" : "", className)}
>
<RepositoryRowCells
labelId={labelId}
displayName={displayName}
url={url}
canRemove={canRemove}
selected={selected}
onCheckedChange={(shown) => onToggleVisibility(repoId, shown)}
onRemove={() =>
void openSingleDialog(RemoveRepositoryDialog, {
displayName,
index: repoIndex ?? 0,
id: repoId,
})
}
dragListeners={listeners}
dragAttributes={attributes}
/>
</tr>
);
}
function RepositoryDragOverlay({
repo,
selected,
columnWidths,
visualIndex,
guiAnimation,
}: {
repo: TauriUserRepository | undefined;
selected: boolean;
columnWidths: number[];
visualIndex: number;
guiAnimation: boolean;
}) {
const style = useMemo<React.CSSProperties>(
() => ({
transition: guiAnimation ? "background-color 200ms ease" : undefined,
}),
[guiAnimation],
);
if (!repo) return null;
return (
<table
className={cn(
"w-full table-fixed text-left",
visualIndex % 2 === 1 ? "bg-secondary/30" : "",
)}
style={style}
>
{columnWidths.length > 0 && (
<colgroup>
{columnWidths.map((w, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: fixed column order
<col key={i} style={{ width: w }} />
))}
</colgroup>
)}
<tbody>
<tr>
<RepositoryRowCells
displayName={repo.display_name}
url={repo.url}
canRemove={true}
selected={selected}
/>
</tr>
</tbody>
</table>
);
}
function RemoveRepositoryDialog({
dialog,
displayName,
index,
id,
}: {
dialog: DialogContext<void>;
displayName: string;
index: number;
id: string;
}) {
const queryClient = useQueryClient();
const removeRepository = useMutation({
mutationFn: async (id: string) =>
await commands.environmentRemoveRepository(id),
onMutate: async (id) => {
mutationFn: async (args: { index: number; id: string }) =>
await commands.environmentRemoveRepository(args.index, args.id),
onMutate: async ({ index }) => {
await queryClient.cancelQueries(environmentRepositoriesInfo);
const data = queryClient.getQueryData(
environmentRepositoriesInfo.queryKey,
@ -346,10 +846,18 @@ function RemoveRepositoryDialog({
if (data !== undefined) {
queryClient.setQueryData(environmentRepositoriesInfo.queryKey, {
...data,
user_repositories: data.user_repositories.filter((x) => x.id !== id),
user_repositories: data.user_repositories.filter(
(x) => x.index !== index,
),
});
}
return data;
},
onError: (e, _args, ctx) => {
queryClient.setQueryData(environmentRepositoriesInfo.queryKey, ctx);
toastThrownError(e);
},
onSettled: () => queryClient.invalidateQueries(environmentRepositoriesInfo),
});
return (
@ -369,7 +877,7 @@ function RemoveRepositoryDialog({
<Button
onClick={() => {
dialog.close();
removeRepository.mutate(id);
removeRepository.mutate({ index, id });
}}
className={"ml-2"}
>

View file

@ -316,8 +316,10 @@ function UnityVersion({
function CreatingProject() {
return (
<DialogBase>
<RefreshCw className={"w-5 h-5 animate-spin"} />
<p>{tc("projects:creating project...")}</p>
<div className={"flex items-center gap-2"}>
<RefreshCw className={"w-5 h-5 animate-spin"} />
<p>{tc("projects:creating project...")}</p>
</div>
</DialogBase>
);
}

View file

@ -29,7 +29,11 @@ import {
} from "@/components/ui/tooltip";
import type { TauriProject } from "@/lib/bindings";
import { commands } from "@/lib/bindings";
import { dateToString, formatDateOffset } from "@/lib/dateToString";
import {
dateToString,
dayToString,
formatDateOffset,
} from "@/lib/dateToString";
import { openSingleDialog } from "@/lib/dialog";
import { tc } from "@/lib/i18n";
import { toastThrownError } from "@/lib/toast";
@ -45,7 +49,7 @@ export function ProjectGridItem({
const typeIconClass = "w-5 h-5";
const { projectTypeKind, displayType, isLegacy, lastModified } =
const { projectTypeKind, displayType, isLegacy, createdAt, lastModified } =
getProjectDisplayInfo(project);
const removed = !project.is_exists;
@ -165,22 +169,44 @@ export function ProjectGridItem({
</div>
</div>
<div className="text-xs text-muted-foreground">
{tc("general:last modified")}:{" "}
<Tooltip>
<TooltipTrigger>
<time dateTime={lastModified.toISOString()}>
<time className="font-normal">
{formatDateOffset(project.last_modified)}
<div className="flex flex-row gap-1">
<div className="text-xs text-muted-foreground">
{tc("general:created at")}:{" "}
<Tooltip>
<TooltipTrigger>
<time dateTime={createdAt.toISOString()}>
<time className="font-normal">
{dayToString(project.created_at)}
</time>
</time>
</time>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{dateToString(project.last_modified)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{dateToString(project.created_at)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
<p className="text-xs text-muted-foreground">/</p>
<div className="text-xs text-muted-foreground">
{tc("general:last modified")}:{" "}
<Tooltip>
<TooltipTrigger>
<time dateTime={lastModified.toISOString()}>
<time className="font-normal">
{formatDateOffset(project.last_modified)}
</time>
</time>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{dateToString(project.last_modified)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
</div>
<div className="mt-2 flex flex-wrap gap-2 justify-end compact:gap-1">

View file

@ -29,7 +29,11 @@ import {
import { assertNever } from "@/lib/assert-never";
import type { TauriProject, TauriProjectType } from "@/lib/bindings";
import { commands } from "@/lib/bindings";
import { dateToString, formatDateOffset } from "@/lib/dateToString";
import {
dateToString,
dayToString,
formatDateOffset,
} from "@/lib/dateToString";
import { type DialogContext, openSingleDialog, showDialog } from "@/lib/dialog";
import { tc, tt } from "@/lib/i18n";
import { router } from "@/lib/main";
@ -78,7 +82,7 @@ export function ProjectRow({
const noGrowCellClass = `${cellClass} w-1`;
const typeIconClass = "w-5 h-5";
const { projectTypeKind, displayType, isLegacy, lastModified } =
const { projectTypeKind, displayType, isLegacy, createdAt, lastModified } =
getProjectDisplayInfo(project);
const openProjectFolder = () =>
@ -105,7 +109,7 @@ export function ProjectRow({
<tr
className={`group even:bg-secondary/30 ${removed || loading || !(project.is_valid ?? true) ? "opacity-50" : ""}`}
>
<td className={`${cellClass} w-3`}>
<td className={noGrowCellClass}>
<div className={"relative flex"}>
<FavoriteStarToggleButton
favorite={project.favorite}
@ -147,7 +151,7 @@ export function ProjectRow({
</TooltipPortal>
</Tooltip>
</td>
<td className={`${cellClass} w-[8em] min-w-[8em]`}>
<td className={noGrowCellClass}>
<div className="flex flex-row gap-2">
<div className="flex items-center">
{projectTypeKind === "avatars" ? (
@ -171,6 +175,22 @@ export function ProjectRow({
<td className={noGrowCellClass}>
<p className="font-normal">{project.unity}</p>
</td>
<td className={noGrowCellClass}>
<Tooltip>
<TooltipTrigger>
<time dateTime={createdAt.toISOString()}>
<time className="font-normal">
{dayToString(project.created_at)}
</time>
</time>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{dateToString(project.created_at)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</td>
<td className={noGrowCellClass}>
<Tooltip>
<TooltipTrigger>
@ -501,12 +521,14 @@ export function getProjectDisplayInfo(project: TauriProject) {
const projectTypeKind = ProjectDisplayType[project.project_type] ?? "unknown";
const displayType = tc(`projects:type:${projectTypeKind}`);
const isLegacy = LegacyProjectTypes.includes(project.project_type);
const createdAt = new Date(project.created_at);
const lastModified = new Date(project.last_modified);
return {
projectTypeKind,
displayType,
isLegacy,
createdAt,
lastModified,
};
}

View file

@ -30,6 +30,7 @@ const sortingOptions: { key: SimpleSorting; label: string }[] = [
{ key: "name", label: "general:name" },
{ key: "type", label: "projects:type" },
{ key: "unity", label: "projects:unity" },
{ key: "createdAt", label: "general:created at" },
{ key: "lastModified", label: "general:last modified" },
];

View file

@ -11,7 +11,13 @@ import { toastThrownError } from "@/lib/toast";
import { compareUnityVersionString } from "@/lib/version";
import { ProjectRow } from "./-project-row";
export const sortings = ["lastModified", "name", "unity", "type"] as const;
export const sortings = [
"createdAt",
"lastModified",
"name",
"unity",
"type",
] as const;
type SimpleSorting = (typeof sortings)[number];
type Sorting = SimpleSorting | `${SimpleSorting}Reversed`;
@ -158,6 +164,18 @@ export function ProjectsTableCard({
</small>
</button>
</th>
<th className={`${thClass} ${headerBg("createdAt")}`}>
<button
type="button"
className={"flex w-full project-table-button"}
onClick={() => setSorting("createdAt")}
>
{icon("createdAt")}
<small className="font-normal leading-none">
{tc("general:created at")}
</small>
</button>
</th>
<th className={`${thClass} ${headerBg("lastModified")}`}>
<button
type="button"
@ -194,6 +212,12 @@ export function sortSearchProjects(
searched.sort((a, b) => b.last_modified - a.last_modified);
switch (sorting) {
case "createdAt":
searched.sort((a, b) => b.created_at - a.created_at);
break;
case "createdAtReversed":
searched.sort((a, b) => a.created_at - b.created_at);
break;
case "lastModified":
searched.sort((a, b) => b.last_modified - a.last_modified);
break;

View file

@ -44,6 +44,7 @@ export interface PackageRowInfo {
unityIncompatible: Map<string, TauriPackage>;
sources: Set<string>;
isThereSource: boolean; // this will be true even if all sources are hidden
visibleSources: Set<string>;
installed: null | {
version: TauriVersion;
yanked: boolean;
@ -92,7 +93,9 @@ export function combinePackagesAndProjectDetails(
const yankedVersions = new Set<`${string}:${string}`>();
const knownPackages = new Set<string>();
const packagesPerRepository = new Map<string, TauriPackage[]>();
const hiddenPackagesPerRepository = new Map<string, TauriPackage[]>();
const userPackages: TauriPackage[] = [];
const hiddenUserPackages: TauriPackage[] = [];
for (const pkg of packages) {
if (!showPrereleasePackages && pkg.version.pre) continue;
@ -107,13 +110,19 @@ export function combinePackagesAndProjectDetails(
let packages: TauriPackage[];
// check the repository is visible
if (pkg.source === "LocalUser") {
if (hideLocalUserPackages) continue;
packages = userPackages;
if (hideLocalUserPackages) {
packages = hiddenUserPackages;
} else {
packages = userPackages;
}
} else if ("Remote" in pkg.source) {
if (hiddenRepositoriesSet.has(pkg.source.Remote.id)) continue;
packages = packagesPerRepository.get(pkg.source.Remote.id) ?? [];
packagesPerRepository.set(pkg.source.Remote.id, packages);
if (hiddenRepositoriesSet.has(pkg.source.Remote.id)) {
packages = hiddenPackagesPerRepository.get(pkg.source.Remote.id) ?? [];
hiddenPackagesPerRepository.set(pkg.source.Remote.id, packages);
} else {
packages = packagesPerRepository.get(pkg.source.Remote.id) ?? [];
packagesPerRepository.set(pkg.source.Remote.id, packages);
}
} else {
assertNever(pkg.source);
}
@ -138,6 +147,7 @@ export function combinePackagesAndProjectDetails(
unityIncompatible: new Map(),
sources: new Set(),
isThereSource: false,
visibleSources: new Set(),
installed: null,
latest: { status: "none" },
stableLatest: { status: "none" },
@ -178,8 +188,14 @@ export function combinePackagesAndProjectDetails(
if (pkg.source === "LocalUser") {
packageRowInfo.sources.add("User");
if (!hideLocalUserPackages) {
packageRowInfo.visibleSources.add("User");
}
} else if ("Remote" in pkg.source) {
packageRowInfo.sources.add(pkg.source.Remote.display_name);
if (!hiddenRepositoriesSet.has(pkg.source.Remote.id)) {
packageRowInfo.visibleSources.add(pkg.source.Remote.display_name);
}
}
}
@ -187,6 +203,13 @@ export function combinePackagesAndProjectDetails(
packagesPerRepository.get("com.vrchat.repos.official")?.forEach(addPackage);
packagesPerRepository.get("com.vrchat.repos.curated")?.forEach(addPackage);
userPackages.forEach(addPackage);
hiddenUserPackages.forEach((pkg) => {
const packageRowInfo = getRowInfo(pkg);
packageRowInfo.isThereSource = true;
if (pkg.source === "LocalUser") {
packageRowInfo.sources.add("User");
}
});
packagesPerRepository.delete("com.vrchat.repos.official");
packagesPerRepository.delete("com.vrchat.repos.curated");
@ -201,6 +224,17 @@ export function combinePackagesAndProjectDetails(
packages.forEach(addPackage);
}
// process hidden repositories - only add to sources, not to version calculations
for (const packages of hiddenPackagesPerRepository.values()) {
packages.forEach((pkg) => {
const packageRowInfo = getRowInfo(pkg);
packageRowInfo.isThereSource = true;
if (pkg.source !== "LocalUser") {
packageRowInfo.sources.add(pkg.source.Remote.display_name);
}
});
}
// sort versions
for (const value of packagesTable.values()) {
value.unityCompatible = new Map(
@ -299,7 +333,7 @@ export function combinePackagesAndProjectDetails(
pkg.is_yanked ||
yankedVersions.has(`${pkg.name}:${toVersionString(pkg.version)}`),
};
packageRowInfo.isThereSource = knownPackages.has(pkg.name);
packageRowInfo.isThereSource = true;
// if we have the latest version, check if it's upgradable
if (packageRowInfo.latest.status !== "none") {

View file

@ -6,6 +6,8 @@ import {
useQueryClient,
} from "@tanstack/react-query";
import {
ChevronDown,
ChevronRight,
CircleArrowUp,
CircleMinus,
CirclePlus,
@ -91,6 +93,7 @@ export const PackageListCard = memo(function PackageListCard({
const [bulkUpdatePackageIdsRaw, setBulkUpdatePackageIds] = useState<string[]>(
[],
);
const [showHiddenPackages, setShowHiddenPackages] = useState(false);
const bulkUpdatePackageIds = useMemo(() => {
const packageIds = new Set(packageRowsData.map((p) => p.id));
@ -134,6 +137,22 @@ export const PackageListCard = memo(function PackageListCard({
);
}, [packageRowsData, search]);
const hiddenPackages = useMemo(() => {
return packageRowsData.filter(
(pkg) =>
pkg.visibleSources.size === 0 && pkg.isThereSource && !pkg.installed,
);
}, [packageRowsData]);
const visibleHiddenPackagesCount = useMemo(() => {
return hiddenPackages.filter((pkg) => filteredPackageIds.has(pkg.id))
.length;
}, [hiddenPackages, filteredPackageIds]);
const toggleShowHiddenPackages = useCallback(() => {
setShowHiddenPackages((prev) => !prev);
}, []);
const hiddenUserRepositories = useMemo(
() => new Set(repositoriesInfo?.hidden_user_repositories ?? []),
[repositoriesInfo],
@ -226,26 +245,79 @@ export const PackageListCard = memo(function PackageListCard({
</tr>
</thead>
<tbody>
{packageRowsData.map((row) => (
<tr
className="even:bg-secondary/30 anchor-none"
hidden={!filteredPackageIds.has(row.id)}
key={row.id}
>
<PackageRow
pkg={row}
bulkUpdateSelected={bulkUpdatePackageIds.some(
(id) => id === row.id,
)}
bulkUpdateAvailable={canBulkUpdate(
bulkUpdateMode,
bulkUpdateModeForPackage(row),
)}
addBulkUpdatePackage={addBulkUpdatePackage}
removeBulkUpdatePackage={removeBulkUpdatePackage}
/>
</tr>
))}
{packageRowsData.map((row) => {
if (
row.visibleSources.size === 0 &&
row.isThereSource &&
!row.installed
)
return null;
return (
<tr
className="even:bg-secondary/30 anchor-none"
hidden={!filteredPackageIds.has(row.id)}
key={row.id}
>
<PackageRow
pkg={row}
bulkUpdateSelected={bulkUpdatePackageIds.some(
(id) => id === row.id,
)}
bulkUpdateAvailable={canBulkUpdate(
bulkUpdateMode,
bulkUpdateModeForPackage(row),
)}
addBulkUpdatePackage={addBulkUpdatePackage}
removeBulkUpdatePackage={removeBulkUpdatePackage}
/>
</tr>
);
})}
{/* Hidden packages section */}
{hiddenPackages.length > 0 && (
<>
<tr
className="bg-secondary/50 hover:bg-secondary/70 cursor-pointer"
onClick={toggleShowHiddenPackages}
>
<td className="p-3.5 compact:py-1 w-1">
{showHiddenPackages ? (
<ChevronDown className="w-5 h-5" />
) : (
<ChevronRight className="w-5 h-5" />
)}
</td>
<td
colSpan={TABLE_HEAD.length + 1}
className="p-3.5 compact:py-1 font-medium text-sm text-muted-foreground"
>
{tc("projects:manage:hidden packages")} (
{visibleHiddenPackagesCount})
</td>
</tr>
{showHiddenPackages &&
hiddenPackages.map((row) => (
<tr
className="even:bg-secondary/30 anchor-none"
hidden={!filteredPackageIds.has(row.id)}
key={row.id}
>
<PackageRow
pkg={row}
bulkUpdateSelected={bulkUpdatePackageIds.some(
(id) => id === row.id,
)}
bulkUpdateAvailable={canBulkUpdate(
bulkUpdateMode,
bulkUpdateModeForPackage(row),
)}
addBulkUpdatePackage={addBulkUpdatePackage}
removeBulkUpdatePackage={removeBulkUpdatePackage}
/>
</tr>
))}
</>
)}
</tbody>
</ScrollableCardTable>
</CardContent>
@ -883,7 +955,7 @@ const PackageRow = memo(function PackageRow({
return (
<>
<td className={`${cellClass} w-1 compact:px-2`}>
<div className={"flex content-center aspect-square"}>
<div className={"flex items-center justify-center aspect-square"}>
<CheckboxDisabledIfLoading
checked={bulkUpdateSelected}
onCheckedChange={onClickBulkUpdate}
@ -921,27 +993,29 @@ const PackageRow = memo(function PackageRow({
<LatestPackageInfo info={pkg.latest} />
</td>
<td className={`${noGrowCellClass} max-w-32 overflow-hidden`}>
{pkg.sources.size === 0 ? (
{pkg.visibleSources.size === 0 ? (
pkg.isThereSource ? (
<p>{tc("projects:manage:source not selected")}</p>
) : (
<p>{tc("projects:manage:none")}</p>
)
) : pkg.sources.size === 1 ? (
) : pkg.visibleSources.size === 1 ? (
<Tooltip>
<TooltipTrigger>
<p className="overflow-hidden text-ellipsis">
{[...pkg.sources][0]}
{[...pkg.visibleSources][0]}
</p>
</TooltipTrigger>
<TooltipContent>{[...pkg.sources][0]}</TooltipContent>
<TooltipContent>{[...pkg.visibleSources][0]}</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger>
<p>{tc("projects:manage:multiple sources")}</p>
</TooltipTrigger>
<TooltipContent>{[...pkg.sources].join(", ")}</TooltipContent>
<TooltipContent>
{[...pkg.visibleSources].join(", ")}
</TooltipContent>
</Tooltip>
)}
</td>
@ -978,9 +1052,24 @@ const PackageRow = memo(function PackageRow({
</ButtonDisabledIfLoading>
</TooltipTrigger>
<TooltipContent>
{!latestVersion
? tc("projects:manage:tooltip:incompatible with unity")
: tc("projects:manage:tooltip:add package")}
{pkg.visibleSources.size === 0 && pkg.isThereSource ? (
<div className="flex flex-col gap-1">
<p>
{tc(
"projects:manage:tooltip:select repository to install",
)}
</p>
<p className="text-xs opacity-75">
{[...pkg.sources]
.filter((source) => !pkg.visibleSources.has(source))
.join(", ")}
</p>
</div>
) : !latestVersion ? (
tc("projects:manage:tooltip:incompatible with unity")
) : (
tc("projects:manage:tooltip:add package")
)}
</TooltipContent>
</Tooltip>
)}

View file

@ -188,7 +188,7 @@
}
}
:root {
body {
--toastify-font-family: var(--font-sans);
--toastify-color-light: var(--background);
/*--toastify-color-info: #3498db;*/
@ -303,6 +303,17 @@ html {
@apply pe-2.5;
}
/*
* Add padding end for the content area of scrollable card if vertical scroll bar is visible
* This prevents the table / items from being hidden behind the vertical scroll bar
*/
.vrc-get-scrollable-card:has(
> .vrc-get-scrollable-card-vertical-bar
) > div[data-radix-scroll-area-viewport]
> div {
@apply pe-2.5;
}
.vrc-get-sidebar-hostname-warning-container {
contain-intrinsic-size: 0 7em;
contain: size;

View file

@ -0,0 +1,70 @@
Name: alcom
Version: 1.1.6
Release: 1%{?dist}
Summary: A short description of my custom application
%global git_version %(echo "%{version}" | tr '~' '-')
License: MIT
URL: https://vrc-get.anatawa12.com/alcom/
Source0: https://github.com/vrc-get/vrc-get/archive/gui-v%{git_version}.tar.gz
BuildRequires: gcc
BuildRequires: nodejs
BuildRequires: npm
BuildRequires: pkgconfig(gtk+-3.0)
BuildRequires: pkgconfig(webkit2gtk-4.1)
BuildRequires: pkgconfig(openssl)
# we download rust toolchain manually when building inside mock container
%if ! 0%{?install_rust:1}
BuildRequires: cargo
%endif
# disable stripping symbols.
%global __os_install_post %{nil}
%global debug_package %{nil}
%description
ALCOM - Alternative Creator Companion
ALCOM is a fast and open-source alternative VCC (VRChat Creator Companion) written in rust and tauri.
%prep
%setup -q -n vrc-get-gui-v%{git_version}
%if 0%{?install_rust:1}
echo "=== Mock environment detected. Installing isolated Rust toolchain ==="
export RUSTUP_HOME="$(pwd)/.rustup"
export CARGO_HOME="$(pwd)/.cargo"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
cat << EOF > ./load_rust_env.sh
export RUSTUP_HOME="${RUSTUP_HOME}"
export CARGO_HOME="${CARGO_HOME}"
source "${CARGO_HOME}/env"
EOF
%endif
# marker: ci inserts version update here
%build
%{?install_rust: source ./load_rust_env.sh}
cargo xtask build-alcom --release
%install
%{?install_rust: source ./load_rust_env.sh}
rm -rf %{buildroot}
cargo xtask bundle-alcom --release --bundles buildroot --buildroot=%{buildroot}
%files
%license LICENSE
# %doc vrc-get-gui/README.md
#%doc vrc-get-gui/CHANGELOG.md
%{_bindir}/alcom
%{_datadir}/applications/alcom.desktop
%{_datadir}/icons/hicolor/*/apps/alcom.png
%changelog
* Migrated to native rpm build pipeline with spec file

View file

@ -1,10 +0,0 @@
Package: alcom
Version: {{version}}-1
Architecture: {{arch}}
Installed-Size: {{estimated_size}}
Maintainer: anatawa12 <i@anatawa12.com>
Priority: optional
Homepage: https://vrc-get.anatawa12.com/alcom/
Depends: libwebkit2gtk-4.1-0, libgtk-3-0, libc6 (>= {{libc_version}}), libc6 (>= {{libgcc_version}})
Description: ALCOM - Alternative Creator Companion
ALCOM is a fast and open-source alternative VCC (VRChat Creator Companion) written in rust and tauri.

7
vrc-get-gui/bundle/debian/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
/*-build-stamp
/*.substvars
/.debhelper
/files
/cargo_home
/npm_cache
alcom/

View file

@ -0,0 +1,5 @@
alcom for Debian
ALCOM is a fast and open-source alternative VCC (VRChat Creator Companion) written in rust and tauri.
-- anatawa12 <i@anatawa12.com> Thu, 11 Jun 2026 14:34:57 +0000

View file

@ -0,0 +1,20 @@
alcom for Debian
This is README for alcom Debian package.
This Debian package is made to provide one of official distribution of alcom, formaly known as vrc-get-gui.
Starting with 1.1.7 or later, alcom maintainer switched from custom .deb toolchain to official .deb toolchain.
This directory contains source code of debian package as non-native package.
This directory is placed at vrc-get-gui/bundles/debian on original source tree, but before building debian package,
you must copy vrc-get-gui/bundles/debian to debian.
Violating best practice of debian package, this package requires network access, to download cargo dependencies on build.
In addition, by declaring `INSTALL_RUST` environment variable to `1` with `--set-envvar=INSTALL_RUST=1`, you can let
build process to install newest available rust to build on older distribution does not provide new enough rust version.
You also install NODEJS on build by declaring `INSTALL_NODEJS=1`.
Those options are helpful when building inside sandbox like pbuilder.
This Debian package is based on package generated by debmake Version 4.5.1.
-- anatawa12 <i@anatawa12.com> Thu, 11 Jun 2026 14:34:57 +0000

View file

@ -0,0 +1,5 @@
alcom (1.1.6-1) UNRELEASED; urgency=low
* No changelog are provided for this package
-- root <i@anatawa12.com> Thu, 11 Jun 2026 14:34:57 +0000

View file

@ -0,0 +1,8 @@
target/
vrc-get-gui/node_modules/
vrc-get-gui/out/
vrc-get-gui/gen/
debian/cargo_home/
debian/npm_cache/
debian/rustup_home/
debian/nodejs_installed/

View file

@ -0,0 +1,29 @@
Source: alcom
Priority: optional
Maintainer: anatawa12 <i@anatawa12.com>
Build-Depends:
debhelper-compat (= 13),
libssl-dev,
pkg-config,
cargo,
nodejs,
npm,
# curl for rust-install build only
curl,
libgtk-3-dev,
libwebkit2gtk-4.1-dev (>= 2.41),
Standards-Version: 4.7.0
Homepage: https://vrc-get.anatawa12.com/alcom/
Rules-Requires-Root: no
Package: alcom
Architecture: any
Multi-Arch: foreign
Depends:
${misc:Depends},
${shlibs:Depends},
Description: ALCOM - Alternative Creator Companion
ALCOM is a fast and open-source alternative VCC (VRChat Creator Companion) written in rust and tauri.
.
This package is one of official distribution of ALCOM, released as a part of the updates from ALCOM.
No packaging only updates will be provided.

View file

@ -0,0 +1,41 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: alcom
Upstream-Contact: <preferred name and address to reach the upstream project>
Source: <url://example.com>
Files: *
Copyright: Copyright (c) 2023 anatawa12 and other contribcmeutors
License: MIT
MIT License
.
Copyright (c) 2023 anatawa12 and other contributors
.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
.
Files: vrc-get-gui/third-party/Anton-Regular.ttf
vrc-get-gui/third-party/NotoSans-Italic-VariableFont_wdth,wght.ttf
vrc-get-gui/third-party/NotoSans-VariableFont_wdth,wght.ttf
Copyright: 2020 The Anton Project Authors (https://github.com/googlefonts/AntonFont.git)
2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
License: SIL Open Font License 1.1
Files: vrc-get-gui/icons/*
Copyright: 2024 lilxyzw, anatawa12 and other contributors
License: CC-BY-4.0

39
vrc-get-gui/bundle/debian/rules Executable file
View file

@ -0,0 +1,39 @@
#!/usr/bin/make -f
export DEB_BUILD_OPTIONS += nostrip
export CARGO_HOME = $(CURDIR)/debian/cargo_home
export NPM_CONFIG_CACHE = $(CURDIR)/debian/npm_cache
%:
dh $@
# install rust in configure phase when requested
ifeq ($(INSTALL_RUST),1)
export RUSTUP_HOME = $(CURDIR)/debian/rustup_home
export PATH := $(CURDIR)/debian/cargo_home/bin:$(PATH)
override_dh_auto_configure::
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
endif
ifeq ($(INSTALL_NODEJS),1)
export RUSTUP_HOME = $(CURDIR)/debian/rustup_home
export PATH := $(CURDIR)/debian/nodejs_installed/bin:$(PATH)
ifeq ($(DEB_HOST_ARCH),amd64)
NODE_ARCH := x64
else ifeq ($(DEB_HOST_ARCH),arm64)
NODE_ARCH := arm64
endif
override_dh_auto_configure::
mkdir -p $(CURDIR)/debian/nodejs_installed
curl --proto '=https' --tlsv1.2 -sSf https://nodejs.org/dist/v26.3.0/node-v26.3.0-linux-$(NODE_ARCH).tar.gz | gunzip | tar x --strip-components 1 -C $(CURDIR)/debian/nodejs_installed
endif
override_dh_auto_build:
cargo xtask build-alcom --release
override_dh_auto_install:
cargo xtask bundle-alcom --release --bundles buildroot --buildroot=$(CURDIR)/debian/alcom

View file

@ -0,0 +1 @@
3.0 (quilt)

View file

@ -0,0 +1,24 @@
# Metadata about the upstream project.
# See https://wiki.debian.org/UpstreamMetadata
Bug-Database: https://github.com/vrc-get/vrc-get/issues
Bug-Submit: https://github.com/vrc-get/vrc-get/issues/new
Changelog: https://github.com/vrc-get/vrc-get/blob/master/vrc-get-gui/CHANGELOG.md
Documentation: https://vrc-get.anatawa12.com/alcom/
Repository-Browse: https://github.com/vrc-get/vrc-get
Repository: https://github.com/vrc-get/vrc-get.git
#FAQ: https://github.com/<user>/<project>/blob/main/FAQ.md
Donation: https://github.com/sponsors/anatawa12
#Registration: https://github.com/signup
#Archive: PyPI # or CPAN, boost, etc.
# Uncomment these and fill them out to help users evaluate upstream:
#Gallery: https://github.com/<user>/<project>/wiki/pictures-made-with-this-program
#Screenshots: # pictures *of* the program, as opposed to pictures *made with* the program
# - https://github.com/<user>/<project>/wiki/login-screen.png
# - https://github.com/<user>/<project>/wiki/help-menu.png
# - ...
# Uncomment these and fill them out to help resolve security issues:
#Security-Contact: <how to send security-related messages>
#CPE: <space-separated Common Platform Enumerator values - see https://wiki.debian.org/CPEtagPackagesDep>

View file

@ -0,0 +1,10 @@
# You must remove unused comment lines for the released package.
# Compulsory line, this is a version 4 file
version=4
opts=\
filenamemangle=s%.*/@ANY_VERSION@%@PACKAGE@-$1.tar.gz%,\
downloadurlmangle=s%(api.github.com/repos/[^/]+/[^/]+)/git/refs/%$1/tarball/refs/%g,\
uversionmangle=s/(\d)[-]?((RC|rc|pre|dev|beta|alpha)\.\d*)$/$1~$2/,\
searchmode=plain \
https://api.github.com/repos/vrc-get/vrc-get/git/matching-refs/tags/gui- \
https://api.github.com/repos/[^/]+/[^/]+/git/refs/tags/gui-@ANY_VERSION@

View file

@ -3,7 +3,7 @@
import { QueryClientProvider } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
import type React from "react";
import { Suspense, useCallback, useEffect } from "react";
import { Suspense, useCallback, useEffect, useEffectEvent } from "react";
import { useTranslation } from "react-i18next";
import { ToastContainer } from "react-toastify";
import Loading from "@/app/-loading";
@ -16,19 +16,37 @@ import { isFindKey, useDocumentEvent } from "@/lib/events";
import { tc } from "@/lib/i18n";
import { processResult } from "@/lib/import-templates";
import { queryClient } from "@/lib/query-client";
import { toastError, toastSuccess, toastThrownError } from "@/lib/toast";
import {
toastError,
toastSuccess,
toastThrownError,
toastWarning,
} from "@/lib/toast";
import { useTauriListen } from "@/lib/use-tauri-listen";
export function Providers({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
useTauriListen<LogEntry>("log", (event) => {
const entry = event.payload as LogEntry;
if (entry.level === "Error" && entry.gui_toast) {
const showToastForLog = useEffectEvent((entry: LogEntry) => {
if (entry.level === "Error" && (entry.gui_toast ?? true)) {
toastError(entry.message);
} else if (entry.level === "Warn" && (entry.gui_toast ?? false)) {
toastWarning(entry.message);
}
});
useTauriListen<LogEntry>("log", (event) => {
showToastForLog(event.payload);
});
useEffect(() => {
commands.utilGetLogEntries().then((value) => {
for (const entry of value) {
showToastForLog(entry);
}
});
}, []);
const moveToRepositories = useCallback(() => {
if (location.pathname !== "/packages/repositories") {
navigate({ to: "/packages/repositories" });

View file

@ -40,7 +40,8 @@ export const commands = {
environmentSetHideLocalUserPackages: (value: boolean) => __TAURI_INVOKE<null>("environment_set_hide_local_user_packages", { value }),
environmentDownloadRepository: (url: string, headers: { [key in string]: string }) => __TAURI_INVOKE<TauriDownloadRepository>("environment_download_repository", { url, headers }),
environmentAddRepository: (url: string, headers: { [key in string]: string }) => __TAURI_INVOKE<TauriAddRepositoryResult>("environment_add_repository", { url, headers }),
environmentRemoveRepository: (id: string) => __TAURI_INVOKE<null>("environment_remove_repository", { id }),
environmentRemoveRepository: (index: number, expectedId: string) => __TAURI_INVOKE<null>("environment_remove_repository", { index, expectedId }),
environmentReorderRepositories: (repos: TauriUserRepositoryRef[]) => __TAURI_INVOKE<null>("environment_reorder_repositories", { repos }),
environmentImportRepositoryPick: () => __TAURI_INVOKE<TauriImportRepositoryPickResult>("environment_import_repository_pick"),
environmentImportDownloadRepositories: (channel: string, repositories: TauriRepositoryDescriptor[]) => __TAURI_INVOKE<AsyncCallResult<number, ([TauriRepositoryDescriptor, TauriDownloadRepository])[]>>("environment_import_download_repositories", { channel, repositories }),
environmentImportAddRepositories: (repositories: TauriRepositoryDescriptor[]) => __TAURI_INVOKE<null>("environment_import_add_repositories", { repositories }),
@ -92,6 +93,7 @@ export const commands = {
projectSetUnityPath: (projectPath: string, unityPath: string | null) => __TAURI_INVOKE<boolean>("project_set_unity_path", { projectPath, unityPath }),
utilOpen: (path: string, ifNotExists: OpenOptions) => __TAURI_INVOKE<null>("util_open", { path, ifNotExists }),
utilOpenUrl: (url: string) => __TAURI_INVOKE<null>("util_open_url", { url }),
utilOpenUrlNocheck: (url: string) => __TAURI_INVOKE<null>("util_open_url_nocheck", { url }),
utilGetLogEntries: () => __TAURI_INVOKE<LogEntry_Serialize[]>("util_get_log_entries"),
utilGetVersion: () => __TAURI_INVOKE<string>("util_get_version"),
utilCheckForUpdate: () => __TAURI_INVOKE<{
@ -165,7 +167,7 @@ export type LogEntry_Deserialize = {
level: LogLevel,
target: string,
message: string,
gui_toast: boolean,
gui_toast: boolean | null,
};
export type LogEntry_Serialize = {
@ -173,7 +175,7 @@ export type LogEntry_Serialize = {
level: LogLevel,
target: string,
message: string,
gui_toast: boolean,
gui_toast: boolean | null,
};
export type LogLevel = "Error" | "Warn" | "Info" | "Debug" | "Trace";
@ -404,11 +406,17 @@ export type TauriUserPackage = {
};
export type TauriUserRepository = {
index: number,
id: string,
url: string | null,
display_name: string,
};
export type TauriUserRepositoryRef = {
index: number,
id: string,
};
export type TauriVersion = {
major: number,
minor: number,

View file

@ -1,6 +1,16 @@
import type React from "react";
import { tc } from "@/lib/i18n";
export function dayToString(dateIn: Date | number | string) {
const date = typeof dateIn !== "object" ? new Date(dateIn) : dateIn;
const year = date.getFullYear().toString().padStart(4, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
return `${year}-${month}-${day}`;
}
export function dateToString(dateIn: Date | number | string) {
const date = typeof dateIn !== "object" ? new Date(dateIn) : dateIn;

View file

@ -149,7 +149,7 @@ function UnityInstallWindow({
dialog: DialogContext<void>;
}) {
const openUnityHub = async () => {
await commands.utilOpenUrl(installWithUnityHubLink);
await commands.utilOpenUrlNocheck(installWithUnityHubLink);
};
return (

View file

@ -14,6 +14,12 @@ export function toastNormal(message: ToastContent) {
});
}
export function toastWarning(message: ToastContent) {
toast.warning(wrapWithDiv(message), {
pauseOnFocusLoss: false,
});
}
export function toastInfo(message: ToastContent) {
toast.info(wrapWithDiv(message), {
pauseOnFocusLoss: false,

View file

@ -25,10 +25,11 @@
"general:unknown date": "Unbekanntes Datum",
"general:dialog:select file or directory header": "Datei oder Ordner auswählen",
"general:dialog:select file or directory": "Bitte Datei oder Ordner auswählen",
"general:toast:invalid directory": "Ungültiger Ordner wurde ausgewählt",
"general:dialog:select file or directory": "Bitte Datei oder Ordner auswählen.",
"general:toast:invalid directory": "Ungültiger Ordner wurde ausgewählt.",
"general:toast:invalid file": "Ungültige Datei wurde ausgewählt.",
"general:toast:not supported": "{{name}} wird noch nicht unterstützt",
"general:toast:not supported": "{{name}} wird noch nicht unterstützt.",
"general:not implemented": "Nicht implementiert",
"search:placeholder": "Suchen...",
@ -113,6 +114,7 @@
"projects:grid view": "Gitteransicht",
"projects:sort by": "Sortierreihenfolge:",
"projects:error:load error": "Fehler beim Laden der Projekte: {{msg}}",
"projects:error:noexec filesystem": "Das Projekt befindet sich auf einem Dateisystem dass das 'noexec' flag gesetzt hat. Dies hindert Unity daran native Plugins zu laden, und führt zu einem korrupten Zustand. Bitte verschiebe das Projekt auf ein Dateisystem ohne 'noexec' flag.",
"projects:toast:project added": "Projekt erfolgreich hinzugefügt",
"projects:toast:project already exists": "Das Projekt wurde bereits hinzugefügt.",
@ -216,6 +218,7 @@
"projects:manage:button:unity migrate": "Migriere Projekt",
"projects:manage:manage packages": "Pakete verwalten",
"projects:manage:hidden packages": "Versteckte Pakete",
"projects:manage:tooltip:refresh packages": "Pakete aktualisieren",
"projects:manage:button:upgrade all": "Alle Aktualisieren",
"projects:manage:button:upgrade all stable": "Alle Aktualisieren (Stabil)",
@ -232,6 +235,7 @@
"projects:manage:source not selected": "Inaktive Quelle",
"projects:manage:multiple sources": "Mehrere Quellen",
"projects:manage:tooltip:add package": "Paket hinzufügen",
"projects:manage:tooltip:select repository to install": "Paketquelle für Installation auswählen",
"projects:manage:tooltip:upgrade package": "Paket upgraden",
"projects:manage:tooltip:remove packages": "Paket entfernen",
"projects:manage:tooltip:incompatible with unity": "Nicht unterstützt",
@ -440,7 +444,7 @@
// Reset your PC is a feature in Windows, so please check your translation is consistent with Windows terminology.
// It's at Setting app > System > Recovery > Reset this PC
"settings:warning:in-local-app-data": "Gewählter Pfad ist im LocalAppData Verzeichnis, welcher über \"Diesen PC zurücksetzen\" mit \"Eigene Dateien behalten\" gelöscht wird.\nBitte wähle einen anderen Pfad.",
"settings:warning:in-app-data": "Gewählter Pfad ist im LocalAppData Verzeichnis, welcher über \"Diesen PC zurücksetzen\" mit \"Eigene Dateien behalten\" gelöscht wird.\nBitte wähle einen anderen Pfad.",
"settings:warning:whitespace": "Der Pfad enthält Leerzeichen. Dies kann zu Problemen mit Unity und anderen Tools führen. Bitte wähle einen anderen Pfad.",
"settings:warning:non-ascii": "Der Pfad enthält nicht-ASCII Zeichen. Dies kann zu Problemen mit Unity und anderen Tools führen. Bitte wähle einen anderen Pfad.",
@ -490,6 +494,10 @@
"settings:show prerelease": "Vorschau-Pakete anzeigen",
"settings:show prerelease description": "Aktiviere Vorschau-Pakete, um diese in der Paketliste anzuzeigen. Sie werden auch bei der Auflösung von Abhängigkeiten verwendet.",
"settings:dialog:show prerelease packages": "Zeige Vorschau-Pakete",
"settings:dialog:show prerelease packages description": "Vorschau-Pakete sind unfertige und ungetestete Versionen.<br>Die Wahrscheinlichkeit auf Fehler zu stoßen ist höher.<br>Möchtest du Vorschau-Pakete anzeigen?",
"settings:dialog:enable show prerelease packages": "Anzeigen",
"settings:gui animation": "Aktiviere Animationen",
"settings:gui animation description": "Ist diese Option aktiviert, werden einige Seitenwechsel animiert. Dies funktioniert auf Systemen mit älteren Webview Versionen evtl. nicht richtig.",

View file

@ -37,6 +37,8 @@
"general:error:failed to create dir": "Failed to create directory (missing permission?): {{err}}",
"general:error:failed to create dir missing drive": "Failed to create directory. You may forget to connect the drive.",
"general:created at": "Added",
"general:last modified": "Last Modified",
"general:last modified:moments": "Moments ago",
"general:last modified:minutes_one": "{{count}} minute ago",
@ -218,6 +220,7 @@
"projects:manage:button:unity migrate": "Migrate Project",
"projects:manage:manage packages": "Manage Packages",
"projects:manage:hidden packages": "Hidden Packages",
"projects:manage:tooltip:refresh packages": "Refresh Packages",
"projects:manage:button:upgrade all": "Upgrade All (Latest)",
"projects:manage:button:upgrade all stable": "Upgrade All (Stable)",
@ -234,6 +237,7 @@
"projects:manage:source not selected": "Not selected",
"projects:manage:multiple sources": "Multiple sources",
"projects:manage:tooltip:add package": "Add Package",
"projects:manage:tooltip:select repository to install": "Select repository to install package",
"projects:manage:tooltip:upgrade package": "Upgrade Package",
"projects:manage:tooltip:remove packages": "Remove Package",
"projects:manage:tooltip:incompatible with unity": "Incompatible with Unity",

View file

@ -6,6 +6,11 @@
'check update:dialog:downloading...': 'Téléchargement de la mise à jour...',
'check update:dialog:latest version': 'Dernière version :',
'check update:dialog:new version description': "Une nouvelle version d'ALCOM est disponible.",
'check update:dialog:new version no platform description': "Une nouvelle version d'ALCOM est disponible, mais votre plateforme n'est plus supportée.<br>Si votre système supporte une autre type de plateforme, s'il vous plait téléchargez cette dernière à la place.",
'check update:dialog:new version not updatable description': "Une nouvelle version d'ALCOM est disponible.<br>Cependant, cette installation ne peut être mise à jour automatiquement.<br>S'il vous plait installez la nouvelle version manuellement.",
'check update:dialog:new version updater disabled base description': "Une nouvelle version d'ALCOM est disponible.",
'check update:dialog:new version updater how to upgrade fallback': "Les mises a jour automatiques sont désactivées. S'il vous plait mettez a jour l'application via votre gestionnaire de packets.",
'check update:dialog:open download page': 'Ouvrir la page de téléchargement',
'check update:dialog:relaunching...': "Redémarrage d'ALCOM...",
'check update:dialog:title': "Mises à jour d'ALCOM",
'check update:dialog:update': 'Téléchargement et Mises a jour !',
@ -45,6 +50,7 @@
'general:packages': 'Packages',
'general:source': 'Source',
'general:toast:invalid directory': 'Le dossier projet sélectionné est invalide.',
'general:toast:invalid file': 'Fichier invalide sélectionné.',
'general:toast:not supported': "{{name}} n'est pas encore supporté.",
'general:unknown date': 'Date inconnue',
'general:version': 'Version',
@ -101,6 +107,7 @@
'projects:dialog:warn removing project': 'Vous vous apprêtez a supprimer le projet <b>{{name}}</b>. Êtes vous sur ?',
'projects:do not close': 'Ne fermez pas cette fenêttre.',
'projects:error:load error': 'Erreur lors du chargement des projets: {{msg}}',
'projects:error:noexec filesystem': "Le projet se trouve sur un système de fichier (FS) monté avec le tag 'noexec'. Cela empèche Unity de charger correctement les plugins natifs. Cela peut provoquer des états incompatibles ou corrompre certains objets. S'il vous plait, déplacez le projet vers un système de fichier sans le tag 'noexec'.",
'projects:grid view': 'Vue en Grille',
'projects:hint:create project ready': 'Prêt a créer un projet !',
'projects:hint:invalid project name': 'Nom de projet invalide.',
@ -159,6 +166,7 @@
'projects:manage:dialog:upgrade minor': "Vous êtes en train d'upgrader la version de Unity. Cela pourrais provoquer des changements dans vos scripts et votre Library pourrais devoir être reconstruite.<br/>Faites un backup de votre projet avant de poursuivre.<br/>Voullez vous continuer ?",
'projects:manage:dialog:upgrade minor vrchat unsupported': "Vous êtes en train d'upgrader la version de Unity. Cela pourrais provoquer des changements dans vos scripts et votre Library pourrais devoir être reconstruite.<br/>Faites un backup de votre projet avant de poursuivre.<br/>De plus vous utiliserez une version de Unity non supportée par VRChat, vous empèchant d'uploader du contenu après l'upgrade.<br/>Voullez vous continuer ?",
'projects:manage:dialog:upgrade package': 'Mise a jour de <b>{{name}}</b> de la version {{previousVersion}} vers <b>{{version}}</b>',
'projects:manage:hidden packages': 'Packets cachés',
'projects:manage:incompatible packages': 'Incompatibles',
'projects:manage:installed': 'Installé',
'projects:manage:latest': 'Dernière version',
@ -195,13 +203,14 @@
'projects:manage:tooltip:incompatible with unity': 'Incompatible avec Unity',
'projects:manage:tooltip:refresh packages': 'Rafraichir les packets',
'projects:manage:tooltip:remove packages': 'Supprimer le packet',
'projects:manage:tooltip:select repository to install': 'Sélectionnez le dépot pour installer le packet',
'projects:manage:tooltip:upgrade package': 'Mettre a jour un package',
'projects:manage:unity version': 'Version de Unity: ',
'projects:manage:yanked': 'projects:manage:yanked',
'projects:menuitem:backup': 'Faire un backup',
'projects:menuitem:change launch options': 'Changer les options de démarrage',
'projects:menuitem:copy project': 'Copier le projet',
'projects:menuitem:forget unity path': '===Oublier Unity pour ce projet',
'projects:menuitem:forget unity path': 'Oublier Unity pour ce projet',
'projects:menuitem:open directory': 'Ouvrir le dossier projet',
'projects:migrating...': 'Migration du projet...',
'projects:pre-migrate copying...': 'Copie du projet en cours pour la migration...',
@ -219,7 +228,7 @@
'projects:toast:backup canceled': 'Le backup a été annulé',
'projects:toast:backup succeeded': 'Le backup a été crée avec succès.',
'projects:toast:close unity before migration': 'Unity dois être fermé avant la migration.',
'projects:toast:forgot unity path': '===Unity oublié pour ce projet.',
'projects:toast:forgot unity path': 'Unity oublié pour ce projet.',
'projects:toast:invalid project unity version': "Nous n'avons pu détecter aucune version de Unity utilisable.",
'projects:toast:loading unity from unity hub': 'Chargement de Unity depuis le Unity Hub...',
'projects:toast:match version unity not found': 'Aucune version compatible de Unity trouvé. Installez Unity dans un premier temps ou ajouter une version de Unity manuellement dans les réglages de ALCOM',
@ -282,6 +291,9 @@
'settings:default unity arguments': 'Arguments CLI de Unity',
'settings:default unity arguments description': 'Ces arguments CLI seront utilisés lorsque vous ouvrirez Unity via ALCOM.',
'settings:dialog:default launch arguments': 'Arguments de Untiy par défaut',
'settings:dialog:enable show prerelease packages': 'Afficher',
'settings:dialog:show prerelease packages': 'Afficher les packets expérimentaux',
'settings:dialog:show prerelease packages description': 'Les packets expérimentaux peuvent avoir plus de bugs que les packets stables.<br>Êtes vous sur de vouloir afficher les packets expérimentaux ?',
'settings:error:load error': 'Erreur de chargement des réglages.',
'settings:files and directories': 'Fichiers et Dossiers',
'settings:files and directories:description': 'Accédez rapidement aux fichiers et dossiers relatifs à ALCOM.',
@ -324,7 +336,7 @@
'settings:use legacy unity hub loading': 'Utiliser une ancienne version du chargement du Unity Hub',
'settings:use legacy unity hub loading description': "L'ancien mode de chargement du Unity Hub est plus fiable pour charger Unity depuis le Unity Hub en comparaison de la nouvelle méthode, cependant l'ancien mode prends plus de temps.<br/>Cet ancien mode sera supprimé dans le futur donc si vous avez des problèmes de chargement avec cette nouvelle méthode, n'hésitez pas a ouvrir une réclamation au développeur dans la section appropriée.",
'settings:use vcc scheme description': "Le schéma d'URL <code>vcc:</code> est utilisé pour ouvrir VCC afin d'ajouter un dépot depuis un navigateur WEB.<br>En tant qu'alternative à VCC, vous pouvez utiliser ALCOM en tant qu'application principale.<br>Si cette option est activée, ALCOM enregistrera le schéma d'URL <code>vcc:</code> au démarrage.<br>Après une (ré)installation/mise a jour de VCC, vous devrez redémarrer ALCOM ou manuellement ré-enregistrer VCC avec le bouton d'enregistrement dédié.",
'settings:warning:in-local-app-data': 'Ce chemin existe au sein du dossier LocalAppData, il sera supprimé en utilisant "Réinitialiser cet ordinateur" avec l\'option "Conserver mes fichiers".<br>Il est recommandé de stocker vos données dans un autre emplacement.',
'settings:warning:in-app-data': 'Ce chemin existe au sein du dossier AppData, il sera supprimé en utilisant "Réinitialiser cet ordinateur" avec l\'option "Conserver mes fichiers".<br>Il est recommandé de stocker vos données dans un autre emplacement.',
'settings:warning:non-ascii': 'Cet emplacement contient des caracthères non-ASCII. les caracthères non-ASCII peuvent provoquer des erreurs avec Unity ou des outils tierces. Préférez un autre emplacement si possible.',
'settings:warning:whitespace': 'Cet emplacement contient des espaces. Les espaces peuvent provoquer des erreurs avec Unity ou des outils tierces. Préférez un autre emplacement si possible.',
'settings:webview version': 'Version de la Webview',
@ -456,4 +468,4 @@
'vpm repositories:tooltip:remove curated or official repository': 'Vous ne pouvez pas supprimer les dépots officiels ou épurés par VRChat',
'vpm repositories:url': 'URL',
},
}
}

View file

@ -37,6 +37,8 @@
"general:error:failed to create dir": "ディレクトリの作成に失敗しました: {{err}}",
"general:error:failed to create dir missing drive": "ディレクトリの作成に失敗しました。保存先のドライブの接続を忘れているかもしれません。",
"general:created at": "追加日",
"general:last modified": "最終更新日",
"general:last modified:moments": "たった今",
"general:last modified:minutes": "{{count}}分前",
@ -212,6 +214,7 @@
"projects:manage:button:unity migrate": "プロジェクトを移行",
"projects:manage:manage packages": "パッケージ管理",
"projects:manage:hidden packages": "非表示のパッケージ",
"projects:manage:tooltip:refresh packages": "パッケージリストを再読み込み",
"projects:manage:button:upgrade all": "すべて更新(最新)",
"projects:manage:button:upgrade all stable": "すべて更新(安定版)",
@ -228,6 +231,7 @@
"projects:manage:source not selected": "未選択",
"projects:manage:multiple sources": "複数ソース",
"projects:manage:tooltip:add package": "パッケージを追加",
"projects:manage:tooltip:select repository to install": "パッケージを提供しているリポジトリを選択してください。",
"projects:manage:tooltip:upgrade package": "パッケージを更新",
"projects:manage:tooltip:remove packages": "パッケージを除去",
"projects:manage:tooltip:incompatible with unity": "使用中のUnityと互換性がありません。",

View file

@ -215,6 +215,7 @@
"projects:manage:button:unity migrate": "프로젝트 마이그레이션",
"projects:manage:manage packages": "패키지 관리",
"projects:manage:hidden packages": "숨겨진 패키지",
"projects:manage:tooltip:refresh packages": "패키지 목록 새로고침",
"projects:manage:button:upgrade all": "모두 업데이트 (최신 버전)",
"projects:manage:button:upgrade all stable": "모두 업데이트 (안정 버전)",
@ -231,6 +232,7 @@
"projects:manage:source not selected": "미선택",
"projects:manage:multiple sources": "여러 소스",
"projects:manage:tooltip:add package": "패키지 추가",
"projects:manage:tooltip:select repository to install": "설치하려면 패키지를 제공하는 저장소를 선택해주세요.",
"projects:manage:tooltip:upgrade package": "패키지 업데이트",
"projects:manage:tooltip:remove packages": "패키지 제거",
"projects:manage:tooltip:incompatible with unity": "현재 사용중인 Unity와 호환되지 않습니다.",

View file

@ -212,6 +212,7 @@
"projects:manage:button:unity migrate": "迁移项目",
"projects:manage:manage packages": "管理软件包",
"projects:manage:hidden packages": "隐藏的软件包",
"projects:manage:tooltip:refresh packages": "刷新软件包",
"projects:manage:button:upgrade all": "升级所有软件包(最新)",
"projects:manage:button:upgrade all stable": "升级所有软件包(稳定)",
@ -228,6 +229,7 @@
"projects:manage:source not selected": "未选择",
"projects:manage:multiple sources": "多个源",
"projects:manage:tooltip:add package": "安装软件包",
"projects:manage:tooltip:select repository to install": "选择存储库以安装软件包",
"projects:manage:tooltip:upgrade package": "升级软件包",
"projects:manage:tooltip:remove packages": "删除软件包",
"projects:manage:tooltip:incompatible with unity": "与 Unity 不兼容",

View file

@ -27,6 +27,7 @@
"general:dialog:select file or directory header": "選擇檔案或資料夾",
"general:dialog:select file or directory": "請選擇一個檔案或資料夾。",
"general:toast:invalid directory": "選擇了無效路徑。",
"general:toast:invalid file": "選擇了無效檔案。",
"general:toast:not supported": "尚未支援 {{name}}。",
"general:not implemented": "未實現",
@ -113,6 +114,7 @@
"projects:grid view": "網格視圖",
"projects:sort by": "排序方式:",
"projects:error:load error": "加載專案時發生錯誤: {{msg}}",
"projects:error:noexec filesystem": "此專案目前位於一個啟用了 noexec 掛載設定的檔案系統中。這個設定會阻止 Unity 載入原生外掛,可能導致專案無法正常運作,甚至造成素材或資源損毀。請把專案移到沒有啟用 noexec 的磁碟或資料夾中。",
"projects:toast:project added": "專案已添加成功。",
"projects:toast:project already exists": "已添加過此專案。",
@ -216,6 +218,7 @@
"projects:manage:button:unity migrate": "遷移專案",
"projects:manage:manage packages": "管理套件",
"projects:manage:hidden packages": "隱藏套件",
"projects:manage:tooltip:refresh packages": "刷新套件",
"projects:manage:button:upgrade all": "升級全部(最新版)",
"projects:manage:button:upgrade all stable": "升級全部(穩定版)",
@ -232,6 +235,7 @@
"projects:manage:source not selected": "未選擇",
"projects:manage:multiple sources": "多個來源",
"projects:manage:tooltip:add package": "添加套件",
"projects:manage:tooltip:select repository to install": "選擇儲存庫以安裝套件",
"projects:manage:tooltip:upgrade package": "升級套件",
"projects:manage:tooltip:remove packages": "移除套件",
"projects:manage:tooltip:incompatible with unity": "與 Unity 不相容",
@ -386,7 +390,7 @@
"user packages:dialog:remove package": "移除套件",
"user packages:dialog:confirm remove description": "你要移除位於 {{path}} 的套件 <b>{{name}}</b> 嗎?",
"user packages:dialog:button:remove package": "移除套件",
"user packages:toast:invalid selection": "選定的套件無效。",
"user packages:toast:invalid selection": "選套件無效。",
"user packages:toast:package already added": "已添加過此套件。",
"user packages:toast:package added": "套件添加成功",
"user packages:toast:package removed": "套件移除成功。",
@ -440,7 +444,7 @@
// Reset your PC is a feature in Windows, so please check your translation is consistent with Windows terminology.
// It's at Setting app > System > Recovery > Reset this PC
"settings:warning:in-local-app-data": "這個位置在資料夾 LocalAppData 中,如果在電腦的設定中執行「重設此電腦」並選擇「保留我的檔案」,此資料夾將被刪除。<br>建議你將資料保存到其他位置。",
"settings:warning:in-app-data": "此路徑位於 AppData 資料夾中,即使在「重設此電腦」時選擇「保留我的檔案」,該資料夾仍會被刪除。<br>建議你將資料保存到其他位置。",
"settings:warning:whitespace": "此位置含有空格。這可能導致 Unity 和其他工具出現問題。請考慮使用其他位置。",
"settings:warning:non-ascii": "此位置含有非 ASCII 字元。這可能導致 Unity 出現問題。請考慮使用其他位置。(不要使用中文或全形字元)",
@ -480,7 +484,7 @@
"settings:backup:format:zip-store": "未壓縮的 zip",
"settings:backup:format:zip-fast": "低壓縮率的 zip",
"settings:backup:format:zip-best": "高壓縮率的 zip最慢",
"settings:backup:exclude vpm packages from backup": "備份裡不含 VPM 套件",
"settings:backup:exclude vpm packages from backup": "備份裡不含 VPM 套件",
"settings:backup:exclude vpm packages from backup description": "這樣可以減少備份的大小,但若套件作者違反建議,從他們的儲存庫中移除了某套件,那在還原備份時你就必須改用其他版本的該套件。",
"settings:packages": "套件",
@ -490,6 +494,10 @@
"settings:show prerelease": "顯示預先發行版套件",
"settings:show prerelease description": "啟用「顯示預先發行版套件」將在套件清單中顯示預先發行版的套件。此外,在解析依賴關係時也將使用預先發行版的套件。",
"settings:dialog:show prerelease packages": "顯示預先發行版套件",
"settings:dialog:show prerelease packages description": "預先發行版套件可能比穩定版套件更容易出現問題。<br>確定要顯示預先發行版套件嗎?",
"settings:dialog:enable show prerelease packages": "顯示",
"settings:gui animation": "啟用 GUI 動畫",
"settings:gui animation description": "啟用 GUI 動畫時,頁面切換將帶有動畫效果。但 Webview 版本過舊時可能無效。",
@ -517,11 +525,17 @@
"check update:toast:no updates": "無可用更新。",
"check update:dialog:title": "ALCOM 更新",
"check update:dialog:new version description": "有可用的新版 ALCOM",
"check update:dialog:new version description": "ALCOM 有新版本可用",
// note: we may remove x86_64 macos later. This is preparation for this removal
"check update:dialog:new version no platform description": "ALCOM 有新版本可用,但已不再支援你的平台。<br>如果你的系統支援其他平台版本,請下載對應版本。",
"check update:dialog:new version not updatable description": "ALCOM 有新版本可用。<br>但你目前使用的安裝方式無法自動更新。<br>請手動安裝新版本。",
"check update:dialog:new version updater disabled base description": "ALCOM 有新版本可用。",
"check update:dialog:new version updater how to upgrade fallback": "Automatic updates are disabled. Please upgrade using your package manager.",
"check update:dialog:current version": "目前版本:",
"check update:dialog:latest version": "最新版本:",
"check update:dialog:changelog": "變更日誌",
"check update:dialog:dismiss": "忽略",
"check update:dialog:open download page": "開啟下載頁面",
"check update:dialog:update": "下載並更新!",
"check update:dialog:downloading...": "下載更新中...",
"check update:dialog:relaunching...": "重新啟動 ALCOM 中...",

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,9 @@
"lint": "tsc && biome lint"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@radix-ui/react-accordion": "^1",
"@radix-ui/react-checkbox": "^1",
"@radix-ui/react-dialog": "^1",
@ -28,9 +31,9 @@
"@radix-ui/react-slot": "^1",
"@radix-ui/react-tooltip": "^1",
"@tanstack/react-query": "^5",
"@tanstack/react-router": "^1.168.25",
"@tanstack/router-devtools": "^1.166.11",
"@tauri-apps/api": "2.10.1",
"@tanstack/react-router": "^1.170.10",
"@tanstack/router-devtools": "^1.167.0",
"@tauri-apps/api": "2.11.0",
"@uidotdev/usehooks": "^2",
"class-variance-authority": "^0.7",
"classnames": "^2",
@ -38,25 +41,25 @@
"cmdk": "^1",
"i18next": "^26",
"lucide-react": "^1",
"react": "19.2.5",
"react-dom": "19.2.5",
"react": "19.2.7",
"react-dom": "19.2.7",
"react-i18next": "^17",
"react-toastify": "^11",
"tailwind-merge": "^3"
},
"devDependencies": {
"@biomejs/biome": "^2",
"@rollup/pluginutils": "^5.3.0",
"@tailwindcss/vite": "^4.2.4",
"@tanstack/router-plugin": "^1.167.28",
"@tauri-apps/cli": "2.10.1",
"@rollup/pluginutils": "^5.4.0",
"@tailwindcss/vite": "^4.3.0",
"@tanstack/router-plugin": "^1.168.13",
"@tauri-apps/cli": "2.11.2",
"@types/node": "^20",
"@types/react": "19.2.14",
"@types/react": "19.2.16",
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"@vitejs/plugin-react": "^6.0.2",
"json5": "^2",
"tw-animate-css": "^1.4.0",
"typescript": "~6.0",
"vite": "^8.0.10"
"vite": "^8.0.16"
}
}

View file

@ -797,7 +797,7 @@ PlayerSettings:
webGLMemoryGeometricGrowthCap: 96
webGLPowerPreference: 2
scriptingDefineSymbols:
Android: VRC_SDK_VRCSDK3;UDON;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
Android: VRC_SDK_VRCSDK3;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
EmbeddedLinux: UNITY_POST_PROCESSING_STACK_V2
GameCoreXboxOne: UNITY_POST_PROCESSING_STACK_V2
Nintendo Switch: UNITY_POST_PROCESSING_STACK_V2
@ -805,11 +805,11 @@ PlayerSettings:
PS5: UNITY_POST_PROCESSING_STACK_V2
QNX: UNITY_POST_PROCESSING_STACK_V2
Stadia: UNITY_POST_PROCESSING_STACK_V2
Standalone: VRC_SDK_VRCSDK3;UDON;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
Standalone: VRC_SDK_VRCSDK3;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
VisionOS: UNITY_POST_PROCESSING_STACK_V2
WebGL: UNITY_POST_PROCESSING_STACK_V2
XboxOne: UNITY_POST_PROCESSING_STACK_V2
iPhone: VRC_SDK_VRCSDK3;UDON;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
iPhone: VRC_SDK_VRCSDK3;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
tvOS: UNITY_POST_PROCESSING_STACK_V2
additionalCompilerArguments: {}
platformArchitecture: {}

View file

@ -791,7 +791,7 @@ PlayerSettings:
webGLMemoryGeometricGrowthCap: 96
webGLPowerPreference: 2
scriptingDefineSymbols:
Android: VRC_SDK_VRCSDK3;UDON;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
Android: VRC_SDK_VRCSDK3;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
EmbeddedLinux: UNITY_POST_PROCESSING_STACK_V2
GameCoreXboxOne: UNITY_POST_PROCESSING_STACK_V2
Nintendo Switch: UNITY_POST_PROCESSING_STACK_V2
@ -799,11 +799,11 @@ PlayerSettings:
PS5: UNITY_POST_PROCESSING_STACK_V2
QNX: UNITY_POST_PROCESSING_STACK_V2
Stadia: UNITY_POST_PROCESSING_STACK_V2
Standalone: VRC_SDK_VRCSDK3;UDON;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
Standalone: VRC_SDK_VRCSDK3;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
VisionOS: UNITY_POST_PROCESSING_STACK_V2
WebGL: UNITY_POST_PROCESSING_STACK_V2
XboxOne: UNITY_POST_PROCESSING_STACK_V2
iPhone: VRC_SDK_VRCSDK3;UDON;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
iPhone: VRC_SDK_VRCSDK3;UDONSHARP;UNITY_POST_PROCESSING_STACK_V2;VRC_ENABLE_PLAYER_PERSISTENCE
tvOS: UNITY_POST_PROCESSING_STACK_V2
additionalCompilerArguments: {}
platformArchitecture: {}

View file

@ -1,4 +1,3 @@
use std::fmt::Display;
use std::io;
use std::path::{Path, PathBuf};
@ -97,6 +96,7 @@ pub(crate) fn handlers() -> impl Fn(Invoke) -> bool + Send + Sync + 'static {
environment::packages::environment_download_repository,
environment::packages::environment_add_repository,
environment::packages::environment_remove_repository,
environment::packages::environment_reorder_repositories,
environment::packages::environment_import_repository_pick,
environment::packages::environment_import_download_repositories,
environment::packages::environment_import_add_repositories,
@ -148,6 +148,7 @@ pub(crate) fn handlers() -> impl Fn(Invoke) -> bool + Send + Sync + 'static {
project::project_set_unity_path,
util::util_open,
util::util_open_url,
util::util_open_url_nocheck,
util::util_get_log_entries,
util::util_get_version,
util::util_check_for_update,
@ -205,6 +206,7 @@ pub(crate) fn export_ts() {
environment::packages::environment_download_repository,
environment::packages::environment_add_repository,
environment::packages::environment_remove_repository,
environment::packages::environment_reorder_repositories,
environment::packages::environment_import_repository_pick,
environment::packages::environment_import_download_repositories,
environment::packages::environment_import_add_repositories,
@ -256,6 +258,7 @@ pub(crate) fn export_ts() {
project::project_set_unity_path,
util::util_open,
util::util_open_url,
util::util_open_url_nocheck,
util::util_get_log_entries,
util::util_get_version,
util::util_check_for_update,
@ -319,11 +322,16 @@ enum HandleableRustError {
}
impl RustError {
fn unrecoverable<T: Display>(value: T) -> Self {
error!("{value}");
Self::Unrecoverable {
message: value.to_string(),
}
fn unrecoverable<T: std::error::Error>(value: T) -> Self {
let message = Self::display_error(&value);
error!("{message}");
Self::Unrecoverable { message }
}
fn unrecoverable_str<T: Into<String>>(value: T) -> Self {
let message = value.into();
error!("{message}");
Self::Unrecoverable { message }
}
fn handleable(message: String, body: HandleableRustError) -> Self {
@ -331,6 +339,25 @@ impl RustError {
Self::Handleable { message, body }
}
// formats the error but with inner error message included
fn display_error<T: std::error::Error>(e: T) -> String {
let mut message = format!("{e}");
let mut cur = e.source();
while let Some(src) = cur {
let src_msg = format!("{src}");
if !message.contains(&src_msg) {
message.push_str(": ");
message.push_str(src_msg.as_str());
}
cur = src.source();
}
message
}
fn handleable_missing_dependencies(
message: String,
dependencies: Vec<(Box<str>, VersionRange)>,
@ -361,13 +388,18 @@ macro_rules! impl_from_error {
impl_from_error!(
io::Error,
String,
async_zip::error::ZipError,
vrc_get_vpm::environment::AddRepositoryErr,
vrc_get_vpm::unity_project::RemovePackageErr,
fs_extra::error::Error,
);
impl From<String> for RustError {
fn from(value: String) -> Self {
RustError::unrecoverable_str(value)
}
}
impl From<crate::compressor::CompressError> for RustError {
fn from(value: crate::compressor::CompressError) -> Self {
match value {
@ -382,7 +414,7 @@ impl From<crate::compressor::CompressError> for RustError {
impl From<crate::updater::Error> for RustError {
fn from(value: crate::updater::Error) -> Self {
log::error!(gui_toast = false; "updater error: {value}");
Self::unrecoverable("failed to load the latest release")
Self::unrecoverable_str("failed to load the latest release")
}
}
@ -411,7 +443,7 @@ impl From<ReinstalPackagesError> for RustError {
ReinstalPackagesError::DependenciesNotFound { dependencies } => {
RustError::handleable_missing_dependencies(message, dependencies)
}
_ => RustError::unrecoverable(message),
_ => RustError::unrecoverable(value),
}
}
}
@ -423,7 +455,7 @@ impl From<AddPackageErr> for RustError {
AddPackageErr::DependenciesNotFound { dependencies } => {
RustError::handleable_missing_dependencies(message, dependencies)
}
_ => RustError::unrecoverable(message),
_ => RustError::unrecoverable(value),
}
}
}
@ -435,7 +467,7 @@ impl From<ResolvePackageErr> for RustError {
ResolvePackageErr::DependenciesNotFound { dependencies } => {
RustError::handleable_missing_dependencies(message, dependencies)
}
_ => RustError::unrecoverable(message),
_ => RustError::unrecoverable(value),
}
}
}
@ -476,6 +508,10 @@ struct TauriBasePackageInfo {
is_yanked: bool,
}
fn safe_url(url: &url::Url) -> bool {
matches!(url.scheme(), "http" | "https")
}
impl TauriBasePackageInfo {
fn new(package: &PackageManifest) -> Self {
Self {
@ -487,8 +523,14 @@ impl TauriBasePackageInfo {
.collect(),
version: package.version().into(),
unity: package.unity().map(|v| (v.major(), v.minor())),
changelog_url: package.changelog_url().map(|v| v.to_string()),
documentation_url: package.documentation_url().map(|v| v.to_string()),
changelog_url: package
.changelog_url()
.take_if(|x| safe_url(x))
.map(|v| v.to_string()),
documentation_url: package
.documentation_url()
.take_if(|x| safe_url(x))
.map(|v| v.to_string()),
vpm_dependencies: package
.vpm_dependencies()
.keys()
@ -550,7 +592,7 @@ impl IntoPathBuf for tauri_plugin_dialog::FilePath {
match self {
Self::Url(url) => url
.to_file_path()
.map_err(|_| RustError::unrecoverable("internal error: bad file url")),
.map_err(|_| RustError::unrecoverable_str("internal error: bad file url")),
Self::Path(p) => Ok(p),
}
}

View file

@ -1,6 +1,6 @@
use crate::commands::async_command::{AsyncCallResult, With, async_command};
use crate::commands::prelude::*;
use futures::future::{join_all, try_join_all};
use futures::future::try_join_all;
use indexmap::IndexMap;
use itertools::Itertools;
use log::info;
@ -18,7 +18,7 @@ use vrc_get_vpm::environment::{
use vrc_get_vpm::io::{DefaultEnvironmentIo, IoTrait};
use vrc_get_vpm::repositories_file::RepositoriesFile;
use vrc_get_vpm::repository::RemoteRepository;
use vrc_get_vpm::{HttpClient, VersionSelector};
use vrc_get_vpm::{HttpClient, UserRepoSetting, VersionSelector};
#[tauri::command]
#[specta::specta]
@ -58,6 +58,7 @@ pub async fn environment_packages(
#[derive(Serialize, specta::Type)]
struct TauriUserRepository {
index: usize,
id: String,
url: Option<String>,
display_name: String,
@ -87,9 +88,11 @@ pub async fn environment_repositories_info(
let user_repositories = settings
.get_user_repos()
.iter()
.map(|x| {
.enumerate()
.map(|(index, x)| {
let id = x.id().or(x.url().map(Url::as_str)).unwrap();
TauriUserRepository {
index,
id: id.to_string(),
url: x.url().map(|x| x.to_string()),
display_name: x.name().unwrap_or(id).to_string(),
@ -345,24 +348,48 @@ pub async fn environment_add_repository(
Ok(TauriAddRepositoryResult::Success)
}
// Verifies that the repo at `index` in the freshly-loaded settings still has
// the `expected_id` the frontend last saw. Guards against silent corruption
// from external writes to settings.json between fetch and mutation.
fn verify_repo_at_index(
repos: &[UserRepoSetting],
index: usize,
expected_id: &str,
) -> Result<(), RustError> {
let Some(repo) = repos.get(index) else {
return Err(RustError::unrecoverable_str(format!(
"Repository index {index} out of range (expected id {expected_id}). \
settings.json was likely modified externally; please refresh."
)));
};
let actual = repo.id().or(repo.url().map(Url::as_str));
if actual != Some(expected_id) {
return Err(RustError::unrecoverable_str(format!(
"Repository at index {index} changed (expected id {expected_id}, found {actual:?}). \
settings.json was likely modified externally; please refresh."
)));
}
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn environment_remove_repository(
settings: State<'_, SettingsState>,
packages: State<'_, PackagesState>,
io: State<'_, DefaultEnvironmentIo>,
id: String,
index: usize,
expected_id: String,
) -> Result<(), RustError> {
let mut settings = settings.load_mut(io.inner()).await?;
let removed = settings.remove_repo(|r| r.id() == Some(id.as_str()));
verify_repo_at_index(settings.get_user_repos(), index, &expected_id)?;
join_all(
removed
.iter()
.map(|x| async { io.remove_file(x.local_path()).await.ok() }),
)
.await;
let removed = settings.remove_repo_at_index(index);
if let Some(repo) = &removed {
io.remove_file(repo.local_path()).await.ok();
}
settings.save().await?;
@ -390,6 +417,35 @@ pub struct TauriRepositoryDescriptor {
pub headers: Headers,
}
#[derive(Deserialize, specta::Type)]
pub struct TauriUserRepositoryRef {
pub index: usize,
pub id: String,
}
#[tauri::command]
#[specta::specta]
pub async fn environment_reorder_repositories(
settings: State<'_, SettingsState>,
packages: State<'_, PackagesState>,
io: State<'_, DefaultEnvironmentIo>,
repos: Vec<TauriUserRepositoryRef>,
) -> Result<(), RustError> {
let mut settings = settings.load_mut(io.inner()).await?;
log::debug!("reorder user repositories: {} entries", repos.len());
let user_repos = settings.get_user_repos();
for r in &repos {
verify_repo_at_index(user_repos, r.index, &r.id)?;
}
let indices: Vec<usize> = repos.into_iter().map(|r| r.index).collect();
settings.reorder_user_repos_by_indices(&indices);
settings.save().await?;
packages.clear_cache();
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn environment_import_repository_pick(
@ -537,7 +593,7 @@ pub async fn environment_export_repositories(
.file()
.set_parent(&window)
.add_filter("Text", &["txt"])
.set_file_name("repositories")
.set_file_name("repositories.txt")
.blocking_save_file()
.map(|x| x.into_path_buf())
.transpose()?

View file

@ -18,6 +18,7 @@ use std::sync::atomic::AtomicUsize;
use std::time::Instant;
use tauri::{AppHandle, Emitter, Manager, State, Window};
use tauri_plugin_dialog::DialogExt;
use tokio::sync::Semaphore;
use vrc_get_vpm::ProjectType;
use vrc_get_vpm::environment::{
InvalidRealProjectInformation, PackageInstaller, RealProjectInformation, Settings, UserProject,
@ -349,7 +350,7 @@ pub async fn environment_remove_project_by_path(
let mut connection = VccDatabaseConnection::connect(io.inner()).await?;
migrate_sanitize_projects(&mut connection, io.inner(), &settings).await?;
let Some(project) = connection.find_project(&project_path).unwrap() else {
return Err(RustError::unrecoverable("project not found"));
return Err(RustError::unrecoverable_str("project not found"));
};
connection.remove_project(&project);
connection.save(io.inner()).await?;
@ -441,7 +442,7 @@ where
let source_path = Path::new(&source_path_str);
let Some(new_path) = create_folder(source_path.into()).await else {
return Err(RustError::unrecoverable(
return Err(RustError::unrecoverable_str(
"failed to create a new folder for migration",
));
};
@ -464,6 +465,7 @@ where
proceed: AtomicUsize,
total_files: usize,
new_path: &'a Path,
semaphore: Semaphore,
ctx: &'a AsyncCommandContext<TauriCopyProjectProgress>,
}
@ -487,15 +489,19 @@ where
let new_entry = self.new_path.join(entry.relative_path());
if entry.is_dir() {
let permission = self.semaphore.acquire().await.unwrap();
if let Err(e) = tokio::fs::create_dir(&new_entry).await
&& e.kind() != io::ErrorKind::AlreadyExists
{
return Err(e);
}
drop(permission);
try_join_all(entry.iter().map(|x| self.process(x))).await?;
} else {
let permission = self.semaphore.acquire().await.unwrap();
tokio::fs::copy(entry.absolute_path(), new_entry).await?;
drop(permission);
self.on_finish(entry);
}
@ -504,10 +510,17 @@ where
}
}
let parallelism = std::thread::available_parallelism()
.map(|x| x.get() * 2)
.unwrap_or(4);
info!("Copying project with parallelism: {parallelism}");
CopyFileContext {
proceed: AtomicUsize::new(0),
total_files,
new_path,
semaphore: Semaphore::new(parallelism),
ctx: &ctx,
}
.process(&file_tree)
@ -545,7 +558,7 @@ pub async fn environment_set_favorite_project(
) -> Result<(), RustError> {
let mut connection = VccDatabaseConnection::connect(io.inner()).await?;
let Some(mut project) = connection.find_project(&project_path).unwrap() else {
return Err(RustError::unrecoverable("project not found"));
return Err(RustError::unrecoverable_str("project not found"));
};
project.set_favorite(favorite);
connection.update_project(&project);
@ -719,10 +732,10 @@ pub async fn environment_create_project(
let templates = templates
.get_versioned(template_version)
.ok_or_else(|| RustError::unrecoverable("Templates info version mismatch (bug)"))?;
.ok_or_else(|| RustError::unrecoverable_str("Templates info version mismatch (bug)"))?;
let unity_version = UnityVersion::parse(&unity_version)
.ok_or_else(|| RustError::unrecoverable("Bad Unity Version (unparsable)"))?;
.ok_or_else(|| RustError::unrecoverable_str("Bad Unity Version (unparsable)"))?;
let base_path = Path::new(&base_path);
let base_path = {

View file

@ -33,7 +33,7 @@ pub async fn environment_export_template(
.and_then(|x| x.iter().find(|x| x.id == id))
.take_if(|x| x.source_path.is_some())
else {
return Err(RustError::unrecoverable(
return Err(RustError::unrecoverable_str(
"Template with such id not found (this is bug)",
));
};
@ -41,7 +41,7 @@ pub async fn environment_export_template(
.dialog()
.file()
.set_parent(&window)
.set_file_name(&template.display_name)
.set_file_name(format!("{}.alcomtemplate", &template.display_name))
.add_filter("ALCOM Project Template", &["alcomtemplate"])
.blocking_save_file()
.map(|x| x.into_path_buf())
@ -98,7 +98,7 @@ pub async fn environment_get_alcom_template(
.and_then(|x| x.iter().find(|x| x.id == id))
.and_then(|x| x.alcom_template.as_ref())
{
None => Err(RustError::unrecoverable(
None => Err(RustError::unrecoverable_str(
"Template with such id not found (this is bug)",
)),
Some(template) => Ok(template.into()),
@ -174,7 +174,7 @@ pub async fn environment_save_template(
id: Some(id.clone().unwrap_or_else(new_user_template_id)),
base,
unity_version: Some(VersionRange::from_str(&unity_range).map_err(|x| {
RustError::unrecoverable(format!("Bad Unity Version Range ({unity_range}): {x}"))
RustError::unrecoverable_str(format!("Bad Unity Version Range ({unity_range}): {x}"))
})?),
vpm_dependencies: vpm_packages
.into_iter()
@ -182,7 +182,7 @@ pub async fn environment_save_template(
Ok::<_, RustError>((
pkg,
VersionRange::from_str(&range).map_err(|x| {
RustError::unrecoverable(format!("Bad Version Range ({range}): {x}"))
RustError::unrecoverable_str(format!("Bad Version Range ({range}): {x}"))
})?,
))
})
@ -191,7 +191,7 @@ pub async fn environment_save_template(
};
let template = serialize_alcom_template(template)
.map_err(|x| RustError::unrecoverable(format!("Failed to serialize template: {x}")))?;
.map_err(|x| RustError::unrecoverable_str(format!("Failed to serialize template: {x}")))?;
if let Some(id) = id {
// There is id; overwrite existing one
@ -201,7 +201,7 @@ pub async fn environment_save_template(
.and_then(|x| x.iter().find(|x| x.id == id))
.and_then(|x| x.source_path.as_ref())
else {
return Err(RustError::unrecoverable(
return Err(RustError::unrecoverable_str(
"Template with such id not found (this is bug)",
));
};
@ -297,7 +297,7 @@ pub async fn environment_remove_template(
.take_if(|x| x.alcom_template.is_some())
.take_if(|x| x.source_path.is_some())
{
None => Err(RustError::unrecoverable(
None => Err(RustError::unrecoverable_str(
"Template with such id not found (this is bug)",
)),
Some(template) => {

View file

@ -185,7 +185,7 @@ pub async fn project_install_packages(
) -> Result<TauriPendingProjectChanges, RustError> {
let settings = settings.load(io.inner()).await?;
let Some(packages) = packages.get() else {
return Err(RustError::unrecoverable(
return Err(RustError::unrecoverable_str(
"Internal Error: environment version mismatch",
));
};
@ -194,7 +194,7 @@ pub async fn project_install_packages(
.map(|(id, v)| Some((id, Version::from_str(&v).ok()?)))
.collect::<Option<Vec<_>>>()
else {
return Err(RustError::unrecoverable("bad version file"));
return Err(RustError::unrecoverable_str("bad version file"));
};
changes!(packages, changes, |collection, packages| {
@ -210,7 +210,7 @@ pub async fn project_install_packages(
})
.collect::<Option<Vec<_>>>()
else {
return Err(RustError::unrecoverable("some packages not found"));
return Err(RustError::unrecoverable_str("some packages not found"));
};
let unity_project = load_project(project_path).await?;
@ -301,7 +301,7 @@ pub async fn project_apply_pending_changes(
changes_version: u32,
) -> Result<(), RustError> {
let Some(mut changes) = changes.get_versioned(changes_version) else {
return Err(RustError::unrecoverable("changes version mismatch"));
return Err(RustError::unrecoverable_str("changes version mismatch"));
};
let changes = changes.take_changes();

View file

@ -3,6 +3,7 @@ use std::path::Path;
use crate::commands::async_command::{AsyncCallResult, With, async_command};
use crate::commands::environment::settings::TauriPickProjectDefaultPathResult;
use crate::commands::prelude::*;
use crate::commands::safe_url;
use crate::logging::LogEntry;
use crate::os::open_that;
use crate::updater::{self, Update};
@ -26,7 +27,7 @@ pub async fn util_open(path: String, if_not_exists: OpenOptions) -> Result<(), R
if !path.exists() {
match if_not_exists {
OpenOptions::ErrorIfNotExists => {
return Err(RustError::unrecoverable("Path does not exist"));
return Err(RustError::unrecoverable_str("Path does not exist"));
}
OpenOptions::CreateFolderIfNotExists => {
super::create_dir_all_with_err(&path).await?;
@ -42,9 +43,19 @@ pub async fn util_open(path: String, if_not_exists: OpenOptions) -> Result<(), R
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn util_open_url_nocheck(url: String) -> Result<(), RustError> {
open_that(url)?;
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn util_open_url(url: String) -> Result<(), RustError> {
if !Url::parse(&url).is_ok_and(|x| safe_url(&x)) {
return Err(RustError::unrecoverable_str("Bad URL or bad scheme"));
}
open_that(url)?;
Ok(())
}
@ -65,7 +76,11 @@ pub async fn check_for_update(
app_handle: AppHandle,
stable: bool,
) -> updater::Result<Option<Update>> {
let endpoint = if stable {
let endpoint = if let Ok(env) =
std::env::var("___ALCOM_UPDATER_URL_OVERRIDE_DEBUG_ONLY_FEATURE_YOU_SHOULD_NOT_USE_THIS___")
{
Url::parse(&env).unwrap()
} else if stable {
Url::parse("https://vrc-get.anatawa12.com/api/gui/tauri-updater.json").unwrap()
} else {
Url::parse("https://vrc-get.anatawa12.com/api/gui/tauri-updater-beta.json").unwrap()
@ -133,11 +148,11 @@ pub async fn util_install_and_upgrade(
) -> Result<AsyncCallResult<InstallUpgradeProgress, ()>, RustError> {
async_command(channel, window, async move {
let Some(response) = updater_state.take() else {
return Err(RustError::unrecoverable("No update response found"));
return Err(RustError::unrecoverable_str("No update response found"));
};
if response.version() != version {
return Err(RustError::unrecoverable("Update data version mismatch"));
return Err(RustError::unrecoverable_str("Update data version mismatch"));
}
With::<InstallUpgradeProgress>::continue_async(move |ctx| async move {

View file

@ -246,7 +246,7 @@ pub(crate) struct LogEntry {
level: LogLevel,
target: String,
message: String,
gui_toast: bool,
gui_toast: Option<bool>,
}
fn to_rfc3339_micros<S>(
@ -265,8 +265,7 @@ impl LogEntry {
let gui_toast = record
.key_values()
.get("gui_toast".into())
.and_then(|x| x.to_bool())
.unwrap_or(true);
.and_then(|x| x.to_bool());
LogEntry {
time: chrono::Local::now(),
level: record.level().into(),

View file

@ -115,6 +115,14 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
// ---------------------------------------------------------------------------
fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Result<bool> {
if std::env::var(
"___ALCOM_UPDATER_DISABLE_SIGNATURE_VERIFICATION_DEBUG_ONLY_FEATURE_DO_NOT_USE_THIS_OR_YOU_WILL_BE_HACKED___",
)
.as_deref()
== Ok("YES_I_WANT_TO_BE_HACKED")
{
return Ok(true);
}
let pub_key_decoded = base64_to_string(pub_key)?;
let public_key =
PublicKey::decode(&pub_key_decoded).map_err(|e| Error::Signature(e.to_string()))?;
@ -262,6 +270,18 @@ pub async fn check_for_update<R: Runtime>(
let mut headers = HeaderMap::new();
headers.insert(header::ACCEPT, HeaderValue::from_static("application/json"));
headers.insert(
"X-Alcom-Version",
HeaderValue::from_static(env!("CARGO_PKG_VERSION")),
);
headers.insert(
"X-Alcom-OS",
HeaderValue::from_static(updater_os().unwrap_or("unknown")),
);
headers.insert(
"X-Alcom-Arch",
HeaderValue::from_static(updater_arch().unwrap_or("unknown")),
);
let client = app.state::<reqwest::Client>();
@ -793,7 +813,8 @@ mod windows {
let params = build_updater_args(&self.platform.args, self.current_install);
tempfile.disable_cleanup(true);
start_installer(op, file, params);
drop(tempfile);
start_installer(op, file, params)?;
// For windows install, we need to quit app immediately.
std::process::exit(0);
@ -900,14 +921,14 @@ mod windows {
// os specific call
#[cfg(windows)]
fn start_installer(op: Vec<u16>, file: Vec<u16>, params: Vec<u16>) {
fn start_installer(op: Vec<u16>, file: Vec<u16>, params: Vec<u16>) -> Result<()> {
use ::windows::Win32::UI::Shell::ShellExecuteW;
use ::windows::Win32::UI::WindowsAndMessaging::SW_SHOW;
use ::windows::core::PCWSTR;
unsafe {
// SAFETY: all pointers remain valid for the duration of the call, since owned vec is passed
ShellExecuteW(
let response = ShellExecuteW(
None,
PCWSTR(op.as_ptr()),
PCWSTR(file.as_ptr()),
@ -915,11 +936,19 @@ mod windows {
PCWSTR(std::ptr::null()),
SW_SHOW,
);
let response = response.0 as u32;
if response > 32 {
Ok(())
} else {
// Map the error code (<= 32) to an IO Error
Err(std::io::Error::from_raw_os_error(response as i32).into())
}
}
}
#[cfg(not(windows))]
fn start_installer(_op: Vec<u16>, _file: Vec<u16>, _params: Vec<u16>) {
fn start_installer(_op: Vec<u16>, _file: Vec<u16>, _params: Vec<u16>) -> Result<()> {
unreachable!("install_windows_impl called on a non-Windows platform")
}
}

View file

@ -55,6 +55,9 @@ export default defineConfig({
"@/build": path.join(__dirname, "./build"),
},
},
oxc: {
target: "es2025",
},
build: {
outDir: "out",
chunkSizeWarningLimit: Number.POSITIVE_INFINITY,

View file

@ -18,6 +18,9 @@ windows-sys = {
"Win32_System_Threading",
"Win32_Security",
"Win32_System_IO",
"Win32_UI_Shell",
"Win32_System_Console",
],
}
[build-dependencies]
embed-manifest = "1.5.0"

View file

@ -0,0 +1,12 @@
use embed_manifest::manifest::ExecutionLevel;
use embed_manifest::{embed_manifest, new_manifest};
fn main() {
if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
embed_manifest(
new_manifest("alcom-updater").requested_execution_level(ExecutionLevel::AsInvoker),
)
.expect("unable to embed manifest file");
}
println!("cargo:rerun-if-changed=build.rs");
}

View file

@ -18,6 +18,7 @@ use windows_sys::Win32::System::Console::{GetStdHandle, STD_ERROR_HANDLE, WriteC
use windows_sys::Win32::System::Environment::*;
use windows_sys::Win32::System::Memory::*;
use windows_sys::Win32::System::Threading::*;
use windows_sys::Win32::UI::Shell::PathRenameExtensionW;
use windows_sys::Win32::UI::WindowsAndMessaging::SW_HIDE;
static INSTALLER: &[u8] = include_bytes!(env!("INSTALLER_EXE"));
@ -98,6 +99,9 @@ unsafe fn create_temp_file() -> Option<StackPath> {
return None;
}
// replace extension
PathRenameExtensionW(temp.as_mut_ptr(), w!(".exe"));
Some(name)
}
}

View file

@ -2,7 +2,7 @@
name = "vrc-get-vpm"
# discreate versioning since this library will not have stable versions
version = "0.0.16-beta.0"
version = "0.0.16-rc.0"
edition.workspace = true
license.workspace = true

View file

@ -7,7 +7,7 @@ use crate::{HttpClient, PackageInfo, PackageManifest, io};
use futures::prelude::*;
use hex::FromHex;
use indexmap::IndexMap;
use log::{debug, error};
use log::debug;
use std::io::SeekFrom;
use std::path::{Path, PathBuf};
use std::pin::pin;
@ -136,12 +136,16 @@ async fn get_package<T: HttpClient>(
.and_then(|x| <[u8; 256 / 8] as FromHex>::from_hex(x).ok())
&& repo_hash != zip_hash
{
error!(
"Package hash mismatched! This will be hard error in the future!: {} v{}",
package.name(),
package.version()
);
//return None;
drop(zip_file);
io.remove_file(&zip_path).await.ok();
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Downloaded file for {}@{} has an unexpected SHA256 hash. This may be the repository owner's fault, or the repository or package may be compromised.",
package.name(),
package.version()
),
));
}
Ok(zip_file)

View file

@ -23,7 +23,18 @@ pub struct Settings {
impl Settings {
pub async fn load(io: &DefaultEnvironmentIo) -> io::Result<Self> {
let settings = VpmSettings::load(io).await?;
let settings = if let Some(settings) = VpmSettings::load(io).await? {
settings
} else if let Some(settings) = VpmSettings::load_alt(io).await? {
log::warn!(
gui_toast = true;
"Recovered settings from a vrc-get backup because the VCC configuration file was missing or corrupted. Some changes made in VCC may have been lost."
);
settings
} else {
VpmSettings::default()
};
let vrc_get_settings = VrcGetSettings::load(io).await?;
Ok(Self {
@ -258,6 +269,14 @@ impl Settings {
self.vpm.retain_user_repos(|x| !condition(x))
}
pub fn remove_repo_at_index(&mut self, index: usize) -> Option<UserRepoSetting> {
self.vpm.remove_user_repo_at_index(index)
}
pub fn reorder_user_repos_by_indices(&mut self, indices: &[usize]) {
self.vpm.reorder_user_repos_by_indices(indices);
}
// auto configurations
/// Removes id-duplicated repositories

View file

@ -2,14 +2,14 @@ use crate::UserRepoSetting;
use crate::environment::PackageCollection;
use crate::io;
use crate::io::DefaultEnvironmentIo;
use crate::utils::{load_json_or_default, save_json};
use crate::utils::{save_json, try_load_json};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::path::{Path, PathBuf};
type JsonObject = Map<String, Value>;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
struct AsJson {
#[serde(default)]
@ -78,45 +78,39 @@ struct AsJson {
rest: JsonObject,
}
impl Default for AsJson {
fn default() -> Self {
Self {
path_to_unity_exe: Default::default(),
path_to_unity_hub: Default::default(),
user_projects: Some(vec![]),
unity_editors: Default::default(),
preferred_unity_editors: Default::default(),
default_project_path: Default::default(),
last_ui_state: Default::default(),
skip_unity_auto_find: Default::default(),
user_package_folders: Default::default(),
window_size_data: Default::default(),
skip_requirements: Default::default(),
last_news_update: Default::default(),
allow_pii: Default::default(),
project_backup_path: Default::default(),
show_prerelease_packages: Default::default(),
track_community_repos: Default::default(),
selected_providers: Default::default(),
last_selected_project: Default::default(),
user_repos: Default::default(),
rest: Default::default(),
}
}
}
#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone)]
pub(crate) struct VpmSettings {
parsed: AsJson,
}
const JSON_PATH: &str = "settings.json";
const ALT_JSON_PATH: &str = "vrc-get/vcc-settings-backup.json";
impl VpmSettings {
pub async fn load(io: &DefaultEnvironmentIo) -> io::Result<Self> {
let parsed: AsJson = load_json_or_default(io, JSON_PATH.as_ref()).await?;
pub async fn load(io: &DefaultEnvironmentIo) -> io::Result<Option<Self>> {
Self::load_inner(io, JSON_PATH).await
}
Ok(Self { parsed })
pub async fn load_alt(io: &DefaultEnvironmentIo) -> io::Result<Option<Self>> {
let mut settings = Self::load_inner(io, ALT_JSON_PATH).await?;
// We use data from vcc.litedb for the source of the projecs list since it's much reliable source.
if let Some(ref mut settings) = settings {
settings.parsed.user_projects = None;
}
Ok(settings)
}
async fn load_inner(io: &DefaultEnvironmentIo, path: &str) -> io::Result<Option<Self>> {
let Some(parsed): Option<AsJson> = try_load_json(io, path.as_ref()).await? else {
log::debug!("VpmSettings Configuration file not found at {path}");
return Ok(None);
};
log::debug!("Parsed VpmSettings at {path}");
Ok(Some(Self { parsed }))
}
pub(crate) fn user_repos(&self) -> &[UserRepoSetting] {
@ -161,6 +155,32 @@ impl VpmSettings {
.collect::<Vec<_>>()
}
pub fn remove_user_repo_at_index(&mut self, index: usize) -> Option<UserRepoSetting> {
let repos = &mut self.parsed.user_repos;
if index < repos.len() {
Some(repos.remove(index))
} else {
None
}
}
pub fn reorder_user_repos_by_indices(&mut self, indices: &[usize]) {
let mut pool: Vec<Option<UserRepoSetting>> = std::mem::take(&mut self.parsed.user_repos)
.into_iter()
.map(Some)
.collect();
let mut result = Vec::with_capacity(pool.len());
for &idx in indices {
if let Some(slot) = pool.get_mut(idx)
&& let Some(repo) = slot.take()
{
result.push(repo);
}
}
result.extend(pool.into_iter().flatten());
self.parsed.user_repos = result;
}
pub(crate) fn add_user_repo(&mut self, repo: UserRepoSetting) {
self.parsed.user_repos.push(repo);
}
@ -198,7 +218,8 @@ impl VpmSettings {
}
pub async fn save(&self, io: &DefaultEnvironmentIo) -> io::Result<()> {
save_json(io, JSON_PATH.as_ref(), &self.parsed).await
save_json(io, JSON_PATH.as_ref(), &self.parsed).await?;
save_json(io, ALT_JSON_PATH.as_ref(), &self.parsed).await
}
}

View file

@ -42,6 +42,7 @@ macro_rules! package_json_struct {
$(#[$meta:meta])*
$vis:vis struct $name: ident {
$optional_vis:vis optional$(: #[$optional: meta])?;
optional_url$(: #[$optional_url: meta])?;
$required_vis:vis required$(: #[$required: meta])?;
}
$(#[$vr_get_meta:meta])*
@ -84,9 +85,9 @@ macro_rules! package_json_struct {
$(#[$optional])?
$optional_vis headers: indexmap::IndexMap<Box<str>, Box<str>>,
$(#[$optional])?
$(#[$optional_url])?
$optional_vis changelog_url: Option<Url>,
$(#[$optional])?
$(#[$optional_url])?
$optional_vis documentation_url: Option<Url>,
$(#[$optional])?
@ -119,10 +120,22 @@ where
<Option<T>>::deserialize(de).map(|x| x.unwrap_or_default())
}
fn none_if_none_or_empty<'de, D>(de: D) -> Result<Option<Url>, D::Error>
where
D: Deserializer<'de>,
{
let str = <Option<String>>::deserialize(de)?;
let Some(url) = str.filter(|s| !s.trim().is_empty()) else {
return Ok(None);
};
Ok(Some(Url::parse(&url).map_err(serde::de::Error::custom)?))
}
package_json_struct! {
#[derive(Debug, Clone)]
pub struct PackageManifest {
optional: #[serde(default, deserialize_with = "default_if_none")];
optional_url: #[serde(default, deserialize_with = "none_if_none_or_empty")];
required;
}
#[derive(Debug, Clone, Default)]
@ -258,6 +271,7 @@ impl<'de> Deserialize<'de> for LooseManifest {
package_json_struct! {
pub(super) struct LooseManifest {
pub(super) optional: #[serde(default, deserialize_with = "default_if_err")];
optional_url: #[serde(default, deserialize_with = "default_if_err")];
pub(super) required;
}
#[derive(Default)]
@ -334,3 +348,31 @@ fn deserialize_null_on_dependencies() {
//assert!(package_json.dependencies().is_empty());
assert!(package_json.vpm_dependencies().is_empty());
}
#[test]
fn deserialize_empty_documentation() {
let json = r##"{
"name": "net.yarukizero.vrchat.shizuku",
"displayName": "Shizuku",
"version": "0.0.0",
"unity": "2022.3",
"description": "スクリプトでいい感じに定義したい",
"vpmDependencies": {
"nadena.dev.modular-avatar": ">=1.9.10"
},
"changelogUrl": " ",
"author": {
"name": "azumyar",
"url": "https://github.com/azumyar"
},
"documentationUrl": "",
"license": "MIT",
"zipSHA256": "22a143ed75c429a471ffd784102d2fb577c56b010b49439b5930cbb2df820f8b",
"url": "https://github.com/azumyar/vrchat-shizuku/releases/download/0.0.0/net.yarukizero.vrchat.shizuku-0.0.0.zip"
}"##;
let package_json: PackageManifest = serde_json::from_str(json).unwrap();
assert_eq!(package_json.name(), "net.yarukizero.vrchat.shizuku");
assert_eq!(package_json.version(), &Version::new(0, 0, 0));
assert_eq!(package_json.documentation_url(), None);
assert_eq!(package_json.changelog_url(), None);
}

View file

@ -6,12 +6,13 @@ use futures::FutureExt;
use futures::future::{join_all, try_join3};
use serde::Deserialize;
use serde::de::DeserializeOwned;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::result;
type Result<T> = result::Result<T, std::io::Error>;
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ChipArchitecture {
X86_64,
ARM64,
@ -47,7 +48,27 @@ pub async fn load_unity_by_loading_unity_hub_files() -> Result<Vec<UnityEditorIn
)
.await?;
Ok(a.into_iter().chain(b).chain(c).collect())
// Disk-scanned entries take priority over editors-v2.json entries for the same
// version+architecture, matching Unity Hub's merge behavior.
let disk_scanned: Vec<UnityEditorInHub> = a.into_iter().chain(b).collect();
let disk_scanned_keys: HashSet<(UnityVersion, ChipArchitecture)> = disk_scanned
.iter()
.map(|e| (e.version, arch_for_dedup(e.architecture)))
.collect();
let located_only: Vec<UnityEditorInHub> = c
.into_iter()
.filter(|located| {
!disk_scanned_keys.contains(&(located.version, arch_for_dedup(located.architecture)))
})
.collect();
Ok(disk_scanned.into_iter().chain(located_only).collect())
}
// Unity Hub defaults null/unknown architecture to X86_64 when building unique identifiers.
// We match that behavior here so deduplication between disk-scanned and located editors is correct.
fn arch_for_dedup(arch: Option<ChipArchitecture>) -> ChipArchitecture {
arch.unwrap_or(ChipArchitecture::X86_64)
}
async fn get_custom_install_location(local_settings: &LocalSettings) -> Option<PathBuf> {
@ -171,7 +192,9 @@ async fn load_located_editors(local_settings: &LocalSettings) -> Vec<UnityEditor
#[serde(with = "either::serde_untagged")]
location: Either<String, Vec<String>>,
version: String,
architecture: String,
// architecture may be null or absent in older editors-v2.json entries
#[serde(default)]
architecture: Option<String>,
}
#[derive(Deserialize)]
struct EditorsV2 {
@ -192,9 +215,9 @@ async fn load_located_editors(local_settings: &LocalSettings) -> Vec<UnityEditor
let Some(version) = UnityVersion::parse(&editor.version) else {
continue;
};
let architecture = match editor.architecture.as_str() {
"x86_64" => Some(ChipArchitecture::X86_64),
"arm64" => Some(ChipArchitecture::ARM64),
let architecture = match editor.architecture.as_deref() {
Some("x86_64") | None => Some(ChipArchitecture::X86_64),
Some("arm64") => Some(ChipArchitecture::ARM64),
_ => None,
};
match editor.location {

View file

@ -108,12 +108,12 @@ mod linux {
.take_if(|x| !x.is_empty())
.map(PathBuf::from)
{
config_home.joined("UnityHub")
config_home.joined("unityhub")
} else {
std::env::var_os("HOME")
.map(PathBuf::from)
.expect("HOME environment variable is not set")
.joined(".config/UnityHub")
.joined(".config/unityhub")
}
}

View file

@ -4,6 +4,7 @@ use crate::io::{DefaultProjectIo, IoTrait};
use crate::utils::MapResultExt;
use async_zip::base::read::seek::ZipFileReader;
use futures::prelude::*;
use log::trace;
use std::path::{Component, Path};
pub(crate) async fn extract_zip(
@ -23,6 +24,8 @@ pub(crate) async fn extract_zip(
"path in zip file is not utf8".to_string(),
));
};
let filename = fix_path_separator(filename);
let filename = filename.as_ref();
if !is_complete_relative(filename.as_ref()) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
@ -46,6 +49,20 @@ pub(crate) async fn extract_zip(
Ok(())
}
fn fix_path_separator(p: &str) -> std::borrow::Cow<'_, str> {
if cfg!(windows) {
// On windows Path struct accepts both '/' and '\' as separator so we don't need to convert separator
std::borrow::Cow::Borrowed(p)
} else if !p.contains('\\') {
// If the path does not contain '\\' we don't need to replace path separators
std::borrow::Cow::Borrowed(p)
} else {
// The path contains '\\', we should replace with '/'
trace!("fixing '\\' with '/' in path {p:?}");
std::borrow::Cow::Owned(p.replace('\\', "/"))
}
}
fn is_complete_relative(path: &Path) -> bool {
for x in path.components() {
match x {

View file

@ -7,7 +7,7 @@ use crate::version::Version;
use serde::de::Unexpected;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct UnityVersion {
// major version such as 2019, 2022, and 6
// note: 5 < 2017 < 2023 < 6 < 7 ...
@ -263,6 +263,20 @@ impl PartialEq for ReleaseType {
}
}
impl std::hash::Hash for ReleaseType {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
// Normal and China compare equal, so they must hash identically
let discriminant: u8 = match self {
Self::Alpha => 0,
Self::Beta => 1,
Self::Normal | Self::China => 2,
Self::Patch => 3,
Self::Experimental => 4,
};
discriminant.hash(state);
}
}
impl PartialOrd for ReleaseType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Ord::cmp(self, other))

View file

@ -1,6 +1,6 @@
[package]
name = "vrc-get"
version = "1.9.2-beta.0"
version = "1.9.2-rc.0"
edition.workspace = true
license.workspace = true
authors.workspace = true
@ -30,7 +30,7 @@ serde_json = { version = "1", features = ["preserve_order"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs"] }
[dependencies.vrc-get-vpm]
version = "0.0.16-beta.0"
version = "0.0.16-rc.0"
path = "../vrc-get-vpm"
default-features = false

View file

@ -13,17 +13,16 @@ anyhow = { version = "1.0.102", features = ["backtrace"] }
cargo_metadata = "0.23.1"
clap = { version = "4.6.0", features = ["derive", "env"] }
itertools = "0.14.0"
object = { version = "0.39.0", features = ["read_core", "elf", "macho", "pe"], default-features = false }
object = { version = "0.39.1", features = ["read_core", "elf", "macho", "pe"], default-features = false }
chrono = { version = "0.4.44", features = ["serde", "now"], default-features = false }
serde = { version = "1.0.228", features = ["derive"] }
indexmap = { version = "2.13.0", features = ["serde"] }
serde_json = "1.0.149"
serde_json = "1.0.150"
ureq = { version = "3.3.0", features = ["gzip", "native-tls"], default-features = false }
flate2 = "1.1.1"
tar = { version = "0.4.43", features = [], default-features = false }
plist = "1.8.0"
rpm = { version = "0.22.0", default-features = false, features = ["gzip-compression"] }
ar = "0.9.0"
tar = { version = "0.4.46", features = [], default-features = false }
plist = "1.9.0"
fs_extra = "1.3.0"
base64 = "0.22.1"
minisign = "0.9.1"
zip = { version = "8.6.0", default-features = false, features = ["deflate-flate2"] }

View file

@ -34,6 +34,7 @@ struct UpdaterJson<'a> {
struct Platform {
signature: String,
url: String,
args: Vec<String>,
}
pub fn create_alcom_updater_json(assets_dir: &Path, version: &str, out_path: &Path) -> Result<()> {
@ -65,9 +66,27 @@ pub fn create_alcom_updater_json(assets_dir: &Path, version: &str, out_path: &Pa
.with_context(|| sig_name.clone())?;
let url = format!("{base_url}/{file_name}");
platforms.insert(platform.to_string(), Platform { signature, url });
platforms.insert(
platform.to_string(),
Platform {
signature,
url,
args: vec![],
},
);
}
platforms["windows-x86_64"].args = [
"/SP-",
"/SILENT",
"/NOICONS",
"!peruser:/CURRENTUSER",
"!machine:/ALLUSERS",
]
.iter()
.map(|x| x.to_string())
.collect();
let is_beta = version.contains('-');
let notes = if is_beta {
// https://github.com/vrc-get/vrc-get/blob/master/CHANGELOG-gui.md#unreleased

View file

@ -170,6 +170,10 @@ fn build_cargo(
}
}
if config.devtools {
features.push("devtools");
}
cmd.arg("--features").arg(features.iter().join(","));
if let Some(target) = target_triple {

View file

@ -1,14 +1,12 @@
use crate::utils::{self, build_dir, build_target, target_os};
use anyhow::{Context, Result};
use anyhow::{Context, Result, bail};
use std::fs;
use std::path::{Path, PathBuf};
mod app;
mod appimage;
mod deb;
mod dmg;
mod linux;
mod rpm;
mod setup_exe;
/// Individual bundle artifact that can be produced.
@ -17,15 +15,21 @@ mod setup_exe;
/// If `--bundles` is not specified, all artifacts for the target platform are produced.
///
/// **macOS** artifacts:
/// - `app` `ALCOM.app` application bundle
/// - `dmg` `ALCOM_<version>_<arch>.dmg` disk image
/// - `app-updater` `ALCOM.app.tar.gz` updater payload
/// - `app` - `ALCOM.app` application bundle
/// - `dmg` - `ALCOM_<version>_<arch>.dmg` disk image
/// - `app-updater` - `ALCOM.app.tar.gz` updater payload
///
/// **Linux** artifacts:
/// - `app-image` — `ALCOM_<version>_<arch>.AppImage`
/// - `app-image-updater` — `ALCOM_<version>_<arch>.AppImage.tar.gz` updater payload
/// - `deb` — `ALCOM_<version>_<arch>.deb` Debian package
/// - `rpm` — `ALCOM-<version>-1.<arch>.rpm` RPM package
/// - `app-image` - `ALCOM_<version>_<arch>.AppImage`
/// - `app-image-updater` - `ALCOM_<version>_<arch>.AppImage.tar.gz` updater payload
/// - `deb` - `ALCOM_<version>_<arch>.deb` Debian package
/// - `rpm` - `ALCOM-<version>-1.<arch>.rpm` RPM package
/// - `buildroot` - The package manager independent buildroot for external package managers.
///
/// **Windows** artifacts:
/// - `setup-exe` - `-setup.exe` for first-time installation
/// - `setup-exe-zip` - `-setup.exe.zip` to workaround warning from browsers
/// - `exe-updater` - `-updater.exe` for the updater. This includes
#[derive(clap::ValueEnum, Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum BundleKind {
// --- macOS ---
@ -46,14 +50,18 @@ pub(crate) enum BundleKind {
///
/// Unlike dmg depends on app, deb/rpm doesn't depend on this bundle.
Buildroot,
/// Debian package
Deb,
/// RPM package
Rpm,
/// Windows setup.exe
SetupExe,
/// Windows setup.exe in zip (requires setup.exe to already exist in bundle dir)
SetupExeZip,
/// Windows setup.exe for updater
ExeUpdater,
// deleted
#[value(hide = true)]
Deb,
#[value(hide = true)]
Rpm,
}
/// Bundles the ALCOM application for the target platform.
@ -87,7 +95,7 @@ pub(super) struct Command {
/// Specific bundle artifacts to produce (comma-separated or repeated).
///
/// When not specified, all artifacts for the target platform are produced.
/// Use this to split the bundling process e.g. produce only `app` first,
/// Use this to split the bundling process - e.g. produce only `app` first,
/// then sign it, then produce `dmg` and `app-updater`.
#[arg(long, value_delimiter = ',')]
bundles: Vec<BundleKind>,
@ -105,6 +113,12 @@ impl crate::Command for Command {
let bundles = self.bundles.as_slice();
if bundles.contains(&BundleKind::Deb) || bundles.contains(&BundleKind::Rpm) {
bail!(
"--bundles deb and --bundles rpm are removed. Please use native packaging configuration at vrc-get-gui/bundles"
)
}
if bundles.is_empty() {
println!("Note: no bundles are specified");
}
@ -133,18 +147,14 @@ impl crate::Command for Command {
linux::create_install_build_root(&ctx, self.buildroot.as_deref())?;
}
if bundles.contains(&BundleKind::Deb) {
deb::create_deb(&ctx)?;
}
if bundles.contains(&BundleKind::Rpm) {
rpm::create_rpm(&ctx)?;
}
if bundles.contains(&BundleKind::SetupExe) {
setup_exe::create_setup_exe(&ctx)?;
}
if bundles.contains(&BundleKind::SetupExeZip) {
setup_exe::create_setup_exe_zip(&ctx)?;
}
if bundles.contains(&BundleKind::ExeUpdater) {
setup_exe::create_updater_exe(&ctx)?;
}
@ -202,14 +212,6 @@ impl<'a> BundleContext<'a> {
self.version.as_str()
}
pub fn short_description(&self) -> &str {
"ALCOM - Alternative Creator Companion"
}
pub fn long_description(&self) -> &str {
"ALCOM is a fast and open-source alternative VCC (VRChat Creator Companion) written in rust and tauri."
}
/// Binary name without extension (e.g. `ALCOM`).
pub fn binary_name(&self) -> &str {
"ALCOM"

View file

@ -1,108 +0,0 @@
use crate::bundle_alcom::BundleContext;
use crate::bundle_alcom::linux::*;
use crate::utils::tar::TarBuilderExt;
use crate::utils::{CountingIo, tar, target_arch};
use anyhow::{Context, Result, bail};
use flate2::Compression;
use flate2::write::GzEncoder;
use std::io::Write;
use std::{fs, io};
fn deb_arch(triple: &str) -> Result<&str> {
match target_arch(triple) {
"aarch64" => Ok("arm64"),
"x86_64" => Ok("amd64"),
_ => {
bail!(
"unsupported architecture in target triple for deb: {}",
triple
)
}
}
}
pub fn create_deb(ctx: &BundleContext<'_>) -> Result<()> {
let arch = deb_arch(ctx.target_tuple)?;
let pkg_name = format!("alcom_{}-1_{arch}", ctx.version());
let (estimated_size, data_tar_gz) = {
let gz = GzEncoder::new(Vec::new(), Compression::default());
let mut tar = tar::Builder::new(CountingIo::new(gz));
create_install_build_root_impl(ctx, &mut tar).context("creating data.tar.gz")?;
let finished_gz_count = tar.into_inner()?;
let estimated_size = finished_gz_count.count();
let finished_gz = finished_gz_count.into_inner();
let data_tar_gz = finished_gz.finish().context("finishing data.tar.gz")?;
(estimated_size, data_tar_gz)
};
let library = detect_library_versions(&ctx.binary_path())?;
// Build control.tar.gz
let control_tar_gz = {
let mut control_tar_gz = Vec::new();
let gz = GzEncoder::new(&mut control_tar_gz, Compression::best());
let mut tar = tar::Builder::new(gz);
let control = {
let template_path = ctx.gui_dir.join("bundle/deb-control");
fs::read_to_string(&template_path)
.with_context(|| format!("reading {}", template_path.display()))?
.replace("{{version}}", ctx.version())
.replace("{{arch}}", arch)
.replace("{{estimated_size}}", &(estimated_size / 1024).to_string())
.replace("{{libc_version}}", &library.libc)
.replace("{{libgcc_version}}", &library.libgcc)
};
tar.append_file_data(0o644, "control", io::Cursor::new(control.as_bytes()))
.context("appending control file")?;
let gz = tar.into_inner().context("finishing control tar")?;
gz.finish().context("finishing control gzip")?;
control_tar_gz
};
// Assemble .deb as an ar archive.
let deb_dir = ctx.bundle_dir.join("deb");
fs::create_dir_all(&deb_dir)?;
let deb_name = format!("{pkg_name}.deb");
let deb_out = deb_dir.join(&deb_name);
{
let deb_file = fs::File::create(&deb_out)
.with_context(|| format!("creating {}", deb_out.display()))?;
let mut builder = ar::Builder::new(deb_file);
// debian-binary
let debian_binary = b"2.0\n";
let mut header = ar::Header::new(b"debian-binary".to_vec(), debian_binary.len() as u64);
header.set_mode(0o100644);
builder
.append(&header, &mut debian_binary.as_slice())
.context("appending debian-binary")?;
// control.tar.gz
let mut header = ar::Header::new(b"control.tar.gz".to_vec(), control_tar_gz.len() as u64);
header.set_mode(0o100644);
builder
.append(&header, &mut control_tar_gz.as_slice())
.context("appending control.tar.gz")?;
// data.tar.gz
let mut header = ar::Header::new(b"data.tar.gz".to_vec(), data_tar_gz.len() as u64);
header.set_mode(0o100644);
builder
.append(&header, &mut data_tar_gz.as_slice())
.context("appending data.tar.gz")?;
builder.into_inner()?.flush()?;
}
println!("created: {}", deb_out.display());
Ok(())
}

View file

@ -1,7 +1,5 @@
use super::BundleContext;
use crate::utils::tar::TarBuilderExt;
use anyhow::{Context, Result, bail};
use std::collections::HashMap;
use anyhow::{Context, Result};
use std::path::Path;
use std::{fs, io};
@ -114,6 +112,7 @@ impl<'a> BuildRootFs for RealBuildRootFs<'a> {
}
fn create_file(&mut self, mode: u32, relative: &str, data: &mut dyn io::Read) -> Result<()> {
let _ = mode; // suppress warning on windows
let path = &self.0.join(relative);
std::io::copy(
data,
@ -132,37 +131,6 @@ impl<'a> BuildRootFs for RealBuildRootFs<'a> {
}
}
impl<W: io::Write> BuildRootFs for tar::Builder<W> {
fn create_dir(&mut self, path: &str) -> Result<()> {
self.append_directory(path)
}
fn create_file(&mut self, mode: u32, path: &str, data: &mut dyn io::Read) -> Result<()> {
self.append_file_data(mode, path, data)
}
}
impl BuildRootFs for rpm::PackageBuilder {
fn create_dir(&mut self, path: &str) -> Result<()> {
self.with_dir_entry(rpm::FileOptions::dir(format!("/{path}")))
.map(|_| ())
.with_context(|| format!("creating directory {}", path))
}
fn create_file(&mut self, mode: u32, path: &str, data: &mut dyn io::Read) -> Result<()> {
let mut contents = vec![];
data.read_to_end(&mut contents)
.with_context(|| format!("reading data for {}", path))?;
self.with_file_contents(
contents,
rpm::FileOptions::new(format!("/{path}")).permissions(mode as u16),
)
.map(|_| ())
.with_context(|| format!("creating file {}", path))
}
}
/// Render the desktop file template from `alcom.desktop`.
///
/// The template uses `{{key}}` placeholders as in the tauri bundler.
@ -177,89 +145,3 @@ pub fn render_desktop_file(ctx: &BundleContext<'_>, exec: &str) -> Result<String
pub static LINUX_ICON_RESOLUTIONS: &[&str] = &["32x32", "64x64", "128x128"];
pub static LINUX_ICON_NAME: &str = "alcom"; // keep in sync with alcom.desktop template
pub struct LibraryVersions {
pub libc: String,
pub libgcc: String,
}
pub fn detect_library_versions(path: &Path) -> Result<LibraryVersions> {
use object::read::elf::ElfFile64;
use object::{Endianness, Object, ObjectSymbol};
let binary = fs::read(path).context("Reading binary")?;
let elf = ElfFile64::<Endianness>::parse(&binary).context("failed to parse binary")?;
let Some(versions) = elf.elf_section_table().versions(elf.endian(), elf.data())? else {
bail!("no version table found");
};
let versions = elf
.dynamic_symbols()
.map(|s| versions.version_index(elf.endian(), s.index()))
.flat_map(|i| versions.version(i).transpose())
.collect::<Result<Vec<_>, _>>()?;
let mut by_lib = HashMap::new();
for version in versions.into_iter() {
let lib = version.name().split(|&x| x == b'_').next().unwrap();
let version = VersionNumber::try_from(version.name())?;
let existing = by_lib.entry(lib).or_insert(VersionNumber::MIN);
if *existing < version {
*existing = version;
}
}
//for (lib, version) in &by_lib {
// let lib = std::str::from_utf8(lib)?;
// println!("{lib}: {version}");
//}
return Ok(LibraryVersions {
libc: by_lib[&b"GLIBC"[..]].to_string(),
libgcc: by_lib[&b"GCC"[..]].to_string(),
});
#[derive(Ord, PartialOrd, Eq, PartialEq)]
struct VersionNumber(Vec<u32>);
impl VersionNumber {
pub const MIN: VersionNumber = VersionNumber(Vec::new());
}
impl<'a> TryFrom<&'a [u8]> for VersionNumber {
type Error = anyhow::Error;
fn try_from(value: &'a [u8]) -> std::result::Result<Self, Self::Error> {
let value = if let Some(index) = value.iter().position(|&x| x == b'_') {
value.split_at(index + 1).1
} else {
value
};
let value = std::str::from_utf8(value)?;
let components = value
.split('.')
.map(std::str::FromStr::from_str)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self(components))
}
}
impl std::fmt::Display for VersionNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut iter = self.0.iter();
f.write_fmt(format_args!("{}", iter.next().unwrap()))?;
for x in iter {
f.write_fmt(format_args!(".{}", x))?;
}
Ok(())
}
}
impl std::fmt::Debug for VersionNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
}

View file

@ -1,63 +0,0 @@
use super::BundleContext;
use crate::bundle_alcom::linux::*;
use anyhow::{Context, Result};
use rpm::Dependency;
use std::fs;
pub fn create_rpm(ctx: &BundleContext<'_>) -> Result<()> {
let arch = rpm_arch(ctx.target_tuple);
let rpm_name = format!("alcom-{}-1.{arch}.rpm", ctx.version());
let rpm_dir = ctx.bundle_dir.join("rpm");
fs::create_dir_all(&rpm_dir)?;
let rpm_out = rpm_dir.join(&rpm_name);
let library = detect_library_versions(&ctx.binary_path())?;
let mut builder = rpm::PackageBuilder::new(
"alcom",
// RPM doesn't support '-' in their version name.
// It's recommended to use '~' instead.
// https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/#_handling_non_sorting_versions_with_tilde_dot_and_caret
&ctx.version().replace('-', "~"),
"MIT",
arch,
ctx.short_description(),
);
builder.release("1").description(ctx.long_description());
builder.requires(Dependency::any(format!(
"libgcc_s.so.1(GCC_{})(64bit)",
library.libgcc
)));
builder.requires(Dependency::any(format!(
"libc.so.6(GLIBC_{})(64bit)",
library.libc
)));
builder.requires(Dependency::any("libgtk-3.so.0()(64bit)"));
builder.requires(Dependency::any("libwebkit2gtk-4.1.so.0()(64bit)"));
// Binary.
create_install_build_root_impl(ctx, &mut builder).context("adding files to rpm")?;
let pkg = builder.build().context("building rpm package")?;
pkg.write_file(&rpm_out)
.with_context(|| format!("writing {}", rpm_out.display()))?;
println!("created: {}", rpm_out.display());
Ok(())
}
/// RPM architecture string.
fn rpm_arch(triple: &str) -> &str {
if triple.starts_with("aarch64") {
"aarch64"
} else if triple.starts_with("x86_64") {
"x86_64"
} else {
panic!(
"unsupported architecture in target triple for rpm: {}",
triple
)
}
}

View file

@ -1,10 +1,11 @@
use crate::bundle_alcom::BundleContext;
use crate::utils::command::{CommandExt, WineRunner};
use crate::utils::{download_file_cached, target_abi};
use crate::utils::{cargo, download_file_cached, target_abi};
use anyhow::{Context, Result, bail};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command as ProcessCommand;
use zip::write::FileOptions;
const WEBVIEW2_URL: &str = "https://go.microsoft.com/fwlink/?linkid=2124703";
const INNO_SETUP_VERSION: &str = "6.7.1";
@ -28,6 +29,26 @@ pub fn create_setup_exe(ctx: &BundleContext<'_>) -> Result<()> {
Ok(())
}
pub fn create_setup_exe_zip(ctx: &BundleContext<'_>) -> Result<()> {
let wrapper_in_bundle = ctx.bundle_dir.join("setup/alcom-setup.exe");
let zip = ctx.bundle_dir.join("setup/alcom-setup.exe.zip");
let mut zip = zip::write::ZipWriter::new(fs::File::create(&zip).context("creating zip file")?);
zip.start_file(
format!("ALCOM-{}-x86_64-setup.exe", cargo::gui_version()),
FileOptions::DEFAULT,
)
.context("adding file to zip")?;
std::io::copy(
&mut std::io::BufReader::new(
fs::File::open(&wrapper_in_bundle).context("opening alcom-setup.exe")?,
),
&mut zip,
)
.context("copying file to zip")?;
Ok(())
}
pub fn create_updater_exe(ctx: &BundleContext<'_>) -> Result<()> {
let iss_setup = ctx.bundle_dir.join("setup/alcom-setup.exe");

View file

@ -150,6 +150,7 @@ fn process_pe_64(binary: &[u8]) -> Result<bool> {
| b"crypt32.dll" // since Windows NT 4.0/XP era
| b"bcryptprimitives.dll" // since Windows 7/Server 2008 R2
| b"combase.dll" // since Windows 8 / Server 2012
| b"shlwapi.dll" // since Windows 98
| b"api-ms-win-core-synch-l1-2-0.dll" // since Windows 8 / Server 2012
=> {
println!(

View file

@ -1,4 +1,5 @@
use cargo_metadata::Metadata;
use cargo_metadata::semver::Version;
use std::sync::OnceLock;
#[allow(dead_code)]
@ -10,3 +11,12 @@ pub fn cargo_metadata() -> &'static Metadata {
.expect("cargo metadata failed")
})
}
pub fn gui_version() -> &'static Version {
cargo_metadata()
.packages
.iter()
.find(|p| p.name == "vrc-get-gui")
.map(|p| &p.version)
.expect("vrc-get-gui metadata not found")
}