@rynt/sdk 0.9.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/REGISTRIES.md +189 -0
- package/env.d.ts +11 -0
- package/host-shims.d.ts +30 -0
- package/package.json +88 -0
- package/src/extension-marketplace/api-types.ts +141 -0
- package/src/extension-marketplace/client.ts +296 -0
- package/src/extension-marketplace/index.ts +22 -0
- package/src/extension-marketplace/schemas.ts +178 -0
- package/src/extensions/ExtensionRoutePage.vue +17 -0
- package/src/extensions/context.ts +37 -0
- package/src/extensions/disabled-folder.ts +21 -0
- package/src/extensions/extension-expose-map.ts +5 -0
- package/src/extensions/extension-expose.ts +48 -0
- package/src/extensions/graph.ts +67 -0
- package/src/extensions/index.ts +251 -0
- package/src/extensions/invite-handler/types.ts +20 -0
- package/src/extensions/launcher-entities/create-launcher-entity.ts +25 -0
- package/src/extensions/launcher-entities/keys.ts +46 -0
- package/src/extensions/launcher-entities/launcher-entity-components.ts +177 -0
- package/src/extensions/launcher-entities/props-map.ts +69 -0
- package/src/extensions/launcher-entities/registry.ts +32 -0
- package/src/extensions/launcher-models/apis/accounts-contracts.ts +102 -0
- package/src/extensions/launcher-models/apis/launcher-model-apis.ts +553 -0
- package/src/extensions/launcher-models/keys.ts +23 -0
- package/src/extensions/launcher-models/public.ts +9 -0
- package/src/extensions/launcher-models/registry-core.ts +34 -0
- package/src/extensions/manifest-types.ts +22 -0
- package/src/extensions/manifest.ts +46 -0
- package/src/extensions/marketplace-open-key.ts +26 -0
- package/src/extensions/plugin-types.ts +44 -0
- package/src/extensions/plugin.ts +62 -0
- package/src/extensions/registries/bootstrap.ts +11 -0
- package/src/extensions/registries/builtins/account-provider.ts +6 -0
- package/src/extensions/registries/builtins/app-topbar-left-widgets.ts +6 -0
- package/src/extensions/registries/builtins/app-topbar-right-widgets.ts +6 -0
- package/src/extensions/registries/builtins/app-topbar-status-widgets.ts +6 -0
- package/src/extensions/registries/builtins/build-card-actions.ts +6 -0
- package/src/extensions/registries/builtins/build-card-after-meta.ts +6 -0
- package/src/extensions/registries/builtins/build-card-before-media.ts +6 -0
- package/src/extensions/registries/builtins/build-card-before-meta.ts +6 -0
- package/src/extensions/registries/builtins/build-card-footer-actions.ts +6 -0
- package/src/extensions/registries/builtins/build-detail-after-content.ts +6 -0
- package/src/extensions/registries/builtins/build-detail-before-content.ts +6 -0
- package/src/extensions/registries/builtins/build-detail-before-hero.ts +6 -0
- package/src/extensions/registries/builtins/build-detail-header-actions.ts +6 -0
- package/src/extensions/registries/builtins/build-detail-mod-row-actions.ts +6 -0
- package/src/extensions/registries/builtins/build-detail-resourcepack-row-actions.ts +6 -0
- package/src/extensions/registries/builtins/build-detail-right-column-bottom.ts +6 -0
- package/src/extensions/registries/builtins/build-detail-right-column-top.ts +6 -0
- package/src/extensions/registries/builtins/dialog-footer-actions.ts +6 -0
- package/src/extensions/registries/builtins/feed-after-content.ts +6 -0
- package/src/extensions/registries/builtins/feed-before-content.ts +6 -0
- package/src/extensions/registries/builtins/file-editor.ts +19 -0
- package/src/extensions/registries/builtins/friends-after-list.ts +6 -0
- package/src/extensions/registries/builtins/friends-before-list.ts +6 -0
- package/src/extensions/registries/builtins/index.ts +141 -0
- package/src/extensions/registries/builtins/invite-handler.ts +7 -0
- package/src/extensions/registries/builtins/library-after-content.ts +6 -0
- package/src/extensions/registries/builtins/library-before-content.ts +6 -0
- package/src/extensions/registries/builtins/loader.ts +8 -0
- package/src/extensions/registries/builtins/map-card-actions.ts +6 -0
- package/src/extensions/registries/builtins/map-card-after-meta.ts +6 -0
- package/src/extensions/registries/builtins/map-card-before-meta.ts +6 -0
- package/src/extensions/registries/builtins/map-card-footer-actions.ts +6 -0
- package/src/extensions/registries/builtins/map-detail-after-content.ts +6 -0
- package/src/extensions/registries/builtins/map-detail-before-content.ts +6 -0
- package/src/extensions/registries/builtins/map-detail-header-actions.ts +6 -0
- package/src/extensions/registries/builtins/markdown-editor-tiptap-extensions.ts +7 -0
- package/src/extensions/registries/builtins/markdown-editor-toolbar-actions.ts +6 -0
- package/src/extensions/registries/builtins/markdown-renderer-after-content.ts +6 -0
- package/src/extensions/registries/builtins/markdown-renderer-before-content.ts +6 -0
- package/src/extensions/registries/builtins/mod-details-footer-actions.ts +6 -0
- package/src/extensions/registries/builtins/mod-manage-actions.ts +6 -0
- package/src/extensions/registries/builtins/mod-provider.ts +5 -0
- package/src/extensions/registries/builtins/nav.ts +7 -0
- package/src/extensions/registries/builtins/page.ts +13 -0
- package/src/extensions/registries/builtins/projects-after-content.ts +6 -0
- package/src/extensions/registries/builtins/projects-before-content.ts +6 -0
- package/src/extensions/registries/builtins/resourcepack-manage-actions.ts +7 -0
- package/src/extensions/registries/builtins/server-card-actions.ts +6 -0
- package/src/extensions/registries/builtins/server-card-after-meta.ts +6 -0
- package/src/extensions/registries/builtins/server-card-before-meta.ts +6 -0
- package/src/extensions/registries/builtins/server-card-footer-actions.ts +6 -0
- package/src/extensions/registries/builtins/server-detail-after-content.ts +6 -0
- package/src/extensions/registries/builtins/server-detail-before-content.ts +6 -0
- package/src/extensions/registries/builtins/server-detail-header-actions.ts +6 -0
- package/src/extensions/registries/builtins/settings-after-sections.ts +6 -0
- package/src/extensions/registries/builtins/settings-before-sections.ts +6 -0
- package/src/extensions/registries/builtins/settings-section-widgets.ts +6 -0
- package/src/extensions/registries/builtins/shaderpack-manage-actions.ts +7 -0
- package/src/extensions/registries/builtins/shell.ts +5 -0
- package/src/extensions/registries/builtins/sidebar-after-content.ts +6 -0
- package/src/extensions/registries/builtins/sidebar-before-content.ts +6 -0
- package/src/extensions/registries/builtins/sidebar-footer-widgets.ts +6 -0
- package/src/extensions/registries/builtins/sidebar-header-widgets.ts +6 -0
- package/src/extensions/registries/builtins/sidebar.ts +11 -0
- package/src/extensions/registries/builtins/theme.ts +5 -0
- package/src/extensions/registries/builtins/user-card-after-meta.ts +6 -0
- package/src/extensions/registries/builtins/user-card-before-meta.ts +6 -0
- package/src/extensions/registries/builtins/user-menu-actions.ts +6 -0
- package/src/extensions/registries/builtins/user-menu-after-actions.ts +6 -0
- package/src/extensions/registries/builtins/user-menu-before-actions.ts +6 -0
- package/src/extensions/registries/builtins/user-strip.ts +5 -0
- package/src/extensions/registries/clear-extension-ui-registries.ts +15 -0
- package/src/extensions/registries/define-extension-registry.ts +58 -0
- package/src/extensions/registries/extension-host-api.ts +41 -0
- package/src/extensions/registries/extension-registry-api.ts +103 -0
- package/src/extensions/registries/extension-registry-payload-map.ts +9 -0
- package/src/extensions/registries/extension-scope.ts +41 -0
- package/src/extensions/registries/get-registry.ts +23 -0
- package/src/extensions/registries/index.ts +58 -0
- package/src/extensions/registries/manifest-rynt.ts +193 -0
- package/src/extensions/registries/registry-slot.ts +40 -0
- package/src/extensions/registries/registry-value-map.ts +89 -0
- package/src/extensions/registries/store.ts +206 -0
- package/src/extensions/resolve-extensions.ts +245 -0
- package/src/extensions/router-bridge.ts +103 -0
- package/src/extensions/session.ts +6 -0
- package/src/extensions/slug.ts +23 -0
- package/src/extensions/version.ts +147 -0
- package/src/host/extensions-composables.ts +33 -0
- package/src/host/extensions-init.ts +194 -0
- package/src/host/index.ts +11 -0
- package/src/host/launcher-models/index.ts +4 -0
- package/src/index.ts +229 -0
- package/src/minecraft-loader/base-loader.ts +102 -0
- package/src/minecraft-loader/index.ts +11 -0
- package/src/minecraft-loader/loader-registry.ts +72 -0
- package/src/shared/api/assets.ts +112 -0
- package/src/shared/api/auth.ts +283 -0
- package/src/shared/api/builds.ts +647 -0
- package/src/shared/api/config.ts +19 -0
- package/src/shared/api/download-stats.ts +103 -0
- package/src/shared/api/downloads.ts +36 -0
- package/src/shared/api/entity-authorship.ts +60 -0
- package/src/shared/api/events.ts +393 -0
- package/src/shared/api/friends.ts +140 -0
- package/src/shared/api/graphql.ts +87 -0
- package/src/shared/api/index.ts +23 -0
- package/src/shared/api/invites.ts +262 -0
- package/src/shared/api/library.ts +44 -0
- package/src/shared/api/maps.ts +385 -0
- package/src/shared/api/notify-websocket.ts +140 -0
- package/src/shared/api/posts.ts +357 -0
- package/src/shared/api/projectServers.ts +379 -0
- package/src/shared/api/serverMembers.ts +173 -0
- package/src/shared/api/users.ts +294 -0
- package/src/shared/composables/buildEditor/useBuildEditor.ts +66 -0
- package/src/shared/composables/buildManifest/buildManifest.ts +447 -0
- package/src/shared/composables/filesEditor/filesEditor.ts +346 -0
- package/src/shared/composables/index.ts +10 -0
- package/src/shared/composables/modsEditor/modsEditor.ts +1678 -0
- package/src/shared/composables/registrySlot/registry-slot-utils.ts +25 -0
- package/src/shared/composables/registrySlot/useRegistrySlotMissing.ts +35 -0
- package/src/shared/composables/resourcePacksEditor/resourcePacksEditor.ts +448 -0
- package/src/shared/composables/shaderPacksEditor/shaderPacksEditor.ts +395 -0
- package/src/shared/composables/useSkinRender.ts +70 -0
- package/src/shared/composables/useZlDeepLink.ts +178 -0
- package/src/shared/definitions/defineGraphCache.ts +216 -0
- package/src/shared/definitions/defineStore.ts +32 -0
- package/src/shared/definitions/index.ts +2 -0
- package/src/shared/minecraft-types/build-manifest.ts +611 -0
- package/src/shared/minecraft-types/index.ts +3 -0
- package/src/shared/minecraft-types/launcher-versions.ts +32 -0
- package/src/shared/minecraft-types/minecraft-launcher-types.ts +276 -0
- package/src/shared/mocks/index.ts +1 -0
- package/src/shared/mocks/navigation.ts +17 -0
- package/src/shared/mods/http.ts +45 -0
- package/src/shared/mods/index.ts +5 -0
- package/src/shared/mods/marketplace-editor-search.ts +266 -0
- package/src/shared/mods/marketplace-search-utils.ts +42 -0
- package/src/shared/mods/mod-marketplace-registry.ts +66 -0
- package/src/shared/mods/mod-marketplace-types.ts +28 -0
- package/src/shared/mods/providers/curseforge.ts +464 -0
- package/src/shared/mods/providers/index.ts +8 -0
- package/src/shared/mods/providers/modrinth.ts +402 -0
- package/src/shared/mods/resolve-mods-provider-loader-ids.ts +77 -0
- package/src/shared/mods/types.ts +76 -0
- package/src/shared/styles/index.css +713 -0
- package/src/shared/themes/index.ts +23 -0
- package/src/shared/themes/theme-tokens-black.json +126 -0
- package/src/shared/themes/theme-tokens-classic.json +126 -0
- package/src/shared/themes/theme-tokens-pink.json +126 -0
- package/src/shared/themes/theme-tokens.json +126 -0
- package/src/shared/themes/types.ts +85 -0
- package/src/shared/types/API_DOCUMENTATION.md +422 -0
- package/src/shared/types/account.ts +40 -0
- package/src/shared/types/build.ts +8 -0
- package/src/shared/types/entities.ts +181 -0
- package/src/shared/types/index.ts +6 -0
- package/src/shared/types/invite-payloads.ts +60 -0
- package/src/shared/types/navigation.ts +16 -0
- package/src/shared/types/running-build.ts +51 -0
- package/src/shared/types/serverMember.ts +17 -0
- package/src/shared/types/user.ts +55 -0
- package/src/shared/ui/base/Avatar.vue +262 -0
- package/src/shared/ui/base/Badge.vue +47 -0
- package/src/shared/ui/base/Button.vue +78 -0
- package/src/shared/ui/base/Divider.vue +42 -0
- package/src/shared/ui/base/Icon.vue +597 -0
- package/src/shared/ui/base/StatusIndicator.vue +44 -0
- package/src/shared/ui/base/index.ts +7 -0
- package/src/shared/ui/cards/InviteCard.vue +47 -0
- package/src/shared/ui/cards/index.ts +2 -0
- package/src/shared/ui/dialog/Dialog.vue +71 -0
- package/src/shared/ui/dialog/DialogContent.vue +31 -0
- package/src/shared/ui/dialog/DialogFooter.vue +14 -0
- package/src/shared/ui/dialog/DialogHeader.vue +41 -0
- package/src/shared/ui/dialog/index.ts +5 -0
- package/src/shared/ui/editors/AttachmentImagesEditor.vue +133 -0
- package/src/shared/ui/editors/ContentAttachmentsDisplay.vue +76 -0
- package/src/shared/ui/editors/MarkdownEditor.vue +956 -0
- package/src/shared/ui/editors/MarkdownRenderer.vue +299 -0
- package/src/shared/ui/editors/RichContentImageViewer.vue +85 -0
- package/src/shared/ui/editors/SocialPostMediaZone.vue +320 -0
- package/src/shared/ui/editors/index.ts +6 -0
- package/src/shared/ui/editors/markdown-editor-gallery.ts +234 -0
- package/src/shared/ui/editors/markdown-editor-image.ts +178 -0
- package/src/shared/ui/form/Checkbox.vue +38 -0
- package/src/shared/ui/form/FormField.vue +30 -0
- package/src/shared/ui/form/FormGrid.vue +38 -0
- package/src/shared/ui/form/ImageEditor.vue +598 -0
- package/src/shared/ui/form/Input.vue +72 -0
- package/src/shared/ui/form/Range.vue +65 -0
- package/src/shared/ui/form/Select.vue +76 -0
- package/src/shared/ui/form/Switch.vue +38 -0
- package/src/shared/ui/form/Textarea.vue +144 -0
- package/src/shared/ui/form/index.ts +9 -0
- package/src/shared/ui/index.ts +9 -0
- package/src/shared/ui/layout/BusyOverlay.vue +31 -0
- package/src/shared/ui/layout/Callout.vue +44 -0
- package/src/shared/ui/layout/Card.vue +38 -0
- package/src/shared/ui/layout/Container.vue +36 -0
- package/src/shared/ui/layout/EmptyState.vue +99 -0
- package/src/shared/ui/layout/EntityMediaRow.vue +54 -0
- package/src/shared/ui/layout/FilterResultsLayout.vue +22 -0
- package/src/shared/ui/layout/FloatingPanel.vue +37 -0
- package/src/shared/ui/layout/FullscreenDimmer.vue +11 -0
- package/src/shared/ui/layout/Grid.vue +40 -0
- package/src/shared/ui/layout/Inline.vue +59 -0
- package/src/shared/ui/layout/LoadingState.vue +39 -0
- package/src/shared/ui/layout/MediaBox.vue +47 -0
- package/src/shared/ui/layout/OverlayPanel.vue +28 -0
- package/src/shared/ui/layout/OverlayWaitPanel.vue +22 -0
- package/src/shared/ui/layout/PageSection.vue +43 -0
- package/src/shared/ui/layout/PageToolbar.vue +29 -0
- package/src/shared/ui/layout/Panel.vue +39 -0
- package/src/shared/ui/layout/ProgressBar.vue +49 -0
- package/src/shared/ui/layout/Section.vue +30 -0
- package/src/shared/ui/layout/SegmentedControl.vue +43 -0
- package/src/shared/ui/layout/SelectableCard.vue +46 -0
- package/src/shared/ui/layout/SelectableRow.vue +41 -0
- package/src/shared/ui/layout/Skeleton.vue +25 -0
- package/src/shared/ui/layout/SkeletonAvatar.vue +30 -0
- package/src/shared/ui/layout/SkeletonEntityCard.vue +20 -0
- package/src/shared/ui/layout/SkeletonFeedPost.vue +22 -0
- package/src/shared/ui/layout/SkeletonGrid.vue +18 -0
- package/src/shared/ui/layout/SkeletonListRow.vue +31 -0
- package/src/shared/ui/layout/SkeletonText.vue +25 -0
- package/src/shared/ui/layout/Stack.vue +42 -0
- package/src/shared/ui/layout/StateBlock.vue +44 -0
- package/src/shared/ui/layout/TwoPaneLayout.vue +35 -0
- package/src/shared/ui/layout/VirtualList.vue +160 -0
- package/src/shared/ui/layout/index.ts +35 -0
- package/src/shared/ui/layout/skeletonSurfaceStyles.ts +24 -0
- package/src/shared/ui/navigation/NavItem.vue +139 -0
- package/src/shared/ui/navigation/Tab.vue +61 -0
- package/src/shared/ui/navigation/Tabs.vue +37 -0
- package/src/shared/ui/navigation/index.ts +4 -0
- package/src/shared/ui/primitives/Action.vue +19 -0
- package/src/shared/ui/primitives/Block.vue +28 -0
- package/src/shared/ui/primitives/CanvasView.vue +19 -0
- package/src/shared/ui/primitives/Control.vue +24 -0
- package/src/shared/ui/primitives/ControlSelect.vue +19 -0
- package/src/shared/ui/primitives/ControlTextarea.vue +17 -0
- package/src/shared/ui/primitives/FieldLabel.vue +19 -0
- package/src/shared/ui/primitives/Form.vue +19 -0
- package/src/shared/ui/primitives/Heading.vue +29 -0
- package/src/shared/ui/primitives/Image.vue +17 -0
- package/src/shared/ui/primitives/LineBreak.vue +3 -0
- package/src/shared/ui/primitives/Link.vue +19 -0
- package/src/shared/ui/primitives/List.vue +28 -0
- package/src/shared/ui/primitives/ListItem.vue +19 -0
- package/src/shared/ui/primitives/OptionItem.vue +19 -0
- package/src/shared/ui/primitives/Text.vue +28 -0
- package/src/shared/ui/primitives/VideoView.vue +19 -0
- package/src/shared/ui/primitives/index.ts +19 -0
- package/src/shared/ui/primitives/resolveElement.ts +25 -0
- package/src/shared/ui/special/AngularAccent.vue +106 -0
- package/src/shared/ui/special/ExtensionRegistrySlotButton.vue +143 -0
- package/src/shared/ui/special/InfoRow.vue +39 -0
- package/src/shared/ui/special/LogViewer.vue +53 -0
- package/src/shared/ui/special/PageHeader.vue +23 -0
- package/src/shared/ui/special/RegistrySlotMissingCallout.vue +48 -0
- package/src/shared/ui/special/WelcomeCard.vue +32 -0
- package/src/shared/ui/special/index.ts +9 -0
- package/src/shared/utils/app-paths.ts +50 -0
- package/src/shared/utils/attachments.ts +16 -0
- package/src/shared/utils/autostart.ts +213 -0
- package/src/shared/utils/build-files.ts +439 -0
- package/src/shared/utils/build-manifest-init.ts +176 -0
- package/src/shared/utils/cloudinary.ts +67 -0
- package/src/shared/utils/cn.ts +7 -0
- package/src/shared/utils/download-stats-week.ts +165 -0
- package/src/shared/utils/entity-api-to-cache.ts +84 -0
- package/src/shared/utils/entity-build-from-api.ts +1 -0
- package/src/shared/utils/entity-display.ts +27 -0
- package/src/shared/utils/entity-map-from-api.ts +1 -0
- package/src/shared/utils/file-hash.ts +65 -0
- package/src/shared/utils/formatSize.ts +5 -0
- package/src/shared/utils/formatTime.ts +157 -0
- package/src/shared/utils/getAccountSkinRender.ts +32 -0
- package/src/shared/utils/index.ts +34 -0
- package/src/shared/utils/local-mods.ts +678 -0
- package/src/shared/utils/local-settings.ts +217 -0
- package/src/shared/utils/member-join-stats.ts +35 -0
- package/src/shared/utils/platform.ts +86 -0
- package/src/shared/utils/play-host-slug.ts +92 -0
- package/src/shared/utils/rich-content.ts +294 -0
- package/src/shared/utils/safeRequest.ts +23 -0
- package/src/shared/utils/semver.ts +81 -0
- package/src/shared/utils/serverPermissions.ts +155 -0
- package/src/shared/utils/skin-render-cache.ts +372 -0
- package/src/shared/utils/stripMarkdown.ts +45 -0
- package/src/shared/utils/transliterate.ts +74 -0
- package/src/shared/utils/updateAccountSkinRender.ts +64 -0
- package/src/shared/utils/updater.ts +218 -0
- package/src/shared/utils/uploadImage.ts +195 -0
- package/src/shared/utils/user-status.ts +9 -0
- package/src/tiptap/index.ts +7 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,1678 @@
|
|
|
1
|
+
import { defineStore } from '../../definitions/defineStore';
|
|
2
|
+
import { computed, reactive, ref, watch, type Ref } from 'vue';
|
|
3
|
+
import { debounce } from 'lodash-es';
|
|
4
|
+
import type {
|
|
5
|
+
BuildVersionManifest,
|
|
6
|
+
Mod,
|
|
7
|
+
ResourcePack,
|
|
8
|
+
} from '../../minecraft-types/build-manifest';
|
|
9
|
+
import { useLocalBuildsModel } from '@/models';
|
|
10
|
+
import type {
|
|
11
|
+
MarketplaceInstallVersion,
|
|
12
|
+
MarketplaceItem,
|
|
13
|
+
MarketplaceSearchContext,
|
|
14
|
+
ProjectType,
|
|
15
|
+
InstallContext,
|
|
16
|
+
ProjectDetails,
|
|
17
|
+
} from '../../mods/types';
|
|
18
|
+
import {
|
|
19
|
+
getModMarketplaceProvider,
|
|
20
|
+
listModMarketplaceProviders,
|
|
21
|
+
} from '../../mods/mod-marketplace-registry';
|
|
22
|
+
import {
|
|
23
|
+
mergeMarketplaceItems,
|
|
24
|
+
runMarketplaceSearch,
|
|
25
|
+
} from '../../mods/marketplace-search-utils';
|
|
26
|
+
import { resolveModsProviderLoaderIds } from '../../mods/resolve-mods-provider-loader-ids';
|
|
27
|
+
|
|
28
|
+
function withMarketplaceLoaderIdsForProvider<
|
|
29
|
+
T extends { loader?: string },
|
|
30
|
+
>(ctx: T, providerId: string): T & { marketplaceLoaderIds: string[] } {
|
|
31
|
+
return {
|
|
32
|
+
...ctx,
|
|
33
|
+
marketplaceLoaderIds: resolveModsProviderLoaderIds(ctx.loader, providerId),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
import {
|
|
37
|
+
scanLocalMods,
|
|
38
|
+
toggleModDisabled,
|
|
39
|
+
deleteLocalMod,
|
|
40
|
+
} from '../../utils/local-mods';
|
|
41
|
+
|
|
42
|
+
/** Вкладки: встроенные + id зарегистрированных расширением провайдеров. */
|
|
43
|
+
export type ModsEditorMarketplaceTab = string;
|
|
44
|
+
|
|
45
|
+
export const useModsEditorModel = defineStore(
|
|
46
|
+
(_manifestRef: Ref<BuildVersionManifest | null>) => {
|
|
47
|
+
// Manifest ref - получаем напрямую при создании store
|
|
48
|
+
const manifest = _manifestRef;
|
|
49
|
+
|
|
50
|
+
const searchQuery = ref('');
|
|
51
|
+
const projectType = ref<ProjectType>('mod');
|
|
52
|
+
const marketplaceTab = ref<ModsEditorMarketplaceTab>('all');
|
|
53
|
+
|
|
54
|
+
const isSearching = ref(false);
|
|
55
|
+
const searchError = ref<string | null>(null);
|
|
56
|
+
|
|
57
|
+
const searchResultsByProvider = reactive<Record<string, MarketplaceItem[]>>(
|
|
58
|
+
{},
|
|
59
|
+
);
|
|
60
|
+
const isSearchingByProvider = reactive<Record<string, boolean>>({});
|
|
61
|
+
const isLoadingMoreByProvider = reactive<Record<string, boolean>>({});
|
|
62
|
+
const hasMoreByProvider = reactive<Record<string, boolean>>({});
|
|
63
|
+
const searchPageByProvider = reactive<Record<string, number>>({});
|
|
64
|
+
const marketplaceBrowseStarted = ref(false);
|
|
65
|
+
|
|
66
|
+
function syncMarketplaceProviderSearchKeys(): void {
|
|
67
|
+
for (const p of listModMarketplaceProviders()) {
|
|
68
|
+
if (!(p.id in searchResultsByProvider)) {
|
|
69
|
+
searchResultsByProvider[p.id] = [];
|
|
70
|
+
}
|
|
71
|
+
if (!(p.id in isSearchingByProvider)) {
|
|
72
|
+
isSearchingByProvider[p.id] = false;
|
|
73
|
+
}
|
|
74
|
+
if (!(p.id in isLoadingMoreByProvider)) {
|
|
75
|
+
isLoadingMoreByProvider[p.id] = false;
|
|
76
|
+
}
|
|
77
|
+
if (!(p.id in hasMoreByProvider)) {
|
|
78
|
+
hasMoreByProvider[p.id] = false;
|
|
79
|
+
}
|
|
80
|
+
if (!(p.id in searchPageByProvider)) {
|
|
81
|
+
searchPageByProvider[p.id] = -1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getProvidersForLoadMore(tabId: string): string[] {
|
|
87
|
+
syncMarketplaceProviderSearchKeys();
|
|
88
|
+
if (tabId === 'all') {
|
|
89
|
+
return listModMarketplaceProviders()
|
|
90
|
+
.map((p) => p.id)
|
|
91
|
+
.filter((id) => hasMoreByProvider[id]);
|
|
92
|
+
}
|
|
93
|
+
return hasMoreByProvider[tabId] ? [tabId] : [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function canLoadMoreMarketplace(tabId: string): boolean {
|
|
97
|
+
return getProvidersForLoadMore(tabId).length > 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isLoadingMoreMarketplace(tabId: string): boolean {
|
|
101
|
+
syncMarketplaceProviderSearchKeys();
|
|
102
|
+
if (tabId === 'all') {
|
|
103
|
+
return listModMarketplaceProviders().some(
|
|
104
|
+
(p) => isLoadingMoreByProvider[p.id],
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return Boolean(isLoadingMoreByProvider[tabId]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function marketplaceItemForProvider(
|
|
111
|
+
providerId: string,
|
|
112
|
+
projectId: string,
|
|
113
|
+
projectType: ProjectType,
|
|
114
|
+
): Promise<MarketplaceItem | null> {
|
|
115
|
+
const prov = getModMarketplaceProvider(providerId);
|
|
116
|
+
if (!prov) return null;
|
|
117
|
+
try {
|
|
118
|
+
const d = await prov.getProjectDetails(projectId);
|
|
119
|
+
return {
|
|
120
|
+
provider: d.provider,
|
|
121
|
+
projectType,
|
|
122
|
+
projectId: d.projectId,
|
|
123
|
+
slug: d.slug,
|
|
124
|
+
name: d.name,
|
|
125
|
+
summary: d.summary,
|
|
126
|
+
iconUrl: d.iconUrl,
|
|
127
|
+
downloads: d.downloads,
|
|
128
|
+
};
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.warn(
|
|
131
|
+
`[modsEditor] Could not load marketplace item ${providerId}:${projectId}:`,
|
|
132
|
+
e,
|
|
133
|
+
);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const selectedModDetails = ref<ProjectDetails | null>(null);
|
|
139
|
+
const isLoadingModDetails = ref(false);
|
|
140
|
+
const modDetailsError = ref<string | null>(null);
|
|
141
|
+
|
|
142
|
+
// Incompatibility dialog state
|
|
143
|
+
const incompatibilityDialog = ref<{
|
|
144
|
+
open: boolean;
|
|
145
|
+
incompatibleMod: Mod | null;
|
|
146
|
+
conflictingMod: Mod | null;
|
|
147
|
+
resolveCallback: ((proceed: boolean) => void) | null;
|
|
148
|
+
}>({
|
|
149
|
+
open: false,
|
|
150
|
+
incompatibleMod: null,
|
|
151
|
+
conflictingMod: null,
|
|
152
|
+
resolveCallback: null,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Dependency removal dialog state
|
|
156
|
+
const dependencyRemovalDialog = ref<{
|
|
157
|
+
open: boolean;
|
|
158
|
+
modToRemove: Mod | null;
|
|
159
|
+
dependentMods: Mod[];
|
|
160
|
+
resolveCallback:
|
|
161
|
+
| ((action: 'cancel' | 'remove-only' | 'remove-all') => void)
|
|
162
|
+
| null;
|
|
163
|
+
}>({
|
|
164
|
+
open: false,
|
|
165
|
+
modToRemove: null,
|
|
166
|
+
dependentMods: [],
|
|
167
|
+
resolveCallback: null,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Version selection dialog state
|
|
171
|
+
const versionSelectionDialog = ref<{
|
|
172
|
+
open: boolean;
|
|
173
|
+
item: MarketplaceItem | null;
|
|
174
|
+
versions: MarketplaceInstallVersion[];
|
|
175
|
+
selectedVersionId: string | null;
|
|
176
|
+
resolveCallback: ((selectedVersionId: string | null) => void) | null;
|
|
177
|
+
}>({
|
|
178
|
+
open: false,
|
|
179
|
+
item: null,
|
|
180
|
+
versions: [],
|
|
181
|
+
selectedVersionId: null,
|
|
182
|
+
resolveCallback: null,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Dependency issues dialog state
|
|
186
|
+
type DependencyIssue =
|
|
187
|
+
| {
|
|
188
|
+
type: 'missing';
|
|
189
|
+
mod: Mod;
|
|
190
|
+
dependency: {
|
|
191
|
+
slug: string;
|
|
192
|
+
modId: string;
|
|
193
|
+
source: string;
|
|
194
|
+
name?: string;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
| { type: 'incompatible'; mod: Mod; conflictingMod: Mod }
|
|
198
|
+
| { type: 'incompatible-version'; mod: Mod; reason: string }; // Mod incompatible with selected loader/version
|
|
199
|
+
|
|
200
|
+
const dependencyIssuesDialog = ref<{
|
|
201
|
+
open: boolean;
|
|
202
|
+
issues: DependencyIssue[];
|
|
203
|
+
isResolving: boolean;
|
|
204
|
+
isResolved: boolean;
|
|
205
|
+
}>({
|
|
206
|
+
open: false,
|
|
207
|
+
issues: [],
|
|
208
|
+
isResolving: false,
|
|
209
|
+
isResolved: false,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Store original manifest for comparing with local mods
|
|
213
|
+
const originalManifest = ref<BuildVersionManifest | null>(null);
|
|
214
|
+
|
|
215
|
+
// Local mods (not in manifest)
|
|
216
|
+
const localMods = ref<Mod[]>([]);
|
|
217
|
+
const localResourcePacks = ref<ResourcePack[]>([]);
|
|
218
|
+
|
|
219
|
+
// Computed, которые читают напрямую из manifest (теперь работают, т.к. manifest установлен при создании)
|
|
220
|
+
const installedMods = computed<Mod[]>(
|
|
221
|
+
() => (manifest.value?.mods ?? []) as Mod[],
|
|
222
|
+
);
|
|
223
|
+
const installedResourcePacks = computed<ResourcePack[]>(
|
|
224
|
+
() => (manifest.value?.resourcePacks ?? []) as ResourcePack[],
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// All mods including local ones
|
|
228
|
+
const allMods = computed<Mod[]>(() => [
|
|
229
|
+
...installedMods.value,
|
|
230
|
+
...localMods.value,
|
|
231
|
+
]);
|
|
232
|
+
const allResourcePacks = computed<ResourcePack[]>(() => [
|
|
233
|
+
...installedResourcePacks.value,
|
|
234
|
+
...localResourcePacks.value,
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
function isInstalledBySlug(slug: string): boolean {
|
|
238
|
+
if (projectType.value === 'resourcepack') {
|
|
239
|
+
return installedResourcePacks.value.some(
|
|
240
|
+
(rp) => rp.projectSlug === slug || rp.id === slug,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return installedMods.value.some(
|
|
244
|
+
(m) => m.projectSlug === slug || m.id === slug,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function searchProvider(
|
|
249
|
+
providerId: string,
|
|
250
|
+
reset: boolean,
|
|
251
|
+
baseSearchCtx: MarketplaceSearchContext,
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
const prov = getModMarketplaceProvider(providerId);
|
|
254
|
+
if (!prov) return;
|
|
255
|
+
|
|
256
|
+
const page = reset ? 0 : (searchPageByProvider[providerId] ?? -1) + 1;
|
|
257
|
+
const ctx = withMarketplaceLoaderIdsForProvider(
|
|
258
|
+
{ ...baseSearchCtx, page },
|
|
259
|
+
providerId,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (reset) {
|
|
263
|
+
isSearchingByProvider[providerId] = true;
|
|
264
|
+
} else {
|
|
265
|
+
isLoadingMoreByProvider[providerId] = true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const result = await runMarketplaceSearch(prov, ctx);
|
|
270
|
+
if (reset) {
|
|
271
|
+
searchResultsByProvider[providerId] = result.items;
|
|
272
|
+
} else {
|
|
273
|
+
searchResultsByProvider[providerId] = mergeMarketplaceItems(
|
|
274
|
+
searchResultsByProvider[providerId] ?? [],
|
|
275
|
+
result.items,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
hasMoreByProvider[providerId] = result.hasMore;
|
|
279
|
+
searchPageByProvider[providerId] = page;
|
|
280
|
+
} catch (e) {
|
|
281
|
+
console.warn(`[modsEditor] ${providerId} search failed:`, e);
|
|
282
|
+
if (reset) {
|
|
283
|
+
searchResultsByProvider[providerId] = [];
|
|
284
|
+
hasMoreByProvider[providerId] = false;
|
|
285
|
+
searchPageByProvider[providerId] = -1;
|
|
286
|
+
}
|
|
287
|
+
} finally {
|
|
288
|
+
if (reset) {
|
|
289
|
+
isSearchingByProvider[providerId] = false;
|
|
290
|
+
} else {
|
|
291
|
+
isLoadingMoreByProvider[providerId] = false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function searchProviders(
|
|
297
|
+
providerIds: string[],
|
|
298
|
+
reset: boolean,
|
|
299
|
+
): Promise<void> {
|
|
300
|
+
syncMarketplaceProviderSearchKeys();
|
|
301
|
+
marketplaceBrowseStarted.value = true;
|
|
302
|
+
|
|
303
|
+
const q = searchQuery.value;
|
|
304
|
+
const baseSearchCtx: MarketplaceSearchContext = {
|
|
305
|
+
projectType: projectType.value,
|
|
306
|
+
query: q,
|
|
307
|
+
minecraftVersion:
|
|
308
|
+
manifest.value?._meta?.manifest?.minecraftVersion || undefined,
|
|
309
|
+
loader: manifest.value?._meta?.manifest?.loader || undefined,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
if (reset) {
|
|
313
|
+
isSearching.value = true;
|
|
314
|
+
searchError.value = null;
|
|
315
|
+
for (const id of providerIds) {
|
|
316
|
+
searchResultsByProvider[id] = [];
|
|
317
|
+
hasMoreByProvider[id] = false;
|
|
318
|
+
searchPageByProvider[id] = -1;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
await Promise.allSettled(
|
|
324
|
+
providerIds.map((id) => searchProvider(id, reset, baseSearchCtx)),
|
|
325
|
+
);
|
|
326
|
+
} catch (e) {
|
|
327
|
+
searchError.value = e instanceof Error ? e.message : String(e);
|
|
328
|
+
} finally {
|
|
329
|
+
if (reset) {
|
|
330
|
+
isSearching.value = false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function search(reset = true) {
|
|
336
|
+
const allIds = listModMarketplaceProviders().map((p) => p.id);
|
|
337
|
+
await searchProviders(allIds, reset);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function loadMoreMarketplaceResults(): Promise<void> {
|
|
341
|
+
if (isSearching.value) return;
|
|
342
|
+
const tabId = marketplaceTab.value;
|
|
343
|
+
const providerIds = getProvidersForLoadMore(tabId);
|
|
344
|
+
if (!providerIds.length) return;
|
|
345
|
+
if (isLoadingMoreMarketplace(tabId)) return;
|
|
346
|
+
await searchProviders(providerIds, false);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function install(
|
|
350
|
+
item: MarketplaceItem,
|
|
351
|
+
options?: { skipVersionPrompt?: boolean; selectedVersionId?: string },
|
|
352
|
+
) {
|
|
353
|
+
if (!manifest.value) throw new Error('Manifest is not initialized');
|
|
354
|
+
|
|
355
|
+
if (!manifest.value.mods) {
|
|
356
|
+
manifest.value.mods = [];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const mcVersion = manifest.value._meta?.manifest?.minecraftVersion;
|
|
360
|
+
if (!mcVersion || mcVersion.trim() === '')
|
|
361
|
+
throw new Error('Select Minecraft version first');
|
|
362
|
+
const loader = manifest.value._meta?.manifest?.loader;
|
|
363
|
+
if (!loader || loader.trim() === '')
|
|
364
|
+
throw new Error('Select loader first');
|
|
365
|
+
|
|
366
|
+
const baseInstallCtx: InstallContext = {
|
|
367
|
+
projectType: projectType.value,
|
|
368
|
+
minecraftVersion:
|
|
369
|
+
manifest.value?._meta?.manifest?.minecraftVersion || '',
|
|
370
|
+
loader: manifest.value?._meta?.manifest?.loader || 'vanilla',
|
|
371
|
+
};
|
|
372
|
+
const installCtx = withMarketplaceLoaderIdsForProvider(
|
|
373
|
+
baseInstallCtx,
|
|
374
|
+
item.provider,
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
let selectedVersionId: string | undefined = options?.selectedVersionId;
|
|
378
|
+
const shouldSelectVersion =
|
|
379
|
+
!selectedVersionId &&
|
|
380
|
+
!options?.skipVersionPrompt &&
|
|
381
|
+
projectType.value === 'mod';
|
|
382
|
+
if (shouldSelectVersion) {
|
|
383
|
+
const prov = getModMarketplaceProvider(item.provider);
|
|
384
|
+
if (prov?.getInstallVersions) {
|
|
385
|
+
const versions = await prov.getInstallVersions(
|
|
386
|
+
item.projectId,
|
|
387
|
+
installCtx,
|
|
388
|
+
);
|
|
389
|
+
if (versions.length > 1) {
|
|
390
|
+
const selected = await showVersionSelectionDialog(item, versions);
|
|
391
|
+
if (!selected) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
selectedVersionId = selected;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let entry: Mod | ResourcePack;
|
|
400
|
+
try {
|
|
401
|
+
const prov = getModMarketplaceProvider(item.provider);
|
|
402
|
+
if (!prov) {
|
|
403
|
+
throw new Error(`Unknown marketplace provider: ${item.provider}`);
|
|
404
|
+
}
|
|
405
|
+
entry = await prov.install(item, installCtx, selectedVersionId);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (projectType.value === 'resourcepack') {
|
|
411
|
+
const rp = entry as ResourcePack;
|
|
412
|
+
const next = [...(manifest.value.resourcePacks ?? [])].filter(
|
|
413
|
+
(x) => x.id !== rp.id, // Use id (projectId) for comparison
|
|
414
|
+
);
|
|
415
|
+
next.push(rp);
|
|
416
|
+
manifest.value.resourcePacks = next;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const mod = entry as Mod;
|
|
421
|
+
|
|
422
|
+
// Resolve all required dependencies first
|
|
423
|
+
const dependencies = await resolveDependencies(mod, installCtx);
|
|
424
|
+
|
|
425
|
+
// Check for incompatibilities with all mods that will be installed (main mod + dependencies)
|
|
426
|
+
const modsToCheck = [mod, ...dependencies];
|
|
427
|
+
const currentInstalledMods = manifest.value.mods ?? [];
|
|
428
|
+
|
|
429
|
+
for (const modToCheck of modsToCheck) {
|
|
430
|
+
const { conflictingMod } = checkIncompatibilities(
|
|
431
|
+
modToCheck,
|
|
432
|
+
currentInstalledMods,
|
|
433
|
+
);
|
|
434
|
+
if (conflictingMod) {
|
|
435
|
+
const proceed = await showIncompatibilityDialog(
|
|
436
|
+
modToCheck,
|
|
437
|
+
conflictingMod,
|
|
438
|
+
);
|
|
439
|
+
if (!proceed) {
|
|
440
|
+
return; // User cancelled installation
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Also check for incompatibilities between mods being installed
|
|
445
|
+
for (const otherMod of modsToCheck) {
|
|
446
|
+
if (modToCheck.id === otherMod.id) continue; // Skip self
|
|
447
|
+
|
|
448
|
+
const { conflictingMod: conflict } = checkIncompatibilities(
|
|
449
|
+
modToCheck,
|
|
450
|
+
[otherMod],
|
|
451
|
+
);
|
|
452
|
+
if (conflict) {
|
|
453
|
+
const proceed = await showIncompatibilityDialog(
|
|
454
|
+
modToCheck,
|
|
455
|
+
conflict,
|
|
456
|
+
);
|
|
457
|
+
if (!proceed) {
|
|
458
|
+
return; // User cancelled installation
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Add all dependencies first
|
|
465
|
+
const allModsToInstall = [...installedMods.value];
|
|
466
|
+
for (const depMod of dependencies) {
|
|
467
|
+
// Remove if already exists (update) - use id (projectId) for comparison
|
|
468
|
+
const existingIndex = allModsToInstall.findIndex(
|
|
469
|
+
(m) => m.id === depMod.id,
|
|
470
|
+
);
|
|
471
|
+
if (existingIndex >= 0) {
|
|
472
|
+
allModsToInstall[existingIndex] = depMod;
|
|
473
|
+
} else {
|
|
474
|
+
allModsToInstall.push(depMod);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Add/update the main mod - use id (projectId) for comparison
|
|
479
|
+
const mainModIndex = allModsToInstall.findIndex((m) => m.id === mod.id);
|
|
480
|
+
|
|
481
|
+
// Update dependencies of main mod with actual installed versions
|
|
482
|
+
if (mod.dependencies && mod.dependencies.length > 0) {
|
|
483
|
+
mod.dependencies = mod.dependencies.map((dep) => {
|
|
484
|
+
// First, try to find in newly installed dependencies
|
|
485
|
+
const installedDep = dependencies.find(
|
|
486
|
+
(m) => m.id === dep.modId || m.projectSlug === dep.slug,
|
|
487
|
+
);
|
|
488
|
+
if (installedDep) {
|
|
489
|
+
return {
|
|
490
|
+
...dep,
|
|
491
|
+
version: installedDep.version, // Use actual installed dependency version
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// If not found in new dependencies, check already installed mods
|
|
496
|
+
const existingDep = allModsToInstall.find(
|
|
497
|
+
(m) => m.id === dep.modId || m.projectSlug === dep.slug,
|
|
498
|
+
);
|
|
499
|
+
if (existingDep) {
|
|
500
|
+
return {
|
|
501
|
+
...dep,
|
|
502
|
+
version: existingDep.version, // Use version of already installed dependency
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// If dependency not found (shouldn't happen for required deps), keep original
|
|
507
|
+
return dep;
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (mainModIndex >= 0) {
|
|
512
|
+
allModsToInstall[mainModIndex] = mod;
|
|
513
|
+
} else {
|
|
514
|
+
allModsToInstall.push(mod);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
console.log('[modsEditor.install] Добавляем мод:', {
|
|
518
|
+
modId: mod.id,
|
|
519
|
+
modName: mod.name,
|
|
520
|
+
allModsToInstallCount: allModsToInstall.length,
|
|
521
|
+
installedModsCountBefore: installedMods.value.length,
|
|
522
|
+
manifestValueExists: !!manifest.value,
|
|
523
|
+
manifestModsExists: !!manifest.value?.mods,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Убеждаемся, что массив mods инициализирован
|
|
527
|
+
if (!manifest.value.mods) {
|
|
528
|
+
manifest.value.mods = [];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Обновляем массив модов - Vue должен отследить изменение
|
|
532
|
+
manifest.value.mods = [...allModsToInstall];
|
|
533
|
+
|
|
534
|
+
console.log('[modsEditor.install] После добавления:', {
|
|
535
|
+
manifestModsCount: manifest.value.mods?.length ?? 0,
|
|
536
|
+
installedModsCountAfter: installedMods.value.length,
|
|
537
|
+
allModsComputedCount: allMods.value.length,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Find all mods that depend on the given mod
|
|
543
|
+
* Uses slug for matching (as per new dependency format)
|
|
544
|
+
*/
|
|
545
|
+
function findDependentMods(mod: Mod): Mod[] {
|
|
546
|
+
const installed = installedMods.value;
|
|
547
|
+
const dependent: Mod[] = [];
|
|
548
|
+
|
|
549
|
+
for (const installedMod of installed) {
|
|
550
|
+
if (
|
|
551
|
+
installedMod.id === mod.id ||
|
|
552
|
+
installedMod.projectSlug === mod.projectSlug
|
|
553
|
+
) {
|
|
554
|
+
continue; // Skip the mod itself
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Check if this mod has a required dependency on the mod we're removing
|
|
558
|
+
// Match by slug (primary) or fallback to modId for backwards compatibility
|
|
559
|
+
const hasRequiredDep = installedMod.dependencies?.some(
|
|
560
|
+
(dep) =>
|
|
561
|
+
dep.type === 'required' &&
|
|
562
|
+
(dep.slug === mod.projectSlug ||
|
|
563
|
+
dep.modId === mod.id ||
|
|
564
|
+
dep.modId === mod.projectSlug),
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
if (hasRequiredDep) {
|
|
568
|
+
dependent.push(installedMod);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return dependent;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Find all mods that can be safely removed (no one depends on them)
|
|
577
|
+
* Recursively finds orphaned dependencies that were installed only for the removed mod
|
|
578
|
+
*/
|
|
579
|
+
function findOrphanedDependencies(
|
|
580
|
+
removedMod: Mod,
|
|
581
|
+
remainingMods: Mod[],
|
|
582
|
+
): Mod[] {
|
|
583
|
+
const orphaned: Mod[] = [];
|
|
584
|
+
const processed = new Set<string>();
|
|
585
|
+
|
|
586
|
+
function findOrphans(currentMod: Mod) {
|
|
587
|
+
const modKey = `${currentMod.source}:${currentMod.projectSlug || currentMod.id}`;
|
|
588
|
+
if (processed.has(modKey)) return;
|
|
589
|
+
processed.add(modKey);
|
|
590
|
+
|
|
591
|
+
// Check if anyone in remaining mods depends on this mod
|
|
592
|
+
// Use slug for matching (as per new dependency format)
|
|
593
|
+
const hasDependents = remainingMods.some((m) => {
|
|
594
|
+
if (m.id === currentMod.id) return false;
|
|
595
|
+
return m.dependencies?.some(
|
|
596
|
+
(dep) =>
|
|
597
|
+
dep.type === 'required' &&
|
|
598
|
+
(dep.slug === currentMod.projectSlug ||
|
|
599
|
+
dep.modId === currentMod.id),
|
|
600
|
+
);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
if (!hasDependents) {
|
|
604
|
+
// This mod is orphaned - no one depends on it
|
|
605
|
+
orphaned.push(currentMod);
|
|
606
|
+
|
|
607
|
+
// Recursively check this mod's dependencies (they might also be orphaned)
|
|
608
|
+
const requiredDeps =
|
|
609
|
+
currentMod.dependencies?.filter((d) => d.type === 'required') ?? [];
|
|
610
|
+
for (const dep of requiredDeps) {
|
|
611
|
+
// Find dependency mod by slug (primary) or modId (fallback)
|
|
612
|
+
const depMod = remainingMods.find(
|
|
613
|
+
(m) => m.projectSlug === dep.slug || m.id === dep.modId,
|
|
614
|
+
);
|
|
615
|
+
if (depMod) {
|
|
616
|
+
findOrphans(depMod);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Start from the removed mod's dependencies
|
|
623
|
+
const requiredDeps =
|
|
624
|
+
removedMod.dependencies?.filter((d) => d.type === 'required') ?? [];
|
|
625
|
+
for (const dep of requiredDeps) {
|
|
626
|
+
const depMod = remainingMods.find(
|
|
627
|
+
(m) => m.projectSlug === dep.modId || m.id === dep.modId,
|
|
628
|
+
);
|
|
629
|
+
if (depMod) {
|
|
630
|
+
findOrphans(depMod);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return orphaned;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Show dependency removal dialog and wait for user decision
|
|
639
|
+
*/
|
|
640
|
+
function showDependencyRemovalDialog(
|
|
641
|
+
modToRemove: Mod,
|
|
642
|
+
dependentMods: Mod[],
|
|
643
|
+
): Promise<'cancel' | 'remove-only' | 'remove-all'> {
|
|
644
|
+
return new Promise((resolve) => {
|
|
645
|
+
dependencyRemovalDialog.value = {
|
|
646
|
+
open: true,
|
|
647
|
+
modToRemove,
|
|
648
|
+
dependentMods,
|
|
649
|
+
resolveCallback: resolve,
|
|
650
|
+
};
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function handleDependencyRemovalCancel() {
|
|
655
|
+
if (dependencyRemovalDialog.value.resolveCallback) {
|
|
656
|
+
dependencyRemovalDialog.value.resolveCallback('cancel');
|
|
657
|
+
}
|
|
658
|
+
dependencyRemovalDialog.value = {
|
|
659
|
+
open: false,
|
|
660
|
+
modToRemove: null,
|
|
661
|
+
dependentMods: [],
|
|
662
|
+
resolveCallback: null,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function handleDependencyRemovalOnly() {
|
|
667
|
+
if (dependencyRemovalDialog.value.resolveCallback) {
|
|
668
|
+
dependencyRemovalDialog.value.resolveCallback('remove-only');
|
|
669
|
+
}
|
|
670
|
+
dependencyRemovalDialog.value = {
|
|
671
|
+
open: false,
|
|
672
|
+
modToRemove: null,
|
|
673
|
+
dependentMods: [],
|
|
674
|
+
resolveCallback: null,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function handleDependencyRemovalAll() {
|
|
679
|
+
if (dependencyRemovalDialog.value.resolveCallback) {
|
|
680
|
+
dependencyRemovalDialog.value.resolveCallback('remove-all');
|
|
681
|
+
}
|
|
682
|
+
dependencyRemovalDialog.value = {
|
|
683
|
+
open: false,
|
|
684
|
+
modToRemove: null,
|
|
685
|
+
dependentMods: [],
|
|
686
|
+
resolveCallback: null,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Check if a mod has all its required dependencies installed
|
|
692
|
+
* Uses slug and version for matching (as per new dependency format)
|
|
693
|
+
*/
|
|
694
|
+
function checkModDependenciesStatus(mod: Mod): {
|
|
695
|
+
isValid: boolean;
|
|
696
|
+
missingDependencies: string[];
|
|
697
|
+
} {
|
|
698
|
+
const missing: string[] = [];
|
|
699
|
+
const installed = installedMods.value;
|
|
700
|
+
const currentMc = manifest.value?._meta?.manifest?.minecraftVersion || '';
|
|
701
|
+
const currentLoader =
|
|
702
|
+
manifest.value?._meta?.manifest?.loader || 'vanilla';
|
|
703
|
+
|
|
704
|
+
// Check compatibility with selected loader and Minecraft version
|
|
705
|
+
if (mod.constraints) {
|
|
706
|
+
const mcMatch = mod.constraints.minecraft === currentMc;
|
|
707
|
+
const loaderMatch = mod.constraints.loader === currentLoader;
|
|
708
|
+
|
|
709
|
+
if (!mcMatch || !loaderMatch) {
|
|
710
|
+
// Add compatibility issue to missing dependencies for UI display
|
|
711
|
+
const reasons: string[] = [];
|
|
712
|
+
if (!mcMatch) {
|
|
713
|
+
reasons.push(
|
|
714
|
+
`Minecraft ${mod.constraints.minecraft} (выбрано: ${currentMc})`,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
if (!loaderMatch) {
|
|
718
|
+
reasons.push(
|
|
719
|
+
`Loader ${mod.constraints.loader} (выбран: ${currentLoader})`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
missing.push(`Несовместимость: ${reasons.join(', ')}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const requiredDeps =
|
|
727
|
+
mod.dependencies?.filter((d) => d.type === 'required') ?? [];
|
|
728
|
+
|
|
729
|
+
for (const dep of requiredDeps) {
|
|
730
|
+
// Check by slug (primary check) and optionally by version
|
|
731
|
+
const isInstalled = installed.some((installedMod) => {
|
|
732
|
+
const slugMatch = installedMod.projectSlug === dep.slug;
|
|
733
|
+
if (!slugMatch) return false;
|
|
734
|
+
|
|
735
|
+
// If version is specified in dependency, check version compatibility
|
|
736
|
+
// For now, we just check if slug matches (version range checking can be added later)
|
|
737
|
+
return true;
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
if (!isInstalled) {
|
|
741
|
+
missing.push(dep.slug || dep.modId);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return {
|
|
746
|
+
isValid: missing.length === 0,
|
|
747
|
+
missingDependencies: missing,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Get dependency status for all installed mods
|
|
753
|
+
*/
|
|
754
|
+
const modsDependencyStatus = computed(() => {
|
|
755
|
+
const status = new Map<
|
|
756
|
+
string,
|
|
757
|
+
{ isValid: boolean; missingDependencies: string[] }
|
|
758
|
+
>();
|
|
759
|
+
const installed = installedMods.value;
|
|
760
|
+
const currentMc = manifest.value?._meta?.manifest?.minecraftVersion || '';
|
|
761
|
+
const currentLoader =
|
|
762
|
+
manifest.value?._meta?.manifest?.loader || 'vanilla';
|
|
763
|
+
|
|
764
|
+
for (const mod of installed) {
|
|
765
|
+
const modKey = mod.id || mod.projectSlug;
|
|
766
|
+
const modStatus = checkModDependenciesStatus(mod);
|
|
767
|
+
status.set(modKey, modStatus);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return status;
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Collect all dependency issues (missing dependencies and incompatibilities)
|
|
775
|
+
*/
|
|
776
|
+
function collectAllDependencyIssues(): DependencyIssue[] {
|
|
777
|
+
const issues: DependencyIssue[] = [];
|
|
778
|
+
const installed = installedMods.value;
|
|
779
|
+
const currentMc = manifest.value?._meta?.manifest?.minecraftVersion || '';
|
|
780
|
+
const currentLoader =
|
|
781
|
+
manifest.value?._meta?.manifest?.loader || 'vanilla';
|
|
782
|
+
|
|
783
|
+
for (const mod of installed) {
|
|
784
|
+
// Check compatibility with selected loader and Minecraft version
|
|
785
|
+
if (mod.constraints) {
|
|
786
|
+
const mcMatch = mod.constraints.minecraft === currentMc;
|
|
787
|
+
const loaderMatch = mod.constraints.loader === currentLoader;
|
|
788
|
+
|
|
789
|
+
if (!mcMatch || !loaderMatch) {
|
|
790
|
+
const reasons: string[] = [];
|
|
791
|
+
if (!mcMatch) {
|
|
792
|
+
reasons.push(
|
|
793
|
+
`Minecraft ${mod.constraints.minecraft} (выбрано: ${currentMc})`,
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
if (!loaderMatch) {
|
|
797
|
+
reasons.push(
|
|
798
|
+
`${mod.constraints.loader} (выбран: ${currentLoader})`,
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
issues.push({
|
|
803
|
+
type: 'incompatible-version',
|
|
804
|
+
mod,
|
|
805
|
+
reason: `Несовместим с выбранными настройками: ${reasons.join(', ')}`,
|
|
806
|
+
});
|
|
807
|
+
// TODO: Implement auto-resolve: search for compatible version of this mod for current loader/version
|
|
808
|
+
continue; // Skip other checks for incompatible mods
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Check for missing required dependencies
|
|
813
|
+
const requiredDeps =
|
|
814
|
+
mod.dependencies?.filter((d) => d.type === 'required') ?? [];
|
|
815
|
+
for (const dep of requiredDeps) {
|
|
816
|
+
const isInstalled = installed.some(
|
|
817
|
+
(installedMod) =>
|
|
818
|
+
installedMod.projectSlug === dep.slug ||
|
|
819
|
+
installedMod.id === dep.modId,
|
|
820
|
+
);
|
|
821
|
+
if (!isInstalled) {
|
|
822
|
+
issues.push({
|
|
823
|
+
type: 'missing',
|
|
824
|
+
mod,
|
|
825
|
+
dependency: {
|
|
826
|
+
slug: dep.slug,
|
|
827
|
+
modId: dep.modId,
|
|
828
|
+
source: dep.source,
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Check for incompatibilities
|
|
835
|
+
const incompatibleDeps =
|
|
836
|
+
mod.dependencies?.filter((d) => d.type === 'incompatible') ?? [];
|
|
837
|
+
for (const dep of incompatibleDeps) {
|
|
838
|
+
const conflicting = installed.find(
|
|
839
|
+
(installedMod) =>
|
|
840
|
+
installedMod.projectSlug === dep.slug ||
|
|
841
|
+
installedMod.id === dep.modId,
|
|
842
|
+
);
|
|
843
|
+
if (conflicting) {
|
|
844
|
+
issues.push({
|
|
845
|
+
type: 'incompatible',
|
|
846
|
+
mod,
|
|
847
|
+
conflictingMod: conflicting,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return issues;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Open dependency issues dialog
|
|
858
|
+
*/
|
|
859
|
+
function openDependencyIssuesDialog() {
|
|
860
|
+
const issues = collectAllDependencyIssues();
|
|
861
|
+
dependencyIssuesDialog.value = {
|
|
862
|
+
open: true,
|
|
863
|
+
issues,
|
|
864
|
+
isResolving: false,
|
|
865
|
+
isResolved: false,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Close dependency issues dialog
|
|
871
|
+
*/
|
|
872
|
+
function closeDependencyIssuesDialog() {
|
|
873
|
+
dependencyIssuesDialog.value = {
|
|
874
|
+
open: false,
|
|
875
|
+
issues: [],
|
|
876
|
+
isResolving: false,
|
|
877
|
+
isResolved: false,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Resolve a single issue (install missing dependency or remove incompatible mod)
|
|
883
|
+
*/
|
|
884
|
+
async function resolveIssue(issue: DependencyIssue) {
|
|
885
|
+
if (!manifest.value) return;
|
|
886
|
+
const mc = manifest.value._meta?.manifest?.minecraftVersion;
|
|
887
|
+
const loader = manifest.value._meta?.manifest?.loader;
|
|
888
|
+
if (!mc || !loader) return;
|
|
889
|
+
|
|
890
|
+
dependencyIssuesDialog.value.isResolving = true;
|
|
891
|
+
|
|
892
|
+
try {
|
|
893
|
+
if (issue.type === 'missing') {
|
|
894
|
+
// Install missing dependency
|
|
895
|
+
const installCtx = {
|
|
896
|
+
projectType: projectType.value,
|
|
897
|
+
minecraftVersion:
|
|
898
|
+
manifest.value?._meta?.manifest?.minecraftVersion || '',
|
|
899
|
+
loader: manifest.value?._meta?.manifest?.loader || 'vanilla',
|
|
900
|
+
} as const;
|
|
901
|
+
|
|
902
|
+
let depItem: MarketplaceItem | null = null;
|
|
903
|
+
depItem = await marketplaceItemForProvider(
|
|
904
|
+
issue.dependency.source,
|
|
905
|
+
issue.dependency.modId,
|
|
906
|
+
installCtx.projectType,
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
if (depItem) {
|
|
910
|
+
await install(depItem, { skipVersionPrompt: true });
|
|
911
|
+
issue.dependency.name = depItem.name;
|
|
912
|
+
} else {
|
|
913
|
+
console.warn(
|
|
914
|
+
`[modsEditor] Could not resolve dependency ${issue.dependency.modId} from ${issue.dependency.source}`,
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
} else if (issue.type === 'incompatible') {
|
|
918
|
+
// Remove incompatible mod
|
|
919
|
+
await removeInstalled(issue.conflictingMod.id);
|
|
920
|
+
} else if (issue.type === 'incompatible-version') {
|
|
921
|
+
// Remove mod incompatible with selected loader/version
|
|
922
|
+
// TODO: Implement auto-resolve: search for compatible version of this mod for current loader/version
|
|
923
|
+
await removeInstalled(issue.mod.id);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Refresh issues after resolution (wait a bit for reactive updates)
|
|
927
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
928
|
+
const updatedIssues = collectAllDependencyIssues();
|
|
929
|
+
dependencyIssuesDialog.value.issues = updatedIssues;
|
|
930
|
+
dependencyIssuesDialog.value.isResolved = updatedIssues.length === 0;
|
|
931
|
+
} catch (error) {
|
|
932
|
+
console.error('[modsEditor] Error resolving issue:', error);
|
|
933
|
+
} finally {
|
|
934
|
+
dependencyIssuesDialog.value.isResolving = false;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Resolve all issues automatically
|
|
940
|
+
*/
|
|
941
|
+
async function resolveAllIssues() {
|
|
942
|
+
if (!manifest.value) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const mc = manifest.value._meta?.manifest?.minecraftVersion;
|
|
946
|
+
const loader = manifest.value._meta?.manifest?.loader;
|
|
947
|
+
if (!mc || !loader) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
dependencyIssuesDialog.value.isResolving = true;
|
|
952
|
+
|
|
953
|
+
try {
|
|
954
|
+
const issues = [...dependencyIssuesDialog.value.issues];
|
|
955
|
+
const installCtx = {
|
|
956
|
+
projectType: projectType.value,
|
|
957
|
+
minecraftVersion:
|
|
958
|
+
manifest.value?._meta?.manifest?.minecraftVersion || '',
|
|
959
|
+
loader: manifest.value?._meta?.manifest?.loader || 'vanilla',
|
|
960
|
+
} as const;
|
|
961
|
+
|
|
962
|
+
// Resolve issues in order: first install missing dependencies, then remove incompatibilities
|
|
963
|
+
const missingIssues = issues.filter((i) => i.type === 'missing');
|
|
964
|
+
const incompatibleIssues = issues.filter(
|
|
965
|
+
(i) => i.type === 'incompatible',
|
|
966
|
+
);
|
|
967
|
+
const incompatibleVersionIssues = issues.filter(
|
|
968
|
+
(i) => i.type === 'incompatible-version',
|
|
969
|
+
);
|
|
970
|
+
|
|
971
|
+
// Install missing dependencies first
|
|
972
|
+
for (const issue of missingIssues) {
|
|
973
|
+
if (issue.type === 'missing') {
|
|
974
|
+
let depItem: MarketplaceItem | null = null;
|
|
975
|
+
depItem = await marketplaceItemForProvider(
|
|
976
|
+
issue.dependency.source,
|
|
977
|
+
issue.dependency.modId,
|
|
978
|
+
installCtx.projectType,
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
if (depItem) {
|
|
982
|
+
await install(depItem, { skipVersionPrompt: true });
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Then remove incompatibilities
|
|
988
|
+
for (const issue of incompatibleIssues) {
|
|
989
|
+
if (issue.type === 'incompatible') {
|
|
990
|
+
await removeInstalled(issue.conflictingMod.id);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Remove incompatible-version mods (mods incompatible with selected loader/version)
|
|
995
|
+
for (const issue of incompatibleVersionIssues) {
|
|
996
|
+
if (issue.type === 'incompatible-version') {
|
|
997
|
+
// TODO: Implement auto-resolve: search for compatible version of this mod for current loader/version
|
|
998
|
+
await removeInstalled(issue.mod.id);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Final check (wait a bit for reactive updates)
|
|
1003
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1004
|
+
const updatedIssues = collectAllDependencyIssues();
|
|
1005
|
+
|
|
1006
|
+
dependencyIssuesDialog.value.issues = updatedIssues;
|
|
1007
|
+
dependencyIssuesDialog.value.isResolved = updatedIssues.length === 0;
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
console.error('[modsEditor] Error resolving all issues:', error);
|
|
1010
|
+
} finally {
|
|
1011
|
+
dependencyIssuesDialog.value.isResolving = false;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async function removeInstalled(idOrSlug: string) {
|
|
1016
|
+
if (!manifest.value) return;
|
|
1017
|
+
if (projectType.value === 'resourcepack') {
|
|
1018
|
+
manifest.value.resourcePacks = (
|
|
1019
|
+
manifest.value.resourcePacks ?? []
|
|
1020
|
+
).filter(
|
|
1021
|
+
(rp) => rp.id !== idOrSlug && rp.projectSlug !== idOrSlug, // Support both id and slug for lookup
|
|
1022
|
+
);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const allMods = manifest.value.mods ?? [];
|
|
1027
|
+
// Support lookup by id (projectId) or slug
|
|
1028
|
+
const modToRemove = allMods.find(
|
|
1029
|
+
(m) => m.id === idOrSlug || m.projectSlug === idOrSlug,
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
if (!modToRemove) {
|
|
1033
|
+
console.warn('[modsEditor] Mod not found for removal:', idOrSlug);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Check if anyone depends on this mod
|
|
1038
|
+
const dependentMods = findDependentMods(modToRemove);
|
|
1039
|
+
console.log(
|
|
1040
|
+
'[modsEditor] Removing mod:',
|
|
1041
|
+
modToRemove.name,
|
|
1042
|
+
'Dependent mods found:',
|
|
1043
|
+
dependentMods.length,
|
|
1044
|
+
dependentMods.map((m) => m.name),
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
if (dependentMods.length > 0) {
|
|
1048
|
+
// Show dialog to choose action
|
|
1049
|
+
console.log('[modsEditor] Showing dependency removal dialog');
|
|
1050
|
+
const action = await showDependencyRemovalDialog(
|
|
1051
|
+
modToRemove,
|
|
1052
|
+
dependentMods,
|
|
1053
|
+
);
|
|
1054
|
+
console.log('[modsEditor] User action:', action);
|
|
1055
|
+
|
|
1056
|
+
if (action === 'cancel') {
|
|
1057
|
+
return; // User cancelled
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (action === 'remove-all') {
|
|
1061
|
+
// Remove the mod and all dependent mods - use id (projectId) for comparison
|
|
1062
|
+
const toRemoveIds = new Set([
|
|
1063
|
+
modToRemove.id,
|
|
1064
|
+
...dependentMods.map((m) => m.id),
|
|
1065
|
+
]);
|
|
1066
|
+
|
|
1067
|
+
let remainingMods = allMods.filter((m) => !toRemoveIds.has(m.id));
|
|
1068
|
+
|
|
1069
|
+
// Also remove orphaned dependencies recursively for all removed mods
|
|
1070
|
+
const allRemovedMods = [modToRemove, ...dependentMods];
|
|
1071
|
+
const allOrphanedIds = new Set<string>();
|
|
1072
|
+
|
|
1073
|
+
for (const removedMod of allRemovedMods) {
|
|
1074
|
+
const orphaned = findOrphanedDependencies(
|
|
1075
|
+
removedMod,
|
|
1076
|
+
remainingMods,
|
|
1077
|
+
);
|
|
1078
|
+
for (const orphan of orphaned) {
|
|
1079
|
+
allOrphanedIds.add(orphan.id); // Use id (projectId) for comparison
|
|
1080
|
+
}
|
|
1081
|
+
// Update remaining mods after each check to avoid counting already-orphaned mods
|
|
1082
|
+
remainingMods = remainingMods.filter(
|
|
1083
|
+
(m) => !allOrphanedIds.has(m.id),
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Files will be deleted when manifest is saved
|
|
1088
|
+
manifest.value.mods = remainingMods;
|
|
1089
|
+
} else {
|
|
1090
|
+
// Remove only this mod (user chose to keep dependent mods, even if they might break)
|
|
1091
|
+
// Files will be deleted when manifest is saved
|
|
1092
|
+
manifest.value.mods = allMods.filter(
|
|
1093
|
+
(m) => m.id !== idOrSlug && m.projectSlug !== idOrSlug, // Support both id and slug for lookup
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
} else {
|
|
1097
|
+
// No one depends on this mod, safe to remove
|
|
1098
|
+
// Also remove orphaned dependencies
|
|
1099
|
+
const remainingMods = allMods.filter(
|
|
1100
|
+
(m) => m.id !== idOrSlug && m.projectSlug !== idOrSlug, // Support both id and slug for lookup
|
|
1101
|
+
);
|
|
1102
|
+
const orphaned = findOrphanedDependencies(modToRemove, remainingMods);
|
|
1103
|
+
const orphanedIds = new Set(orphaned.map((m) => m.id)); // Use id (projectId) for comparison
|
|
1104
|
+
|
|
1105
|
+
// Files will be deleted when manifest is saved
|
|
1106
|
+
manifest.value.mods = remainingMods.filter(
|
|
1107
|
+
(m) => !orphanedIds.has(m.id),
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
async function changeInstalledModVersion(mod: Mod) {
|
|
1113
|
+
if (mod.source !== 'modrinth' && mod.source !== 'curseforge') {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const baseInstallCtx: InstallContext = {
|
|
1118
|
+
projectType: 'mod',
|
|
1119
|
+
minecraftVersion:
|
|
1120
|
+
manifest.value?._meta?.manifest?.minecraftVersion || '',
|
|
1121
|
+
loader: manifest.value?._meta?.manifest?.loader || 'vanilla',
|
|
1122
|
+
};
|
|
1123
|
+
const installCtx = withMarketplaceLoaderIdsForProvider(
|
|
1124
|
+
baseInstallCtx,
|
|
1125
|
+
mod.source,
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
const prov = getModMarketplaceProvider(mod.source);
|
|
1129
|
+
if (!prov?.getInstallVersions) {
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const versions = await prov.getInstallVersions(mod.id, installCtx);
|
|
1134
|
+
if (!versions.length) {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
let selectedVersionId = versions.find((v) => v.label === mod.version)?.id;
|
|
1139
|
+
if (versions.length > 1) {
|
|
1140
|
+
selectedVersionId =
|
|
1141
|
+
(await showVersionSelectionDialog(
|
|
1142
|
+
{
|
|
1143
|
+
provider: mod.source,
|
|
1144
|
+
projectType: 'mod',
|
|
1145
|
+
projectId: mod.id,
|
|
1146
|
+
slug: mod.projectSlug || mod.id,
|
|
1147
|
+
name: mod.name,
|
|
1148
|
+
iconUrl: mod.image,
|
|
1149
|
+
},
|
|
1150
|
+
versions,
|
|
1151
|
+
)) ?? undefined;
|
|
1152
|
+
if (!selectedVersionId) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
await install(
|
|
1158
|
+
{
|
|
1159
|
+
provider: mod.source,
|
|
1160
|
+
projectType: 'mod',
|
|
1161
|
+
projectId: mod.id,
|
|
1162
|
+
slug: mod.projectSlug || mod.id,
|
|
1163
|
+
name: mod.name,
|
|
1164
|
+
iconUrl: mod.image,
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
skipVersionPrompt: true,
|
|
1168
|
+
selectedVersionId: selectedVersionId ?? versions[0].id,
|
|
1169
|
+
},
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function showVersionSelectionDialog(
|
|
1174
|
+
item: MarketplaceItem,
|
|
1175
|
+
versions: MarketplaceInstallVersion[],
|
|
1176
|
+
): Promise<string | null> {
|
|
1177
|
+
return new Promise((resolve) => {
|
|
1178
|
+
versionSelectionDialog.value = {
|
|
1179
|
+
open: true,
|
|
1180
|
+
item,
|
|
1181
|
+
versions,
|
|
1182
|
+
selectedVersionId:
|
|
1183
|
+
versions.find((v) => v.recommended)?.id ?? versions[0]?.id ?? null,
|
|
1184
|
+
resolveCallback: resolve,
|
|
1185
|
+
};
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function closeVersionSelectionDialog() {
|
|
1190
|
+
if (versionSelectionDialog.value.resolveCallback) {
|
|
1191
|
+
versionSelectionDialog.value.resolveCallback(null);
|
|
1192
|
+
}
|
|
1193
|
+
versionSelectionDialog.value = {
|
|
1194
|
+
open: false,
|
|
1195
|
+
item: null,
|
|
1196
|
+
versions: [],
|
|
1197
|
+
selectedVersionId: null,
|
|
1198
|
+
resolveCallback: null,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function confirmVersionSelectionDialog() {
|
|
1203
|
+
if (versionSelectionDialog.value.resolveCallback) {
|
|
1204
|
+
versionSelectionDialog.value.resolveCallback(
|
|
1205
|
+
versionSelectionDialog.value.selectedVersionId,
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
versionSelectionDialog.value = {
|
|
1209
|
+
open: false,
|
|
1210
|
+
item: null,
|
|
1211
|
+
versions: [],
|
|
1212
|
+
selectedVersionId: null,
|
|
1213
|
+
resolveCallback: null,
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
async function loadModDetails(item: MarketplaceItem) {
|
|
1218
|
+
isLoadingModDetails.value = true;
|
|
1219
|
+
modDetailsError.value = null;
|
|
1220
|
+
try {
|
|
1221
|
+
const prov = getModMarketplaceProvider(item.provider);
|
|
1222
|
+
if (!prov) {
|
|
1223
|
+
throw new Error(`Unknown provider: ${item.provider}`);
|
|
1224
|
+
}
|
|
1225
|
+
const details = await prov.getProjectDetails(item.projectId);
|
|
1226
|
+
selectedModDetails.value = details;
|
|
1227
|
+
} catch (e) {
|
|
1228
|
+
modDetailsError.value = e instanceof Error ? e.message : String(e);
|
|
1229
|
+
selectedModDetails.value = null;
|
|
1230
|
+
} finally {
|
|
1231
|
+
isLoadingModDetails.value = false;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
async function loadInstalledModDetails(mod: Mod) {
|
|
1236
|
+
isLoadingModDetails.value = true;
|
|
1237
|
+
modDetailsError.value = null;
|
|
1238
|
+
try {
|
|
1239
|
+
let details: ProjectDetails | null = null;
|
|
1240
|
+
const prov = getModMarketplaceProvider(mod.source);
|
|
1241
|
+
if (prov) {
|
|
1242
|
+
details = await prov.getProjectDetails(mod.id);
|
|
1243
|
+
}
|
|
1244
|
+
if (details) {
|
|
1245
|
+
selectedModDetails.value = details;
|
|
1246
|
+
} else {
|
|
1247
|
+
// For direct/local mods, create a basic ProjectDetails object
|
|
1248
|
+
selectedModDetails.value = {
|
|
1249
|
+
provider: mod.source as any,
|
|
1250
|
+
projectId: mod.id,
|
|
1251
|
+
slug: mod.projectSlug || mod.id,
|
|
1252
|
+
name: mod.name,
|
|
1253
|
+
summary: 'Локальный мод',
|
|
1254
|
+
iconUrl: mod.image || '',
|
|
1255
|
+
downloads: 0,
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
} catch (e) {
|
|
1259
|
+
modDetailsError.value = e instanceof Error ? e.message : String(e);
|
|
1260
|
+
selectedModDetails.value = null;
|
|
1261
|
+
} finally {
|
|
1262
|
+
isLoadingModDetails.value = false;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function clearModDetails() {
|
|
1267
|
+
selectedModDetails.value = null;
|
|
1268
|
+
modDetailsError.value = null;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Check for incompatibilities between a mod and installed mods
|
|
1273
|
+
* Uses slug for matching (as per new dependency format)
|
|
1274
|
+
* @param mod - The mod to check
|
|
1275
|
+
* @param installedModsList - Optional list of installed mods to check against (defaults to all installed mods)
|
|
1276
|
+
*/
|
|
1277
|
+
function checkIncompatibilities(
|
|
1278
|
+
mod: Mod,
|
|
1279
|
+
installedModsList?: Mod[],
|
|
1280
|
+
): { conflictingMod: Mod | null } {
|
|
1281
|
+
const installed = installedModsList ?? installedMods.value;
|
|
1282
|
+
|
|
1283
|
+
// Check if mod has incompatible dependencies
|
|
1284
|
+
const incompatibleDeps =
|
|
1285
|
+
mod.dependencies?.filter((d) => d.type === 'incompatible') ?? [];
|
|
1286
|
+
|
|
1287
|
+
for (const dep of incompatibleDeps) {
|
|
1288
|
+
// Find installed mod that matches this incompatible dependency
|
|
1289
|
+
// Match by slug (primary) or fallback to modId for backwards compatibility
|
|
1290
|
+
const conflicting = installed.find((installedMod) => {
|
|
1291
|
+
return (
|
|
1292
|
+
installedMod.projectSlug === dep.slug ||
|
|
1293
|
+
installedMod.id === dep.modId ||
|
|
1294
|
+
// Also check if installed mod's dependencies mark this mod as incompatible
|
|
1295
|
+
installedMod.dependencies?.some(
|
|
1296
|
+
(installedDep) =>
|
|
1297
|
+
installedDep.type === 'incompatible' &&
|
|
1298
|
+
(installedDep.slug === mod.projectSlug ||
|
|
1299
|
+
installedDep.modId === mod.id),
|
|
1300
|
+
)
|
|
1301
|
+
);
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
if (conflicting) {
|
|
1305
|
+
return { conflictingMod: conflicting };
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// Also check if any installed mod marks this mod as incompatible
|
|
1310
|
+
for (const installedMod of installed) {
|
|
1311
|
+
const hasIncompatible = installedMod.dependencies?.some(
|
|
1312
|
+
(dep) =>
|
|
1313
|
+
dep.type === 'incompatible' &&
|
|
1314
|
+
(dep.slug === mod.projectSlug || dep.modId === mod.id),
|
|
1315
|
+
);
|
|
1316
|
+
|
|
1317
|
+
if (hasIncompatible) {
|
|
1318
|
+
return { conflictingMod: installedMod };
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
return { conflictingMod: null };
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Resolve all required dependencies for a mod recursively
|
|
1327
|
+
* Uses modId (projectId) for API resolution, slug for checking if already installed
|
|
1328
|
+
*/
|
|
1329
|
+
async function resolveDependencies(
|
|
1330
|
+
mod: Mod,
|
|
1331
|
+
installCtx: InstallContext,
|
|
1332
|
+
resolved: Set<string> = new Set(),
|
|
1333
|
+
toInstall: Mod[] = [],
|
|
1334
|
+
): Promise<Mod[]> {
|
|
1335
|
+
const modKey = `${mod.source}:${mod.id}`;
|
|
1336
|
+
if (resolved.has(modKey)) {
|
|
1337
|
+
return toInstall; // Already resolved
|
|
1338
|
+
}
|
|
1339
|
+
resolved.add(modKey);
|
|
1340
|
+
|
|
1341
|
+
const requiredDeps =
|
|
1342
|
+
mod.dependencies?.filter((d) => d.type === 'required') ?? [];
|
|
1343
|
+
|
|
1344
|
+
for (const dep of requiredDeps) {
|
|
1345
|
+
// Check if dependency is already installed by slug (primary check)
|
|
1346
|
+
const isAlreadyInstalled = installedMods.value.some(
|
|
1347
|
+
(installedMod) =>
|
|
1348
|
+
installedMod.projectSlug === dep.slug ||
|
|
1349
|
+
installedMod.id === dep.modId, // Fallback for backwards compatibility
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
if (isAlreadyInstalled) {
|
|
1353
|
+
continue; // Skip already installed dependencies
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const depItem = await marketplaceItemForProvider(
|
|
1357
|
+
dep.source,
|
|
1358
|
+
dep.modId,
|
|
1359
|
+
installCtx.projectType,
|
|
1360
|
+
);
|
|
1361
|
+
|
|
1362
|
+
if (!depItem) {
|
|
1363
|
+
console.warn(
|
|
1364
|
+
`[modsEditor] Could not resolve dependency ${dep.modId} (slug: ${dep.slug}) from ${dep.source}`,
|
|
1365
|
+
);
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const depProv = getModMarketplaceProvider(depItem.provider);
|
|
1370
|
+
if (!depProv) {
|
|
1371
|
+
console.warn(
|
|
1372
|
+
`[modsEditor] No marketplace provider for dependency source ${depItem.provider}`,
|
|
1373
|
+
);
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const depInstallCtx = withMarketplaceLoaderIdsForProvider(
|
|
1378
|
+
installCtx,
|
|
1379
|
+
depItem.provider,
|
|
1380
|
+
);
|
|
1381
|
+
const depEntry = await depProv.install(depItem, depInstallCtx);
|
|
1382
|
+
const depMod = depEntry as Mod;
|
|
1383
|
+
|
|
1384
|
+
// Recursively resolve dependencies of this dependency
|
|
1385
|
+
await resolveDependencies(depMod, installCtx, resolved, toInstall);
|
|
1386
|
+
|
|
1387
|
+
toInstall.push(depMod);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
return toInstall;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Show incompatibility dialog and wait for user decision
|
|
1395
|
+
*/
|
|
1396
|
+
function showIncompatibilityDialog(
|
|
1397
|
+
incompatibleMod: Mod,
|
|
1398
|
+
conflictingMod: Mod,
|
|
1399
|
+
): Promise<boolean> {
|
|
1400
|
+
return new Promise((resolve) => {
|
|
1401
|
+
incompatibilityDialog.value = {
|
|
1402
|
+
open: true,
|
|
1403
|
+
incompatibleMod,
|
|
1404
|
+
conflictingMod,
|
|
1405
|
+
resolveCallback: resolve,
|
|
1406
|
+
};
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function handleIncompatibilityCancel() {
|
|
1411
|
+
if (incompatibilityDialog.value.resolveCallback) {
|
|
1412
|
+
incompatibilityDialog.value.resolveCallback(false);
|
|
1413
|
+
}
|
|
1414
|
+
incompatibilityDialog.value = {
|
|
1415
|
+
open: false,
|
|
1416
|
+
incompatibleMod: null,
|
|
1417
|
+
conflictingMod: null,
|
|
1418
|
+
resolveCallback: null,
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function handleIncompatibilityInstallAnyway() {
|
|
1423
|
+
if (incompatibilityDialog.value.resolveCallback) {
|
|
1424
|
+
incompatibilityDialog.value.resolveCallback(true);
|
|
1425
|
+
}
|
|
1426
|
+
incompatibilityDialog.value = {
|
|
1427
|
+
open: false,
|
|
1428
|
+
incompatibleMod: null,
|
|
1429
|
+
conflictingMod: null,
|
|
1430
|
+
resolveCallback: null,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
const activeSearchResults = computed(() =>
|
|
1435
|
+
getMarketplaceSearchResults(marketplaceTab.value),
|
|
1436
|
+
);
|
|
1437
|
+
|
|
1438
|
+
function isMarketplaceTabSearching(tabId: string): boolean {
|
|
1439
|
+
syncMarketplaceProviderSearchKeys();
|
|
1440
|
+
if (tabId === 'all') {
|
|
1441
|
+
return listModMarketplaceProviders().some(
|
|
1442
|
+
(p) => isSearchingByProvider[p.id],
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
return Boolean(isSearchingByProvider[tabId]);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function getMarketplaceSearchResults(tabId: string): MarketplaceItem[] {
|
|
1449
|
+
syncMarketplaceProviderSearchKeys();
|
|
1450
|
+
if (tabId === 'all') {
|
|
1451
|
+
const merged: MarketplaceItem[] = [];
|
|
1452
|
+
for (const p of listModMarketplaceProviders()) {
|
|
1453
|
+
merged.push(...(searchResultsByProvider[p.id] ?? []));
|
|
1454
|
+
}
|
|
1455
|
+
return merged;
|
|
1456
|
+
}
|
|
1457
|
+
return searchResultsByProvider[tabId] ?? [];
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// Debounced search function (wait 500ms after user stops typing)
|
|
1461
|
+
let debouncedSearch = debounce(() => {
|
|
1462
|
+
void search(true);
|
|
1463
|
+
}, 500);
|
|
1464
|
+
|
|
1465
|
+
// Store watcher stop function
|
|
1466
|
+
let stopWatcher: (() => void) | null = null;
|
|
1467
|
+
|
|
1468
|
+
// Auto-search when query changes (debounced) - will be recreated in init
|
|
1469
|
+
function setupSearchWatcher() {
|
|
1470
|
+
// Stop existing watcher if any
|
|
1471
|
+
if (stopWatcher) {
|
|
1472
|
+
stopWatcher();
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// Create new watcher
|
|
1476
|
+
stopWatcher = watch(searchQuery, () => {
|
|
1477
|
+
debouncedSearch();
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// Initial setup
|
|
1482
|
+
setupSearchWatcher();
|
|
1483
|
+
|
|
1484
|
+
watch(
|
|
1485
|
+
() => [
|
|
1486
|
+
manifest.value?._meta?.manifest?.minecraftVersion,
|
|
1487
|
+
manifest.value?._meta?.manifest?.loader,
|
|
1488
|
+
] as const,
|
|
1489
|
+
() => {
|
|
1490
|
+
if (!marketplaceBrowseStarted.value) return;
|
|
1491
|
+
void search(true);
|
|
1492
|
+
},
|
|
1493
|
+
);
|
|
1494
|
+
|
|
1495
|
+
/**
|
|
1496
|
+
* Scan for local mods and resource packs
|
|
1497
|
+
*/
|
|
1498
|
+
function scanLocalModsAndResourcePacks() {
|
|
1499
|
+
if (!manifest.value?.id) return;
|
|
1500
|
+
|
|
1501
|
+
// Get original manifest from cache (before any edits)
|
|
1502
|
+
const localBuilds = useLocalBuildsModel();
|
|
1503
|
+
const original = localBuilds.getManifestById(manifest.value.id);
|
|
1504
|
+
originalManifest.value = original;
|
|
1505
|
+
|
|
1506
|
+
// Scan local mods
|
|
1507
|
+
localMods.value = scanLocalMods(manifest.value.id, original);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* Toggle disabled state for a mod
|
|
1512
|
+
*/
|
|
1513
|
+
function toggleModDisabledState(mod: Mod, disabled: boolean) {
|
|
1514
|
+
if (!manifest.value?.id) return false;
|
|
1515
|
+
|
|
1516
|
+
// If it's a local mod, toggle the file
|
|
1517
|
+
if (mod.source === 'direct' && mod.id.startsWith('local-')) {
|
|
1518
|
+
const success = toggleModDisabled(manifest.value.id, mod, disabled);
|
|
1519
|
+
if (success) {
|
|
1520
|
+
// Update the mod's disabled state
|
|
1521
|
+
mod.disabled = disabled;
|
|
1522
|
+
// Update file path if needed
|
|
1523
|
+
if (mod.files.length > 0) {
|
|
1524
|
+
const file = mod.files[0];
|
|
1525
|
+
if (disabled) {
|
|
1526
|
+
file.path = file.path.replace(/\.jar$/, '.jar.disabled');
|
|
1527
|
+
} else {
|
|
1528
|
+
file.path = file.path.replace(/\.disabled$/, '');
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return success;
|
|
1533
|
+
} else {
|
|
1534
|
+
// For manifest mods, update the manifest AND rename the file
|
|
1535
|
+
if (!manifest.value.mods) return false;
|
|
1536
|
+
|
|
1537
|
+
const modIndex = manifest.value.mods.findIndex((m) => m.id === mod.id);
|
|
1538
|
+
if (modIndex >= 0) {
|
|
1539
|
+
// First, rename the file on disk
|
|
1540
|
+
const success = toggleModDisabled(manifest.value.id, mod, disabled);
|
|
1541
|
+
if (!success) return false;
|
|
1542
|
+
|
|
1543
|
+
// Then update the manifest
|
|
1544
|
+
manifest.value.mods[modIndex].disabled = disabled;
|
|
1545
|
+
// Also update file paths
|
|
1546
|
+
for (const file of manifest.value.mods[modIndex].files) {
|
|
1547
|
+
if (disabled) {
|
|
1548
|
+
if (!file.path.endsWith('.disabled')) {
|
|
1549
|
+
file.path = `${file.path}.disabled`;
|
|
1550
|
+
}
|
|
1551
|
+
if (!file.filename.endsWith('.disabled')) {
|
|
1552
|
+
file.filename = `${file.filename}.disabled`;
|
|
1553
|
+
}
|
|
1554
|
+
} else {
|
|
1555
|
+
file.path = file.path.replace(/\.disabled$/, '');
|
|
1556
|
+
file.filename = file.filename.replace(/\.disabled$/, '');
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return true;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* Delete a local mod
|
|
1567
|
+
*/
|
|
1568
|
+
function deleteLocalModFile(mod: Mod) {
|
|
1569
|
+
if (!manifest.value?.id) return false;
|
|
1570
|
+
|
|
1571
|
+
if (mod.source === 'direct' && mod.id.startsWith('local-')) {
|
|
1572
|
+
const success = deleteLocalMod(manifest.value.id, mod);
|
|
1573
|
+
if (success) {
|
|
1574
|
+
// Remove from local mods list
|
|
1575
|
+
localMods.value = localMods.value.filter((m) => m.id !== mod.id);
|
|
1576
|
+
}
|
|
1577
|
+
return success;
|
|
1578
|
+
}
|
|
1579
|
+
return false;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* Инициализация редактора (манифест уже передан при создании store)
|
|
1584
|
+
*/
|
|
1585
|
+
const init = async (_manifest: BuildVersionManifest | null) => {
|
|
1586
|
+
console.log('[modsEditor.init] Инициализация:', {
|
|
1587
|
+
hasValue: !!manifest.value,
|
|
1588
|
+
minecraftVersion: manifest.value?._meta?.manifest?.minecraftVersion,
|
|
1589
|
+
loader: manifest.value?._meta?.manifest?.loader,
|
|
1590
|
+
manifest,
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
// Reset search state when reinitializing
|
|
1594
|
+
searchQuery.value = '';
|
|
1595
|
+
searchError.value = null;
|
|
1596
|
+
isSearching.value = false;
|
|
1597
|
+
selectedModDetails.value = null;
|
|
1598
|
+
modDetailsError.value = null;
|
|
1599
|
+
syncMarketplaceProviderSearchKeys();
|
|
1600
|
+
for (const p of listModMarketplaceProviders()) {
|
|
1601
|
+
searchResultsByProvider[p.id] = [];
|
|
1602
|
+
isSearchingByProvider[p.id] = false;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
const validTabs = new Set<string>([
|
|
1606
|
+
'all',
|
|
1607
|
+
...listModMarketplaceProviders().map((p) => p.id),
|
|
1608
|
+
]);
|
|
1609
|
+
if (!validTabs.has(marketplaceTab.value)) {
|
|
1610
|
+
marketplaceTab.value = 'all';
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// Recreate watcher to ensure it works after reinitialization
|
|
1614
|
+
setupSearchWatcher();
|
|
1615
|
+
|
|
1616
|
+
// Browse marketplace with empty query
|
|
1617
|
+
void search(true);
|
|
1618
|
+
|
|
1619
|
+
// Scan local mods after initialization
|
|
1620
|
+
scanLocalModsAndResourcePacks();
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1623
|
+
return {
|
|
1624
|
+
// Mods editor API
|
|
1625
|
+
projectType,
|
|
1626
|
+
marketplaceTab,
|
|
1627
|
+
searchQuery,
|
|
1628
|
+
isSearching,
|
|
1629
|
+
searchError,
|
|
1630
|
+
activeSearchResults,
|
|
1631
|
+
isMarketplaceTabSearching,
|
|
1632
|
+
getMarketplaceSearchResults,
|
|
1633
|
+
installedMods,
|
|
1634
|
+
installedResourcePacks,
|
|
1635
|
+
allMods,
|
|
1636
|
+
allResourcePacks,
|
|
1637
|
+
localMods,
|
|
1638
|
+
localResourcePacks,
|
|
1639
|
+
isInstalledBySlug,
|
|
1640
|
+
selectedModDetails,
|
|
1641
|
+
isLoadingModDetails,
|
|
1642
|
+
modDetailsError,
|
|
1643
|
+
search,
|
|
1644
|
+
loadMoreMarketplaceResults,
|
|
1645
|
+
canLoadMoreMarketplace,
|
|
1646
|
+
isLoadingMoreMarketplace,
|
|
1647
|
+
marketplaceBrowseStarted,
|
|
1648
|
+
install,
|
|
1649
|
+
removeInstalled,
|
|
1650
|
+
loadModDetails,
|
|
1651
|
+
loadInstalledModDetails,
|
|
1652
|
+
clearModDetails,
|
|
1653
|
+
incompatibilityDialog,
|
|
1654
|
+
handleIncompatibilityCancel,
|
|
1655
|
+
handleIncompatibilityInstallAnyway,
|
|
1656
|
+
dependencyRemovalDialog,
|
|
1657
|
+
handleDependencyRemovalCancel,
|
|
1658
|
+
handleDependencyRemovalOnly,
|
|
1659
|
+
handleDependencyRemovalAll,
|
|
1660
|
+
modsDependencyStatus,
|
|
1661
|
+
dependencyIssuesDialog,
|
|
1662
|
+
openDependencyIssuesDialog,
|
|
1663
|
+
closeDependencyIssuesDialog,
|
|
1664
|
+
resolveIssue,
|
|
1665
|
+
resolveAllIssues,
|
|
1666
|
+
collectAllDependencyIssues,
|
|
1667
|
+
scanLocalModsAndResourcePacks,
|
|
1668
|
+
toggleModDisabledState,
|
|
1669
|
+
deleteLocalModFile,
|
|
1670
|
+
versionSelectionDialog,
|
|
1671
|
+
closeVersionSelectionDialog,
|
|
1672
|
+
confirmVersionSelectionDialog,
|
|
1673
|
+
changeInstalledModVersion,
|
|
1674
|
+
init,
|
|
1675
|
+
};
|
|
1676
|
+
},
|
|
1677
|
+
false,
|
|
1678
|
+
);
|