From 7c67cca7a764c382a025896a29ae30b605bb5fbf Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Thu, 27 Aug 2026 17:49:46 +0100 Subject: [PATCH] feat(instance-sync): screenshots sync (#7218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pnpm prepr * feat(instance-sync): screenshots sync * fix: prepr + fmt * fix: qa * feat: screenshit editor * feat: screenshot* editor qa * fix: qa * fix: lint * fix: aecsocket rev * qa: a11y tab navigation focus is cut off * qa: Change “Edited” badge to be bg-highlight-green * qa: friends list input style wrong * qa: toolbar width + labels * qa: multiselect changes * qa: anim changes * qa: card hover colors * qa: copy feedback * qa: hide date * qa: instance icon in overflow/contextmenu * qa: spacing * qa: group empty state text * qa: drag preview badge * qa: group deletion skip warning if no screenshots * qa: redesign editor * qa: remove screenshot parent/edited badge stuff * qa: control font size consistency * qa: viewer copy control * qa: editor fixes * qa: redirect on disable screenshot sync * qa: margin police * fix: crop * fix: qa * feat: initial start on basic instance file syncing (#7220) * qa: final * qa: final 2 * fix: lint + prepr * fix: copy * fix: screenshot editing outside of app causing ghost files in db + sync fail rollback impl * chore: split up * fix: fmt --------- Co-authored-by: tdgao --- apps/app-frontend/package.json | 2 + apps/app-frontend/src/App.vue | 49 +- .../src/components/ui/friends/FriendsList.vue | 5 +- .../components/ui/modal/AppSettingsModal.vue | 24 +- .../components/ui/screenshots-page/card.vue | 195 ++ .../ui/screenshots-page/drag-gather.vue | 96 + .../ui/screenshots-page/drag-preview.vue | 65 + .../components/ui/screenshots-page/group.vue | 141 ++ .../components/ui/screenshots-page/index.vue | 1469 ++++++++++++++ .../ui/screenshots-page/section.vue | 138 ++ .../ui/screenshots-page/toolbar.vue | 87 + .../use-screenshot-drag-gather.ts | 130 ++ .../instances/DefaultInstanceSettings.vue | 362 ---- .../instances/InstancesSyncedSettings.vue | 887 +++++++++ .../src/components/ui/world/WorldItem.vue | 43 +- .../world/modal/ConfirmRemoveWorldModal.vue | 16 + .../ui/world/modal/DesyncServerModal.vue | 74 + .../generated/app-events/InstancePayload.ts | 2 +- .../generated/app-events/postcard/index.d.ts | 2 +- .../generated/app-events/postcard/index.js | 14 +- apps/app-frontend/src/helpers/instance.ts | 213 ++ apps/app-frontend/src/helpers/types.d.ts | 6 + apps/app-frontend/src/helpers/worlds.ts | 47 +- .../app-frontend/src/locales/ar-SA/index.json | 12 - .../app-frontend/src/locales/cs-CZ/index.json | 9 - .../app-frontend/src/locales/da-DK/index.json | 9 - .../app-frontend/src/locales/de-CH/index.json | 12 - .../app-frontend/src/locales/de-DE/index.json | 12 - .../app-frontend/src/locales/en-US/index.json | 313 ++- .../src/locales/es-419/index.json | 12 - .../app-frontend/src/locales/es-ES/index.json | 12 - .../app-frontend/src/locales/fi-FI/index.json | 9 - .../src/locales/fil-PH/index.json | 9 - .../app-frontend/src/locales/fr-FR/index.json | 12 - .../app-frontend/src/locales/he-IL/index.json | 9 - .../app-frontend/src/locales/hu-HU/index.json | 12 - .../app-frontend/src/locales/id-ID/index.json | 9 - .../app-frontend/src/locales/it-IT/index.json | 12 - .../app-frontend/src/locales/ja-JP/index.json | 12 - .../app-frontend/src/locales/ko-KR/index.json | 12 - .../app-frontend/src/locales/ms-MY/index.json | 9 - .../app-frontend/src/locales/nl-NL/index.json | 12 - .../app-frontend/src/locales/no-NO/index.json | 9 - .../app-frontend/src/locales/pl-PL/index.json | 12 - .../app-frontend/src/locales/pt-BR/index.json | 12 - .../app-frontend/src/locales/pt-PT/index.json | 9 - .../app-frontend/src/locales/ro-RO/index.json | 9 - .../app-frontend/src/locales/ru-RU/index.json | 12 - .../app-frontend/src/locales/sr-CS/index.json | 9 - .../app-frontend/src/locales/sv-SE/index.json | 12 - .../app-frontend/src/locales/th-TH/index.json | 9 - .../app-frontend/src/locales/tr-TR/index.json | 12 - .../app-frontend/src/locales/uk-UA/index.json | 12 - .../app-frontend/src/locales/vi-VN/index.json | 9 - .../app-frontend/src/locales/zh-CN/index.json | 12 - .../app-frontend/src/locales/zh-TW/index.json | 12 - apps/app-frontend/src/pages/Browse.vue | 2 +- apps/app-frontend/src/pages/Screenshots.vue | 29 + apps/app-frontend/src/pages/index.js | 3 +- .../settings-modal/hooks-settings.vue | 146 +- .../components/settings-modal/index.vue | 46 +- .../settings-modal/java-settings.vue | 308 +-- .../settings-options-transition.vue | 51 + .../synced-options-settings.vue | 376 ++++ .../settings-modal/window-settings.vue | 128 +- apps/app-frontend/src/pages/instance/index.ts | 3 +- .../src/pages/instance/layout.vue | 18 + .../src/pages/instance/query-options.ts | 48 +- .../src/pages/instance/screenshots/index.vue | 11 + .../share/shared-instance-members-table.vue | 2 +- .../src/pages/instance/worlds/index.vue | 44 +- .../src/pages/project/Gallery.vue | 320 +-- .../src/providers/app-settings-modal.ts | 3 + apps/app-frontend/src/providers/setup.ts | 2 + .../providers/setup/image-viewer-editor.ts | 20 + apps/app-frontend/src/routes.js | 10 + apps/app/build.rs | 27 + apps/app/src/api/instance.rs | 306 +++ apps/app/src/api/worlds.rs | 10 + apps/app/src/macos/menu.rs | 88 + apps/app/src/macos/mod.rs | 1 + apps/app/src/main.rs | 7 + .../src/pages/[type]/[project]/gallery.vue | 249 +-- .../src/pages/discover/[type]/index.vue | 2 +- ...8fa2c60ebd20a6c10d62fbae546780863d7f7.json | 20 + ...c5cb51b41bf5fa94b8bd4b9676f360bf2de7e.json | 12 + ...001e6e09eaf922553d5bbfefd6d12c01eb98f.json | 12 + ...5b56c11ddfa4e1512cbda761e10cba5d78e68.json | 20 + ...c7a2b421a4350290ce3dd27075567e67c7ad2.json | 26 + ...f262f9d961d1a0bf170c890ce61bb1dea1dc5.json | 12 + ...4ae6a27161b7bdd38ba8b4bbbb900d43fc3c8.json | 20 + ...89ea4f165a4a2405a069dd95326cc1c1bda8e.json | 12 + ...bdab9e29c3e8f8401225d8351348e51cbe314.json | 26 + ...80a97310f308b3aa25c322d499b32d549f7b7.json | 32 + ...bd807403238b5fd6891803a00464dbfa663fc.json | 12 + ...3bd43f0aaa4b554c2a3cac6fb1079e539bf27.json | 20 + ...eb85abf6c98d4bdfcd93a9a6054e9d5235099.json | 12 + ...d8329d4bd301d4cfae600ff08564075e45989.json | 12 + ...50386782589c77c0517026489d8928149941b.json | 12 + ...1ab4de4ed309b54239bacb54b0d173c4e5acb.json | 12 + ...12e9c53683afbe05ab5f441c5f5fb1bce0472.json | 26 + ...67fdc4ff254e929cafce9fd3ff38449e88b89.json | 12 + ...cd6b28eddf7e6366de078850788d2b3128d0b.json | 38 + ...7668cfd117f83baab840228dbd5f6c6e9fe89.json | 12 + ...eae357c1043ac65bc021fe093da5e72b2684a.json | 12 + ...14690b61e08f5cb3316c4349c78dc0e4d81f7.json | 12 + ...3a59d340a3ba4a0543539fd37ead0f9813a37.json | 26 + ...dd02ebf24d7d61bb74b22abdf46e53b4efdfa.json | 12 + ...dc78ed283e6ce57c9a2332645df74b359b5f7.json | 32 + ...bd64a1c6bb5fc5f3c0082500afdcfa7340efa.json | 19 + ...2a75e1c92cece3b2715f9a3392c058e9347a8.json | 20 + ...ab5a059f3d1b868a5c45dbb9de86dcd177085.json | 12 + ...f674df6125b03954683a70431257cc8c923e9.json | 12 + ...44bedb489b84d81eba304af25523c8d38dcd2.json | 12 + ...77504cc79e5b200d5c260d86bb33e093112d9.json | 26 + ...e591e1a305966ffaa84fbc2e15cccc98854aa.json | 12 + ...65d017d21a6c6476d0f2ae09c076d75f0dda3.json | 10 + ...f53b73af4d0715d7c15732416d7ecf6204019.json | 12 + ...75c1883beaecc71a81d50e6a67d0565782ba0.json | 12 + ...e8f44f71a1e3131d56e06aae05b26d4bbdebd.json | 20 + ...4172cb6567c6c055399632d960548672ce343.json | 12 + ...004b3283447e78920e762bcac2765fca6f3eb.json | 20 + ...f9d3a54720cbdcdf957b985ec7bef43dbe978.json | 32 + ...eba891c23cd79886a97d91bddb7019baab3ce.json | 12 + ...04f808cff64f5341e2b82bc6b934fb99078b3.json | 38 + ...3c6f3e5719319ce8469e0304fd2d93e046006.json | 20 + ...98455c6db66d485824f27ab198c3f00de1770.json | 19 + ...44857cc3145d7936b79ab4cf6adcbeeec3095.json | 20 + ...93230488492804bb4f1d5ccec4a90454438f7.json | 12 + ...e053f5fb7e204e01e54892168c6c36873d60f.json | 12 + ...782521100fac266da99102379ba01a89e9076.json | 12 + ...7e0ae6202cd391ce25a26642fcfffff05c688.json | 12 + ...3b360940b0419833a5679133fff5faaa81201.json | 44 + ...49a24fd1debeeef6495fd49cc1da50e8f56b5.json | 12 + ...e9c86aa6b24f126c30a02b50473d4a0b78086.json | 12 + ...d04834e26b15af36df178c16da30b5fdda090.json | 20 + ...9df2d66a0bb9f2aa766f2fe979cd82c0536a6.json | 12 + ...e521fa08921e146ae79ea4bd0990fa1cc11ba.json | 12 + ...693a973084f3c3ffe8d521daf8d553957f5ae.json | 12 + ...5414f0ab43a9c28c4ea2327cd0022876b925f.json | 12 + ...b5ef93185aab1e2722fd13bbdae287d988c2f.json | 20 + ...e461b77671619a65fb809bd9b8ed9ab71baaa.json | 20 + ...640acd542e91c99fa491b282542ec4c58032c.json | 20 + ...cda439b83265cc885d720104f61d8defcba66.json | 12 + ...20260819120000_instance-synced-options.sql | 123 ++ .../20260819130000_screenshot-groups.sql | 41 + ...20260824120000_screenshot-editor-state.sql | 1 + packages/app-lib/src/api/instance.rs | 41 +- .../app-lib/src/api/instance/lifecycle.rs | 23 +- .../src/api/instance/screenshot_groups.rs | 350 ++++ .../src/api/instance/screenshots/mod.rs | 10 + .../api/instance/screenshots/operations.rs | 655 +++++++ .../instance/screenshots/reconciliation.rs | 323 +++ .../synced_options/command_history.rs | 185 ++ .../src/api/instance/synced_options/files.rs | 300 +++ .../api/instance/synced_options/hotbars.rs | 607 ++++++ .../src/api/instance/synced_options/mod.rs | 28 + .../instance/synced_options/orchestration.rs | 919 +++++++++ .../src/api/instance/synced_servers/codec.rs | 112 ++ .../src/api/instance/synced_servers/mod.rs | 24 + .../api/instance/synced_servers/modpack.rs | 225 +++ .../api/instance/synced_servers/operations.rs | 824 ++++++++ .../api/instance/synced_servers/storage.rs | 358 ++++ .../src/api/instance/synced_servers/types.rs | 131 ++ packages/app-lib/src/api/mod.rs | 12 +- .../app-lib/src/api/pack/install_mrpack.rs | 21 + packages/app-lib/src/api/worlds.rs | 179 +- packages/app-lib/src/event/mod.rs | 1 + packages/app-lib/src/launcher/mod.rs | 10 + packages/app-lib/src/state/dirs.rs | 7 + .../adapters/sqlite/instance_rows.rs | 218 ++- .../state/instances/adapters/sqlite/mod.rs | 1 + .../adapters/sqlite/screenshot_rows.rs | 236 +++ .../instances/commands/create_instance.rs | 16 + .../state/instances/commands/get_instance.rs | 5 +- .../instances/commands/remove_instance.rs | 3 + .../src/state/instances/model/instance.rs | 39 + .../app-lib/src/state/instances/watcher.rs | 75 +- packages/app-lib/src/state/mod.rs | 42 +- packages/app-lib/src/state/process.rs | 193 +- packages/assets/generated-icons.ts | 16 + packages/assets/icons/circle.svg | 15 + packages/assets/icons/crop.svg | 16 + packages/assets/icons/eraser.svg | 16 + packages/assets/icons/highlighter.svg | 16 + packages/assets/icons/mouse-pointer-2.svg | 15 + packages/assets/icons/move-up-right.svg | 16 + packages/assets/icons/square.svg | 15 + packages/assets/icons/type.svg | 17 + packages/ui/package.json | 1 + .../base/buttons/CheckCircleButton.vue | 55 + .../ui/src/components/base/buttons/index.ts | 1 + packages/ui/src/components/base/index.ts | 1 + .../image-viewer-editor/controls.vue | 357 ++++ .../components/image-viewer-editor/editor.vue | 597 ++++++ .../image-viewer-editor-censor-object.ts | 136 ++ .../image-viewer-editor-messages.ts | 56 + .../image-viewer-editor-types.ts | 52 + .../components/image-viewer-editor/index.vue | 307 +++ .../image-viewer-editor/toolbar.vue | 92 + .../components/image-viewer-editor/types.ts | 23 + .../image-viewer-editor/use-image-editor.ts | 1741 +++++++++++++++++ packages/ui/src/components/index.ts | 7 + packages/ui/src/components/modal/NewModal.vue | 10 +- packages/ui/src/composables/modal-stack.ts | 19 +- .../src/layouts/shared/browse-tab/layout.vue | 2 +- .../ui/src/layouts/shared/console/layout.vue | 80 +- .../src/layouts/shared/content-tab/layout.vue | 5 +- .../language-settings-selector.vue | 74 +- packages/ui/src/locales/en-US/index.json | 111 ++ .../ui/src/providers/image-viewer-editor.ts | 15 + packages/ui/src/providers/index.ts | 1 + .../stories/base/ImageViewerEditor.stories.ts | 87 + .../buttons/CheckCircleButton.stories.ts | 41 + .../ui/src/stories/modal/NewModal.stories.ts | 25 + packages/ui/src/utils/ace-mode-mcfunction.ts | 105 + pnpm-lock.yaml | 510 ++++- 217 files changed, 17139 insertions(+), 1887 deletions(-) create mode 100644 apps/app-frontend/src/components/ui/screenshots-page/card.vue create mode 100644 apps/app-frontend/src/components/ui/screenshots-page/drag-gather.vue create mode 100644 apps/app-frontend/src/components/ui/screenshots-page/drag-preview.vue create mode 100644 apps/app-frontend/src/components/ui/screenshots-page/group.vue create mode 100644 apps/app-frontend/src/components/ui/screenshots-page/index.vue create mode 100644 apps/app-frontend/src/components/ui/screenshots-page/section.vue create mode 100644 apps/app-frontend/src/components/ui/screenshots-page/toolbar.vue create mode 100644 apps/app-frontend/src/components/ui/screenshots-page/use-screenshot-drag-gather.ts delete mode 100644 apps/app-frontend/src/components/ui/settings/instances/DefaultInstanceSettings.vue create mode 100644 apps/app-frontend/src/components/ui/settings/instances/InstancesSyncedSettings.vue create mode 100644 apps/app-frontend/src/components/ui/world/modal/DesyncServerModal.vue create mode 100644 apps/app-frontend/src/pages/Screenshots.vue create mode 100644 apps/app-frontend/src/pages/instance/components/settings-modal/settings-options-transition.vue create mode 100644 apps/app-frontend/src/pages/instance/components/settings-modal/synced-options-settings.vue create mode 100644 apps/app-frontend/src/pages/instance/screenshots/index.vue create mode 100644 apps/app-frontend/src/providers/setup/image-viewer-editor.ts create mode 100644 apps/app/src/macos/menu.rs create mode 100644 packages/app-lib/.sqlx/query-0365707ab9d9b9eb4f189fa9f888fa2c60ebd20a6c10d62fbae546780863d7f7.json create mode 100644 packages/app-lib/.sqlx/query-0a9977b44045c6d0e974770deabc5cb51b41bf5fa94b8bd4b9676f360bf2de7e.json create mode 100644 packages/app-lib/.sqlx/query-0c2570758978e370bc2b3240874001e6e09eaf922553d5bbfefd6d12c01eb98f.json create mode 100644 packages/app-lib/.sqlx/query-0f66133eae064cc4b6bc079302d5b56c11ddfa4e1512cbda761e10cba5d78e68.json create mode 100644 packages/app-lib/.sqlx/query-10a2857ec0152359f562650711ec7a2b421a4350290ce3dd27075567e67c7ad2.json create mode 100644 packages/app-lib/.sqlx/query-14cb855184725b3e891d2365563f262f9d961d1a0bf170c890ce61bb1dea1dc5.json create mode 100644 packages/app-lib/.sqlx/query-166c4f42eab67dfe1b83d6c29374ae6a27161b7bdd38ba8b4bbbb900d43fc3c8.json create mode 100644 packages/app-lib/.sqlx/query-16ff7e3f6efa13aeee64468c0d089ea4f165a4a2405a069dd95326cc1c1bda8e.json create mode 100644 packages/app-lib/.sqlx/query-18050b91c80f2cc7d218aa334e6bdab9e29c3e8f8401225d8351348e51cbe314.json create mode 100644 packages/app-lib/.sqlx/query-1fb66446c3ba0f52e682f8aae4b80a97310f308b3aa25c322d499b32d549f7b7.json create mode 100644 packages/app-lib/.sqlx/query-209158894a8658b61fda164cb35bd807403238b5fd6891803a00464dbfa663fc.json create mode 100644 packages/app-lib/.sqlx/query-247aa979f6f6fd116b832cc5a513bd43f0aaa4b554c2a3cac6fb1079e539bf27.json create mode 100644 packages/app-lib/.sqlx/query-2ede94b5eab73255500fbd4b6f7eb85abf6c98d4bdfcd93a9a6054e9d5235099.json create mode 100644 packages/app-lib/.sqlx/query-3ad4711be00a29f9c84b1529f7cd8329d4bd301d4cfae600ff08564075e45989.json create mode 100644 packages/app-lib/.sqlx/query-3b97f2c92fe42ade6a2981cc89750386782589c77c0517026489d8928149941b.json create mode 100644 packages/app-lib/.sqlx/query-48c53bc771b0c2af2ccef50cd121ab4de4ed309b54239bacb54b0d173c4e5acb.json create mode 100644 packages/app-lib/.sqlx/query-4e75c5ee789d5a781967fc0845f12e9c53683afbe05ab5f441c5f5fb1bce0472.json create mode 100644 packages/app-lib/.sqlx/query-5616069c5dd7233ef7cf867ac8367fdc4ff254e929cafce9fd3ff38449e88b89.json create mode 100644 packages/app-lib/.sqlx/query-58c94159c024a407b7291a16ac5cd6b28eddf7e6366de078850788d2b3128d0b.json create mode 100644 packages/app-lib/.sqlx/query-58d7b3809798f5476eaa56f23a37668cfd117f83baab840228dbd5f6c6e9fe89.json create mode 100644 packages/app-lib/.sqlx/query-5d509c2cce1728990fbf9ee4921eae357c1043ac65bc021fe093da5e72b2684a.json create mode 100644 packages/app-lib/.sqlx/query-607eac5be3e2a29bb81cce3d0ff14690b61e08f5cb3316c4349c78dc0e4d81f7.json create mode 100644 packages/app-lib/.sqlx/query-6787d19c235ae92fc05f08782173a59d340a3ba4a0543539fd37ead0f9813a37.json create mode 100644 packages/app-lib/.sqlx/query-6c58c5daf14ed5198c14799f4a2dd02ebf24d7d61bb74b22abdf46e53b4efdfa.json create mode 100644 packages/app-lib/.sqlx/query-6e8707551a6154d73d58ac54d35dc78ed283e6ce57c9a2332645df74b359b5f7.json create mode 100644 packages/app-lib/.sqlx/query-6f17b1880668e6efb235eaa3808bd64a1c6bb5fc5f3c0082500afdcfa7340efa.json create mode 100644 packages/app-lib/.sqlx/query-743f351ba5f612262b98b0a12dc2a75e1c92cece3b2715f9a3392c058e9347a8.json create mode 100644 packages/app-lib/.sqlx/query-7567783dcac147378086ed2bd7eab5a059f3d1b868a5c45dbb9de86dcd177085.json create mode 100644 packages/app-lib/.sqlx/query-778ef883e1e68bb549dd1b82761f674df6125b03954683a70431257cc8c923e9.json create mode 100644 packages/app-lib/.sqlx/query-7ea54f8960b92b275b5f9d4190c44bedb489b84d81eba304af25523c8d38dcd2.json create mode 100644 packages/app-lib/.sqlx/query-8540ff309e2e5eaef1a3f76bdf877504cc79e5b200d5c260d86bb33e093112d9.json create mode 100644 packages/app-lib/.sqlx/query-8aa89bfd90dba93d7ae2274f0b1e591e1a305966ffaa84fbc2e15cccc98854aa.json create mode 100644 packages/app-lib/.sqlx/query-8aae262e7a2e6375164af3b296465d017d21a6c6476d0f2ae09c076d75f0dda3.json create mode 100644 packages/app-lib/.sqlx/query-8bff2deeaa56090262af4ce54c7f53b73af4d0715d7c15732416d7ecf6204019.json create mode 100644 packages/app-lib/.sqlx/query-8cde9f140077c5b11ff4c5b174d75c1883beaecc71a81d50e6a67d0565782ba0.json create mode 100644 packages/app-lib/.sqlx/query-9182f6a8ea76fef826b442ff07ae8f44f71a1e3131d56e06aae05b26d4bbdebd.json create mode 100644 packages/app-lib/.sqlx/query-92fb9a65a4d741ffc64c62c65f94172cb6567c6c055399632d960548672ce343.json create mode 100644 packages/app-lib/.sqlx/query-98fbd284e94d1c509ded25c1c17004b3283447e78920e762bcac2765fca6f3eb.json create mode 100644 packages/app-lib/.sqlx/query-9dbcad56f76eca4497e928a6367f9d3a54720cbdcdf957b985ec7bef43dbe978.json create mode 100644 packages/app-lib/.sqlx/query-a0705b02be354c37f7a1294eca7eba891c23cd79886a97d91bddb7019baab3ce.json create mode 100644 packages/app-lib/.sqlx/query-a1313b9534a5eec443721ad6ac904f808cff64f5341e2b82bc6b934fb99078b3.json create mode 100644 packages/app-lib/.sqlx/query-a27af45c646b3bea98974f0b2f63c6f3e5719319ce8469e0304fd2d93e046006.json create mode 100644 packages/app-lib/.sqlx/query-a83e168791306f30ee6f03a274398455c6db66d485824f27ab198c3f00de1770.json create mode 100644 packages/app-lib/.sqlx/query-b012838123b5acd053412951b0044857cc3145d7936b79ab4cf6adcbeeec3095.json create mode 100644 packages/app-lib/.sqlx/query-c3448ac48772d99b4cfd296996a93230488492804bb4f1d5ccec4a90454438f7.json create mode 100644 packages/app-lib/.sqlx/query-c695093aadc64edd159fce723d1e053f5fb7e204e01e54892168c6c36873d60f.json create mode 100644 packages/app-lib/.sqlx/query-cd9918f43f0f30b863ee48a7626782521100fac266da99102379ba01a89e9076.json create mode 100644 packages/app-lib/.sqlx/query-d4879ae44bd13fb97606876e9c67e0ae6202cd391ce25a26642fcfffff05c688.json create mode 100644 packages/app-lib/.sqlx/query-d8f74c9d677ac6cc41503f1c88d3b360940b0419833a5679133fff5faaa81201.json create mode 100644 packages/app-lib/.sqlx/query-db467f33a061ee2b39c3729596a49a24fd1debeeef6495fd49cc1da50e8f56b5.json create mode 100644 packages/app-lib/.sqlx/query-df901b84d082e28c63b41db4a0de9c86aa6b24f126c30a02b50473d4a0b78086.json create mode 100644 packages/app-lib/.sqlx/query-e6a85166a48a4350d3876e3902ad04834e26b15af36df178c16da30b5fdda090.json create mode 100644 packages/app-lib/.sqlx/query-e9ab62553f47a98c02b45655f589df2d66a0bb9f2aa766f2fe979cd82c0536a6.json create mode 100644 packages/app-lib/.sqlx/query-ea6de6d2969a82372bceee3c886e521fa08921e146ae79ea4bd0990fa1cc11ba.json create mode 100644 packages/app-lib/.sqlx/query-edaca667a0fcd5b429b1788e2b4693a973084f3c3ffe8d521daf8d553957f5ae.json create mode 100644 packages/app-lib/.sqlx/query-efd07185aa3047caee3b3323aeb5414f0ab43a9c28c4ea2327cd0022876b925f.json create mode 100644 packages/app-lib/.sqlx/query-f0b9a997d247d3b53604e96c075b5ef93185aab1e2722fd13bbdae287d988c2f.json create mode 100644 packages/app-lib/.sqlx/query-fc91a68aaf7e6762d3bddbd4fe4e461b77671619a65fb809bd9b8ed9ab71baaa.json create mode 100644 packages/app-lib/.sqlx/query-fd2eb3ed33e2a0eaca507bbe156640acd542e91c99fa491b282542ec4c58032c.json create mode 100644 packages/app-lib/.sqlx/query-ffc9e28c260ab9f2f5f57b88fc6cda439b83265cc885d720104f61d8defcba66.json create mode 100644 packages/app-lib/migrations/20260819120000_instance-synced-options.sql create mode 100644 packages/app-lib/migrations/20260819130000_screenshot-groups.sql create mode 100644 packages/app-lib/migrations/20260824120000_screenshot-editor-state.sql create mode 100644 packages/app-lib/src/api/instance/screenshot_groups.rs create mode 100644 packages/app-lib/src/api/instance/screenshots/mod.rs create mode 100644 packages/app-lib/src/api/instance/screenshots/operations.rs create mode 100644 packages/app-lib/src/api/instance/screenshots/reconciliation.rs create mode 100644 packages/app-lib/src/api/instance/synced_options/command_history.rs create mode 100644 packages/app-lib/src/api/instance/synced_options/files.rs create mode 100644 packages/app-lib/src/api/instance/synced_options/hotbars.rs create mode 100644 packages/app-lib/src/api/instance/synced_options/mod.rs create mode 100644 packages/app-lib/src/api/instance/synced_options/orchestration.rs create mode 100644 packages/app-lib/src/api/instance/synced_servers/codec.rs create mode 100644 packages/app-lib/src/api/instance/synced_servers/mod.rs create mode 100644 packages/app-lib/src/api/instance/synced_servers/modpack.rs create mode 100644 packages/app-lib/src/api/instance/synced_servers/operations.rs create mode 100644 packages/app-lib/src/api/instance/synced_servers/storage.rs create mode 100644 packages/app-lib/src/api/instance/synced_servers/types.rs create mode 100644 packages/app-lib/src/state/instances/adapters/sqlite/screenshot_rows.rs create mode 100644 packages/assets/icons/circle.svg create mode 100644 packages/assets/icons/crop.svg create mode 100644 packages/assets/icons/eraser.svg create mode 100644 packages/assets/icons/highlighter.svg create mode 100644 packages/assets/icons/mouse-pointer-2.svg create mode 100644 packages/assets/icons/move-up-right.svg create mode 100644 packages/assets/icons/square.svg create mode 100644 packages/assets/icons/type.svg create mode 100644 packages/ui/src/components/base/buttons/CheckCircleButton.vue create mode 100644 packages/ui/src/components/image-viewer-editor/controls.vue create mode 100644 packages/ui/src/components/image-viewer-editor/editor.vue create mode 100644 packages/ui/src/components/image-viewer-editor/image-viewer-editor-censor-object.ts create mode 100644 packages/ui/src/components/image-viewer-editor/image-viewer-editor-messages.ts create mode 100644 packages/ui/src/components/image-viewer-editor/image-viewer-editor-types.ts create mode 100644 packages/ui/src/components/image-viewer-editor/index.vue create mode 100644 packages/ui/src/components/image-viewer-editor/toolbar.vue create mode 100644 packages/ui/src/components/image-viewer-editor/types.ts create mode 100644 packages/ui/src/components/image-viewer-editor/use-image-editor.ts create mode 100644 packages/ui/src/providers/image-viewer-editor.ts create mode 100644 packages/ui/src/stories/base/ImageViewerEditor.stories.ts create mode 100644 packages/ui/src/stories/buttons/CheckCircleButton.stories.ts create mode 100644 packages/ui/src/utils/ace-mode-mcfunction.ts diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index abe6e55c0f..7eea9d5397 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -32,6 +32,7 @@ "@tauri-apps/plugin-window-state": "^2.2.2", "@types/three": "^0.172.0", "@vueuse/core": "^11.1.0", + "ace-builds": "^1.43.5", "dayjs": "^1.11.10", "floating-vue": "^5.2.2", "fuse.js": "^6.6.2", @@ -45,6 +46,7 @@ "vue-i18n": "^10.0.0", "vue-router": "^4.6.0", "vue-virtual-scroller": "v2.0.0-beta.8", + "vue3-ace-editor": "^2.2.4", "vuedraggable": "^4.1.0" }, "devDependencies": { diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 28764564ba..668c557a86 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -14,6 +14,7 @@ import { ChevronLeftIcon, ChevronRightIcon, CompassIcon, + ImagesIcon, LogInIcon, LogOutIcon, NewspaperIcon, @@ -64,6 +65,7 @@ import { renderString } from '@modrinth/utils' import { useQuery, useQueryClient } from '@tanstack/vue-query' import { getVersion } from '@tauri-apps/api/app' import { convertFileSrc, invoke } from '@tauri-apps/api/core' +import { listen } from '@tauri-apps/api/event' import { getCurrentWindow } from '@tauri-apps/api/window' import { fetch as tauriFetch } from '@tauri-apps/plugin-http' import { openUrl } from '@tauri-apps/plugin-opener' @@ -118,7 +120,12 @@ import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics' import { check_reachable } from '@/helpers/auth.js' import { get_user, get_user_many, get_version } from '@/helpers/cache.js' import { install_create_modpack_instance, install_get_modpack_preview } from '@/helpers/install' -import { can_current_user_use_shared_instances, get as getInstance, run } from '@/helpers/instance' +import { + can_current_user_use_shared_instances, + get as getInstance, + get_global_synced_options, + run, +} from '@/helpers/instance' import { get as getCreds, getAll as getAllCreds, @@ -173,7 +180,10 @@ import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer' import { get_available_capes, get_available_skins } from './helpers/skins' import { AppNotificationManager } from './providers/app-notifications' import { AppPopupNotificationManager } from './providers/app-popup-notifications' -import { appSettingsModalOpenProfileKey } from './providers/app-settings-modal' +import { + appSettingsModalOpenProfileKey, + appSettingsModalOpenSyncedOptionsKey, +} from './providers/app-settings-modal' const appSettings = useAppSettings() const appTheme = useTheme() @@ -438,6 +448,11 @@ const os = ref('') const isDevEnvironment = ref(false) const stateInitialized = ref(false) +const globalSyncedOptionsQuery = useQuery({ + queryKey: ['global-synced-options'], + queryFn: get_global_synced_options, + enabled: computed(() => stateInitialized.value), +}) const criticalErrorMessage = ref() @@ -469,7 +484,24 @@ const authUnreachable = computed(() => { return false }) +let unlistenEditMenu + +function handleEditMenuAction(action) { + const event = new CustomEvent(`edit-menu:${action}`, { cancelable: true }) + if (document.dispatchEvent(event)) document.execCommand(action) +} + onMounted(async () => { + try { + const listeners = await Promise.all([ + listen('edit-menu://undo', () => handleEditMenuAction('undo')), + listen('edit-menu://redo', () => handleEditMenuAction('redo')), + ]) + unlistenEditMenu = () => listeners.forEach((unlisten) => unlisten()) + } catch (error) { + handleError(error) + } + await useCheckDisableMouseover() try { handleAdsConsentRequired(await should_show_ads_consent_popup()) @@ -490,6 +522,7 @@ onUnmounted(async () => { document.querySelector('body').removeEventListener('auxclick', handleAuxClick) document.querySelector('body').removeEventListener('contextmenu', handleContextMenu) document.removeEventListener('fullscreenchange', handleFullscreenChange) + unlistenEditMenu?.() clearDelayedUpdatePopup() if (fullscreenAdsWindowHold) { @@ -557,6 +590,10 @@ const messages = defineMessages({ id: 'app.nav.modrinth-hosting', defaultMessage: 'Modrinth Hosting', }, + screenshots: { + id: 'app.nav.screenshots', + defaultMessage: 'Screenshots', + }, createNewInstance: { id: 'app.nav.create-new-instance', defaultMessage: 'Create new instance', @@ -999,6 +1036,7 @@ const updateToPlayModal = ref() const modrinthLoginModal = ref() const appSettingsModal = ref() provide(appSettingsModalOpenProfileKey, () => appSettingsModal.value?.showProfile()) +provide(appSettingsModalOpenSyncedOptionsKey, () => appSettingsModal.value?.showSyncedOptions()) watch(incompatibilityWarningModal, (modal) => { if (modal) { @@ -2025,6 +2063,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload) + + + diff --git a/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue index d1dfbec528..78b58fc443 100644 --- a/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue +++ b/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue @@ -1,12 +1,12 @@ + + + + diff --git a/apps/app-frontend/src/components/ui/screenshots-page/drag-gather.vue b/apps/app-frontend/src/components/ui/screenshots-page/drag-gather.vue new file mode 100644 index 0000000000..eedee94806 --- /dev/null +++ b/apps/app-frontend/src/components/ui/screenshots-page/drag-gather.vue @@ -0,0 +1,96 @@ + + + diff --git a/apps/app-frontend/src/components/ui/screenshots-page/drag-preview.vue b/apps/app-frontend/src/components/ui/screenshots-page/drag-preview.vue new file mode 100644 index 0000000000..f658bcad94 --- /dev/null +++ b/apps/app-frontend/src/components/ui/screenshots-page/drag-preview.vue @@ -0,0 +1,65 @@ + + + diff --git a/apps/app-frontend/src/components/ui/screenshots-page/group.vue b/apps/app-frontend/src/components/ui/screenshots-page/group.vue new file mode 100644 index 0000000000..f9705c6e5f --- /dev/null +++ b/apps/app-frontend/src/components/ui/screenshots-page/group.vue @@ -0,0 +1,141 @@ + + + diff --git a/apps/app-frontend/src/components/ui/screenshots-page/index.vue b/apps/app-frontend/src/components/ui/screenshots-page/index.vue new file mode 100644 index 0000000000..e688e435f7 --- /dev/null +++ b/apps/app-frontend/src/components/ui/screenshots-page/index.vue @@ -0,0 +1,1469 @@ + + + diff --git a/apps/app-frontend/src/components/ui/screenshots-page/section.vue b/apps/app-frontend/src/components/ui/screenshots-page/section.vue new file mode 100644 index 0000000000..b5872c4ddf --- /dev/null +++ b/apps/app-frontend/src/components/ui/screenshots-page/section.vue @@ -0,0 +1,138 @@ + + + diff --git a/apps/app-frontend/src/components/ui/screenshots-page/toolbar.vue b/apps/app-frontend/src/components/ui/screenshots-page/toolbar.vue new file mode 100644 index 0000000000..3b5812ec1c --- /dev/null +++ b/apps/app-frontend/src/components/ui/screenshots-page/toolbar.vue @@ -0,0 +1,87 @@ + + + diff --git a/apps/app-frontend/src/components/ui/screenshots-page/use-screenshot-drag-gather.ts b/apps/app-frontend/src/components/ui/screenshots-page/use-screenshot-drag-gather.ts new file mode 100644 index 0000000000..9afe8b949a --- /dev/null +++ b/apps/app-frontend/src/components/ui/screenshots-page/use-screenshot-drag-gather.ts @@ -0,0 +1,130 @@ +import { onBeforeUnmount, type Ref, ref } from 'vue' + +import type { InstanceScreenshot } from '@/helpers/instance' + +type Point = { + x: number + y: number +} + +export type ActiveScreenshotDrag = { + primarySelectionKey: string + selectionKeys: string[] +} + +export const gatherDuration = 500 + +export type ScreenshotDragGatherItem = { + screenshot: InstanceScreenshot + selectionKey: string + rect: { + left: number + top: number + width: number + height: number + } +} + +function getSelectionKey(screenshot: InstanceScreenshot) { + return JSON.stringify([screenshot.instance_id, screenshot.file_name]) +} + +export function useScreenshotDragGather(screenshots: Ref) { + const items = ref([]) + const target = ref({ x: 0, y: 0 }) + const targetOffset = ref({ x: 0, y: 0 }) + const isGathering = ref(false) + let cleanupTimer: number | undefined + + const clearCleanupTimer = () => { + if (cleanupTimer !== undefined) { + clearTimeout(cleanupTimer) + cleanupTimer = undefined + } + } + + const clear = () => { + clearCleanupTimer() + items.value = [] + targetOffset.value = { x: 0, y: 0 } + isGathering.value = false + } + + const updateTarget = (pointer: Point) => { + target.value = { + x: pointer.x + targetOffset.value.x, + y: pointer.y + targetOffset.value.y, + } + } + + const start = (drag: ActiveScreenshotDrag | null, pointer: Point) => { + clear() + + const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + if (!drag || drag.selectionKeys.length < 2 || reduceMotion) return + + const screenshotCards = Array.from( + document.querySelectorAll('[data-screenshot-card]'), + ) + const sourceCard = screenshotCards.find( + (card) => card.dataset.selectionKey === drag.primarySelectionKey, + ) + + if (sourceCard) { + const sourceRect = sourceCard.getBoundingClientRect() + targetOffset.value = { + x: sourceRect.left + sourceRect.width / 2 - pointer.x, + y: sourceRect.top + sourceRect.height / 2 - pointer.y, + } + } + + const screenshotsByKey = new Map( + screenshots.value.map((screenshot) => [getSelectionKey(screenshot), screenshot]), + ) + items.value = drag.selectionKeys.flatMap((selectionKey) => { + const screenshot = screenshotsByKey.get(selectionKey) + const card = screenshotCards.find( + (candidate) => + candidate.dataset.selectionKey === selectionKey && candidate.getClientRects().length > 0, + ) + if (!screenshot || !card) return [] + + const rect = card.getBoundingClientRect() + return [ + { + screenshot, + selectionKey, + rect: { + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + }, + }, + ] + }) + updateTarget(pointer) + isGathering.value = items.value.length > 0 + } + + const finish = () => { + isGathering.value = false + clearCleanupTimer() + cleanupTimer = window.setTimeout(() => { + items.value = [] + cleanupTimer = undefined + }, 400) + } + + onBeforeUnmount(clearCleanupTimer) + + return { + items, + target, + isGathering, + start, + updateTarget, + clear, + finish, + } +} diff --git a/apps/app-frontend/src/components/ui/settings/instances/DefaultInstanceSettings.vue b/apps/app-frontend/src/components/ui/settings/instances/DefaultInstanceSettings.vue deleted file mode 100644 index ebe9e9af7b..0000000000 --- a/apps/app-frontend/src/components/ui/settings/instances/DefaultInstanceSettings.vue +++ /dev/null @@ -1,362 +0,0 @@ - - - diff --git a/apps/app-frontend/src/components/ui/settings/instances/InstancesSyncedSettings.vue b/apps/app-frontend/src/components/ui/settings/instances/InstancesSyncedSettings.vue new file mode 100644 index 0000000000..ecef91748c --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/InstancesSyncedSettings.vue @@ -0,0 +1,887 @@ + + + + + diff --git a/apps/app-frontend/src/components/ui/world/WorldItem.vue b/apps/app-frontend/src/components/ui/world/WorldItem.vue index 509fbdd27d..c226d7be0d 100644 --- a/apps/app-frontend/src/components/ui/world/WorldItem.vue +++ b/apps/app-frontend/src/components/ui/world/WorldItem.vue @@ -67,7 +67,7 @@ const router = useRouter() const { addNotification } = injectNotificationManager() const emit = defineEmits<{ - (e: 'play' | 'play-instance' | 'update' | 'stop' | 'refresh' | 'edit' | 'delete'): void + (e: 'play' | 'play-instance' | 'update' | 'stop' | 'refresh' | 'edit' | 'delete' | 'desync'): void (e: 'open-folder', world: SingleplayerWorld): void }>() @@ -95,6 +95,8 @@ const props = withDefaults( } managed?: boolean + showPlayButton?: boolean + cardBackground?: 'raised' | 'surface-2' // Instance instanceId?: string @@ -117,6 +119,8 @@ const props = withDefaults( gameMode: undefined, managed: false, + showPlayButton: true, + cardBackground: 'raised', instanceId: undefined, instanceName: undefined, @@ -218,6 +222,10 @@ const messages = defineMessages({ id: 'instance.worlds.copy_address', defaultMessage: 'Copy address', }, + desync: { + id: 'instance.worlds.desync_server', + defaultMessage: 'Desync', + }, viewInstance: { id: 'instance.worlds.view_instance', defaultMessage: 'View instance', @@ -270,7 +278,10 @@ const messages = defineMessages({ const cardOptions = useTemplateRef('cardOptions') const showStop = computed( - () => (props.playingWorld || (locked.value && props.playingInstance)) && !props.startingInstance, + () => + props.showPlayButton && + (props.playingWorld || (locked.value && props.playingInstance)) && + !props.startingInstance, ) const playDisabled = computed( () => @@ -327,6 +338,17 @@ const overflowOptions = computed((): ButtonMenuOption[] => [ shown: props.world.type === 'server', action: () => copyToClipboard((props.world as ServerWorld).address), }, + { + id: 'desync', + label: formatMessage(messages.desync), + icon: XIcon, + action: () => emit('desync'), + shown: + !props.instanceId && + props.world.type === 'server' && + (props.world as ServerWorld).source === 'user_synced' && + !!(props.world as ServerWorld).server_id, + }, { id: 'edit', label: formatMessage(commonMessages.editButton), @@ -404,6 +426,7 @@ const contextMenuOptions = computed((): ButtonMenuOption[] => [ label: formatMessage(commonMessages.stopButton), icon: StopCircleIcon, tone: 'red', + shown: props.showPlayButton, action: () => emit('stop'), } : { @@ -411,11 +434,12 @@ const contextMenuOptions = computed((): ButtonMenuOption[] => [ label: formatMessage(messages.playWorld), icon: PlayIcon, tone: 'brand', + shown: props.showPlayButton, disabled: playDisabled.value, tooltip: playTooltip.value ?? undefined, action: () => emit('play'), }, - { type: 'divider' }, + { type: 'divider', shown: props.showPlayButton }, ...overflowOptions.value, ]) @@ -433,9 +457,11 @@ function openContextMenu(event: MouseEvent) { />
-