@aphexcms/cms-core 0.2.3 → 2.0.0
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/dist/api/api-keys.d.ts +37 -0
- package/dist/api/api-keys.d.ts.map +1 -0
- package/dist/api/api-keys.js +20 -0
- package/dist/api/assets.d.ts +27 -0
- package/dist/api/assets.d.ts.map +1 -1
- package/dist/api/assets.js +22 -1
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +4 -0
- package/dist/api/instance.d.ts +17 -0
- package/dist/api/instance.d.ts.map +1 -0
- package/dist/api/instance.js +21 -0
- package/dist/api/invitations.d.ts +37 -0
- package/dist/api/invitations.d.ts.map +1 -0
- package/dist/api/invitations.js +27 -0
- package/dist/api/organizations.d.ts +7 -0
- package/dist/api/organizations.d.ts.map +1 -1
- package/dist/api/organizations.js +7 -0
- package/dist/api/types.d.ts +1 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/user.d.ts +23 -0
- package/dist/api/user.d.ts.map +1 -0
- package/dist/api/user.js +20 -0
- package/dist/auth/auth-errors.d.ts +1 -1
- package/dist/auth/auth-errors.d.ts.map +1 -1
- package/dist/auth/auth-hooks.d.ts.map +1 -1
- package/dist/auth/auth-hooks.js +39 -23
- package/dist/auth/provider.d.ts +2 -2
- package/dist/auth/provider.d.ts.map +1 -1
- package/dist/cli/generate-types.d.ts +14 -0
- package/dist/cli/generate-types.d.ts.map +1 -0
- package/dist/cli/generate-types.js +15 -7
- package/dist/cli/generate-types.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/components/AdminApp.svelte +160 -63
- package/dist/components/AdminApp.svelte.d.ts +1 -1
- package/dist/components/AdminApp.svelte.d.ts.map +1 -1
- package/dist/components/admin/AdminLayout.svelte.d.ts +3 -3
- package/dist/components/admin/AssetBrowserModal.svelte +66 -0
- package/dist/components/admin/AssetBrowserModal.svelte.d.ts +15 -0
- package/dist/components/admin/AssetBrowserModal.svelte.d.ts.map +1 -0
- package/dist/components/admin/DocumentEditor.svelte +137 -69
- package/dist/components/admin/DocumentEditor.svelte.d.ts +1 -1
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
- package/dist/components/admin/DocumentsSkeleton.svelte +40 -0
- package/dist/components/admin/DocumentsSkeleton.svelte.d.ts +7 -0
- package/dist/components/admin/DocumentsSkeleton.svelte.d.ts.map +1 -0
- package/dist/components/admin/MediaBrowser.svelte +1398 -0
- package/dist/components/admin/MediaBrowser.svelte.d.ts +23 -0
- package/dist/components/admin/MediaBrowser.svelte.d.ts.map +1 -0
- package/dist/components/admin/ObjectModal.svelte +3 -4
- package/dist/components/admin/ObjectModal.svelte.d.ts +1 -1
- package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -1
- package/dist/components/admin/SchemaField.svelte +109 -81
- package/dist/components/admin/SchemaField.svelte.d.ts +1 -1
- package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ArrayField.svelte +611 -277
- package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/DateField.svelte +3 -2
- package/dist/components/admin/fields/DateField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/DateTimeField.svelte +3 -2
- package/dist/components/admin/fields/DateTimeField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ImageField.svelte +217 -120
- package/dist/components/admin/fields/ImageField.svelte.d.ts +1 -0
- package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ReferenceField.svelte +11 -6
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/StringField.svelte +2 -1
- package/dist/components/admin/fields/StringField.svelte.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/layout/OrganizationSwitcher.svelte +109 -45
- package/dist/components/layout/OrganizationSwitcher.svelte.d.ts.map +1 -1
- package/dist/components/layout/Sidebar.svelte +36 -14
- package/dist/components/layout/Sidebar.svelte.d.ts +2 -1
- package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte +1 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +8 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
- package/dist/components/layout/sidebar/NavMain.svelte +1 -1
- package/dist/components/layout/sidebar/NavMain.svelte.d.ts +1 -1
- package/dist/components/layout/sidebar/NavMain.svelte.d.ts.map +1 -1
- package/dist/components/layout/sidebar/NavSecondary.svelte +3 -3
- package/dist/components/layout/sidebar/NavUser.svelte +22 -10
- package/dist/components/layout/sidebar/NavUser.svelte.d.ts +2 -2
- package/dist/components/layout/sidebar/NavUser.svelte.d.ts.map +1 -1
- package/dist/db/interfaces/document.d.ts +20 -0
- package/dist/db/interfaces/document.d.ts.map +1 -1
- package/dist/db/interfaces/index.d.ts +3 -1
- package/dist/db/interfaces/index.d.ts.map +1 -1
- package/dist/db/interfaces/instance.d.ts +7 -0
- package/dist/db/interfaces/instance.d.ts.map +1 -0
- package/dist/db/interfaces/instance.js +1 -0
- package/dist/db/interfaces/organization.d.ts +1 -0
- package/dist/db/interfaces/organization.d.ts.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -3
- package/dist/field-validation/date-utils.d.ts.map +1 -1
- package/dist/field-validation/date-utils.js +12 -11
- package/dist/field-validation/rule.d.ts.map +1 -1
- package/dist/field-validation/rule.js +11 -10
- package/dist/field-validation/utils.d.ts.map +1 -1
- package/dist/field-validation/utils.js +16 -15
- package/dist/graphql/index.d.ts +23 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +85 -0
- package/dist/graphql/resolvers.d.ts +4 -0
- package/dist/graphql/resolvers.d.ts.map +1 -0
- package/dist/graphql/resolvers.js +542 -0
- package/dist/graphql/schema.d.ts +3 -0
- package/dist/graphql/schema.d.ts.map +1 -0
- package/dist/graphql/schema.js +356 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +62 -9
- package/dist/lib/api/api-keys.d.ts +37 -0
- package/dist/lib/api/api-keys.d.ts.map +1 -0
- package/dist/lib/api/api-keys.js +21 -0
- package/dist/lib/api/api-keys.js.map +1 -0
- package/dist/lib/api/assets.d.ts +75 -0
- package/dist/lib/api/assets.d.ts.map +1 -0
- package/dist/lib/api/assets.js +74 -0
- package/dist/lib/api/assets.js.map +1 -0
- package/dist/lib/api/client.d.ts +37 -0
- package/dist/lib/api/client.d.ts.map +1 -0
- package/dist/lib/api/client.js +132 -0
- package/dist/lib/api/client.js.map +1 -0
- package/dist/lib/api/documents.d.ts +57 -0
- package/dist/lib/api/documents.d.ts.map +1 -0
- package/dist/lib/api/documents.js +86 -0
- package/dist/lib/api/documents.js.map +1 -0
- package/dist/lib/api/index.d.ts +15 -0
- package/dist/lib/api/index.d.ts.map +1 -0
- package/dist/lib/api/index.js +10 -0
- package/dist/lib/api/index.js.map +1 -0
- package/dist/lib/api/instance.d.ts +17 -0
- package/dist/lib/api/instance.d.ts.map +1 -0
- package/dist/lib/api/instance.js +22 -0
- package/dist/lib/api/instance.js.map +1 -0
- package/dist/lib/api/invitations.d.ts +37 -0
- package/dist/lib/api/invitations.d.ts.map +1 -0
- package/dist/lib/api/invitations.js +28 -0
- package/dist/lib/api/invitations.js.map +1 -0
- package/dist/lib/api/organizations.d.ts +108 -0
- package/dist/lib/api/organizations.d.ts.map +1 -0
- package/dist/lib/api/organizations.js +100 -0
- package/dist/lib/api/organizations.js.map +1 -0
- package/dist/lib/api/types.d.ts +47 -0
- package/dist/lib/api/types.d.ts.map +1 -0
- package/dist/lib/api/types.js +2 -0
- package/dist/lib/api/types.js.map +1 -0
- package/dist/lib/api/user.d.ts +23 -0
- package/dist/lib/api/user.d.ts.map +1 -0
- package/dist/lib/api/user.js +21 -0
- package/dist/lib/api/user.js.map +1 -0
- package/dist/lib/auth/auth-errors.d.ts +7 -0
- package/dist/lib/auth/auth-errors.d.ts.map +1 -0
- package/dist/lib/auth/auth-errors.js +14 -0
- package/dist/lib/auth/auth-errors.js.map +1 -0
- package/dist/lib/auth/auth-hooks.d.ts +6 -0
- package/dist/lib/auth/auth-hooks.d.ts.map +1 -0
- package/dist/lib/auth/auth-hooks.js +139 -0
- package/dist/lib/auth/auth-hooks.js.map +1 -0
- package/dist/lib/auth/provider.d.ts +17 -0
- package/dist/lib/auth/provider.d.ts.map +1 -0
- package/dist/lib/auth/provider.js +1 -0
- package/dist/lib/auth/provider.js.map +1 -0
- package/dist/lib/client/index.d.ts +24 -0
- package/dist/lib/client/index.d.ts.map +1 -0
- package/dist/lib/client/index.js +33 -0
- package/dist/lib/client/index.js.map +1 -0
- package/dist/lib/components/fields/index.d.ts +9 -0
- package/dist/lib/components/fields/index.d.ts.map +1 -0
- package/dist/lib/components/fields/index.js +10 -0
- package/dist/lib/components/fields/index.js.map +1 -0
- package/dist/lib/components/index.d.ts +8 -0
- package/dist/lib/components/index.d.ts.map +1 -0
- package/dist/lib/components/index.js +14 -0
- package/dist/lib/components/index.js.map +1 -0
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +16 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/db/adapters/index.d.ts +1 -0
- package/dist/lib/db/adapters/index.d.ts.map +1 -0
- package/dist/lib/db/adapters/index.js +5 -0
- package/dist/lib/db/adapters/index.js.map +1 -0
- package/dist/lib/db/index.d.ts +2 -0
- package/dist/lib/db/index.d.ts.map +1 -0
- package/dist/lib/db/index.js +1 -0
- package/dist/lib/db/index.js.map +1 -0
- package/dist/lib/db/interfaces/asset.d.ts +73 -0
- package/dist/lib/db/interfaces/asset.d.ts.map +1 -0
- package/dist/lib/db/interfaces/asset.js +1 -0
- package/dist/lib/db/interfaces/asset.js.map +1 -0
- package/dist/lib/db/interfaces/document.d.ts +79 -0
- package/dist/lib/db/interfaces/document.d.ts.map +1 -0
- package/dist/lib/db/interfaces/document.js +1 -0
- package/dist/lib/db/interfaces/document.js.map +1 -0
- package/dist/lib/db/interfaces/index.d.ts +76 -0
- package/dist/lib/db/interfaces/index.d.ts.map +1 -0
- package/dist/lib/db/interfaces/index.js +1 -0
- package/dist/lib/db/interfaces/index.js.map +1 -0
- package/dist/lib/db/interfaces/instance.d.ts +7 -0
- package/dist/lib/db/interfaces/instance.d.ts.map +1 -0
- package/dist/lib/db/interfaces/instance.js +2 -0
- package/dist/lib/db/interfaces/instance.js.map +1 -0
- package/dist/lib/db/interfaces/organization.d.ts +28 -0
- package/dist/lib/db/interfaces/organization.d.ts.map +1 -0
- package/dist/lib/db/interfaces/organization.js +1 -0
- package/dist/lib/db/interfaces/organization.js.map +1 -0
- package/dist/lib/db/interfaces/schema.d.ts +21 -0
- package/dist/lib/db/interfaces/schema.d.ts.map +1 -0
- package/dist/lib/db/interfaces/schema.js +1 -0
- package/dist/lib/db/interfaces/schema.js.map +1 -0
- package/dist/lib/db/interfaces/user.d.ts +17 -0
- package/dist/lib/db/interfaces/user.d.ts.map +1 -0
- package/dist/lib/db/interfaces/user.js +1 -0
- package/dist/lib/db/interfaces/user.js.map +1 -0
- package/dist/lib/db/utils/reference-resolver.d.ts +18 -0
- package/dist/lib/db/utils/reference-resolver.d.ts.map +1 -0
- package/dist/lib/db/utils/reference-resolver.js +81 -0
- package/dist/lib/db/utils/reference-resolver.js.map +1 -0
- package/dist/lib/define.d.ts +3 -0
- package/dist/lib/define.d.ts.map +1 -0
- package/dist/lib/define.js +5 -0
- package/dist/lib/define.js.map +1 -0
- package/dist/lib/email/index.d.ts +2 -0
- package/dist/lib/email/index.d.ts.map +1 -0
- package/dist/lib/email/index.js +1 -0
- package/dist/lib/email/index.js.map +1 -0
- package/dist/lib/email/interfaces/email.d.ts +42 -0
- package/dist/lib/email/interfaces/email.d.ts.map +1 -0
- package/dist/lib/email/interfaces/email.js +1 -0
- package/dist/lib/email/interfaces/email.js.map +1 -0
- package/dist/lib/engine.d.ts +26 -0
- package/dist/lib/engine.d.ts.map +1 -0
- package/dist/lib/engine.js +71 -0
- package/dist/lib/engine.js.map +1 -0
- package/dist/lib/field-validation/date-utils.d.ts +30 -0
- package/dist/lib/field-validation/date-utils.d.ts.map +1 -0
- package/dist/lib/field-validation/date-utils.js +13 -11
- package/dist/lib/field-validation/date-utils.js.map +1 -0
- package/dist/lib/field-validation/rule.d.ts +55 -0
- package/dist/lib/field-validation/rule.d.ts.map +1 -0
- package/dist/lib/field-validation/rule.js +12 -10
- package/dist/lib/field-validation/rule.js.map +1 -0
- package/dist/lib/field-validation/utils.d.ts +43 -0
- package/dist/lib/field-validation/utils.d.ts.map +1 -0
- package/dist/lib/field-validation/utils.js +17 -15
- package/dist/lib/field-validation/utils.js.map +1 -0
- package/dist/lib/graphql/index.d.ts +23 -0
- package/dist/lib/graphql/index.d.ts.map +1 -0
- package/dist/lib/graphql/index.js +86 -0
- package/dist/lib/graphql/index.js.map +1 -0
- package/dist/lib/graphql/resolvers.d.ts +4 -0
- package/dist/lib/graphql/resolvers.d.ts.map +1 -0
- package/dist/lib/graphql/resolvers.js +543 -0
- package/dist/lib/graphql/resolvers.js.map +1 -0
- package/dist/lib/graphql/schema.d.ts +3 -0
- package/dist/lib/graphql/schema.d.ts.map +1 -0
- package/dist/lib/graphql/schema.js +357 -0
- package/dist/lib/graphql/schema.js.map +1 -0
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/hooks.js +235 -0
- package/dist/lib/hooks.js.map +1 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/is-mobile.svelte.d.ts +5 -0
- package/dist/lib/is-mobile.svelte.d.ts.map +1 -0
- package/dist/lib/is-mobile.svelte.js +8 -0
- package/dist/lib/is-mobile.svelte.js.map +1 -0
- package/dist/lib/local-api/auth-helpers.d.ts +65 -0
- package/dist/lib/local-api/auth-helpers.d.ts.map +1 -0
- package/dist/lib/local-api/auth-helpers.js +103 -0
- package/dist/lib/local-api/auth-helpers.js.map +1 -0
- package/dist/lib/local-api/collection-api.d.ts +150 -0
- package/dist/lib/local-api/collection-api.d.ts.map +1 -0
- package/dist/lib/local-api/collection-api.js +311 -0
- package/dist/lib/local-api/collection-api.js.map +1 -0
- package/dist/lib/local-api/index.d.ts +108 -0
- package/dist/lib/local-api/index.d.ts.map +1 -0
- package/dist/lib/local-api/index.js +158 -0
- package/dist/lib/local-api/index.js.map +1 -0
- package/dist/lib/local-api/permissions.d.ts +45 -0
- package/dist/lib/local-api/permissions.d.ts.map +1 -0
- package/dist/lib/local-api/permissions.js +117 -0
- package/dist/lib/local-api/permissions.js.map +1 -0
- package/dist/lib/local-api/types.d.ts +65 -0
- package/dist/lib/local-api/types.d.ts.map +1 -0
- package/dist/lib/local-api/types.js +5 -0
- package/dist/lib/local-api/types.js.map +1 -0
- package/dist/lib/routes/assets-bulk.d.ts +3 -0
- package/dist/lib/routes/assets-bulk.d.ts.map +1 -0
- package/dist/lib/routes/assets-bulk.js +49 -0
- package/dist/lib/routes/assets-bulk.js.map +1 -0
- package/dist/lib/routes/assets-by-id.d.ts +5 -0
- package/dist/lib/routes/assets-by-id.d.ts.map +1 -0
- package/dist/lib/routes/assets-by-id.js +106 -0
- package/dist/lib/routes/assets-by-id.js.map +1 -0
- package/dist/lib/routes/assets-cdn.d.ts +3 -0
- package/dist/lib/routes/assets-cdn.d.ts.map +1 -0
- package/dist/lib/routes/assets-cdn.js +125 -0
- package/dist/lib/routes/assets-cdn.js.map +1 -0
- package/dist/lib/routes/assets-references-counts.d.ts +7 -0
- package/dist/lib/routes/assets-references-counts.d.ts.map +1 -0
- package/dist/lib/routes/assets-references-counts.js +32 -0
- package/dist/lib/routes/assets-references-counts.js.map +1 -0
- package/dist/lib/routes/assets-references.d.ts +7 -0
- package/dist/lib/routes/assets-references.d.ts.map +1 -0
- package/dist/lib/routes/assets-references.js +35 -0
- package/dist/lib/routes/assets-references.js.map +1 -0
- package/dist/lib/routes/assets.d.ts +4 -0
- package/dist/lib/routes/assets.d.ts.map +1 -0
- package/dist/lib/routes/assets.js +121 -0
- package/dist/lib/routes/assets.js.map +1 -0
- package/dist/lib/routes/documents-by-id.d.ts +5 -0
- package/dist/lib/routes/documents-by-id.d.ts.map +1 -0
- package/dist/lib/routes/documents-by-id.js +176 -0
- package/dist/lib/routes/documents-by-id.js.map +1 -0
- package/dist/lib/routes/documents-publish.d.ts +4 -0
- package/dist/lib/routes/documents-publish.d.ts.map +1 -0
- package/dist/lib/routes/documents-publish.js +138 -0
- package/dist/lib/routes/documents-publish.js.map +1 -0
- package/dist/lib/routes/documents-query.d.ts +26 -0
- package/dist/lib/routes/documents-query.d.ts.map +1 -0
- package/dist/lib/routes/documents-query.js +101 -0
- package/dist/lib/routes/documents-query.js.map +1 -0
- package/dist/lib/routes/documents.d.ts +4 -0
- package/dist/lib/routes/documents.d.ts.map +1 -0
- package/dist/lib/routes/documents.js +154 -0
- package/dist/lib/routes/documents.js.map +1 -0
- package/dist/lib/routes/index.d.ts +9 -0
- package/dist/lib/routes/index.d.ts.map +1 -0
- package/dist/lib/routes/index.js +15 -0
- package/dist/lib/routes/index.js.map +1 -0
- package/dist/lib/routes/organizations-by-id.d.ts +5 -0
- package/dist/lib/routes/organizations-by-id.d.ts.map +1 -0
- package/dist/lib/routes/organizations-by-id.js +189 -0
- package/dist/lib/routes/organizations-by-id.js.map +1 -0
- package/dist/lib/routes/organizations-invitations.d.ts +4 -0
- package/dist/lib/routes/organizations-invitations.d.ts.map +1 -0
- package/dist/lib/routes/organizations-invitations.js +127 -0
- package/dist/lib/routes/organizations-invitations.js.map +1 -0
- package/dist/lib/routes/organizations-members.d.ts +5 -0
- package/dist/lib/routes/organizations-members.d.ts.map +1 -0
- package/dist/lib/routes/organizations-members.js +208 -0
- package/dist/lib/routes/organizations-members.js.map +1 -0
- package/dist/lib/routes/organizations-switch.d.ts +3 -0
- package/dist/lib/routes/organizations-switch.d.ts.map +1 -0
- package/dist/lib/routes/organizations-switch.js +55 -0
- package/dist/lib/routes/organizations-switch.js.map +1 -0
- package/dist/lib/routes/organizations.d.ts +4 -0
- package/dist/lib/routes/organizations.d.ts.map +1 -0
- package/dist/lib/routes/organizations.js +111 -0
- package/dist/lib/routes/organizations.js.map +1 -0
- package/dist/lib/routes/schemas-by-type.d.ts +3 -0
- package/dist/lib/routes/schemas-by-type.d.ts.map +1 -0
- package/dist/lib/routes/schemas-by-type.js +27 -0
- package/dist/lib/routes/schemas-by-type.js.map +1 -0
- package/dist/lib/routes/schemas.d.ts +3 -0
- package/dist/lib/routes/schemas.d.ts.map +1 -0
- package/dist/lib/routes/schemas.js +12 -0
- package/dist/lib/routes/schemas.js.map +1 -0
- package/dist/lib/routes/user-preferences.d.ts +4 -0
- package/dist/lib/routes/user-preferences.d.ts.map +1 -0
- package/dist/lib/routes/user-preferences.js +79 -0
- package/dist/lib/routes/user-preferences.js.map +1 -0
- package/dist/lib/routes-exports.d.ts +17 -0
- package/dist/lib/routes-exports.d.ts.map +1 -0
- package/dist/lib/routes-exports.js +23 -0
- package/dist/lib/routes-exports.js.map +1 -0
- package/dist/lib/schema/index.d.ts +6 -0
- package/dist/lib/schema/index.d.ts.map +1 -0
- package/dist/lib/schema/index.js +12 -0
- package/dist/lib/schema/index.js.map +1 -0
- package/dist/lib/schema-context.svelte.d.ts +10 -0
- package/dist/lib/schema-context.svelte.d.ts.map +1 -0
- package/dist/lib/schema-context.svelte.js +19 -0
- package/dist/lib/schema-context.svelte.js.map +1 -0
- package/dist/lib/schema-utils/cleanup.d.ts +21 -0
- package/dist/lib/schema-utils/cleanup.d.ts.map +1 -0
- package/dist/lib/schema-utils/cleanup.js +81 -0
- package/dist/lib/schema-utils/cleanup.js.map +1 -0
- package/dist/lib/schema-utils/index.d.ts +4 -0
- package/dist/lib/schema-utils/index.d.ts.map +1 -0
- package/dist/lib/schema-utils/index.js +5 -0
- package/dist/lib/schema-utils/index.js.map +1 -0
- package/dist/lib/schema-utils/utils.d.ts +34 -0
- package/dist/lib/schema-utils/utils.d.ts.map +1 -0
- package/dist/lib/schema-utils/utils.js +59 -0
- package/dist/lib/schema-utils/utils.js.map +1 -0
- package/dist/lib/schema-utils/validator.d.ts +10 -0
- package/dist/lib/schema-utils/validator.d.ts.map +1 -0
- package/dist/lib/schema-utils/validator.js +167 -0
- package/dist/lib/schema-utils/validator.js.map +1 -0
- package/dist/lib/server/index.d.ts +19 -0
- package/dist/lib/server/index.d.ts.map +1 -0
- package/dist/lib/server/index.js +35 -0
- package/dist/lib/server/index.js.map +1 -0
- package/dist/lib/services/asset-service.d.ts +86 -0
- package/dist/lib/services/asset-service.d.ts.map +1 -0
- package/dist/lib/services/asset-service.js +189 -0
- package/dist/lib/services/asset-service.js.map +1 -0
- package/dist/lib/services/index.d.ts +3 -0
- package/dist/lib/services/index.d.ts.map +1 -0
- package/dist/lib/services/index.js +5 -0
- package/dist/lib/services/index.js.map +1 -0
- package/dist/lib/storage/adapters/index.d.ts +2 -0
- package/dist/lib/storage/adapters/index.d.ts.map +1 -0
- package/dist/lib/storage/adapters/index.js +3 -0
- package/dist/lib/storage/adapters/index.js.map +1 -0
- package/dist/lib/storage/adapters/local-storage-adapter.d.ts +54 -0
- package/dist/lib/storage/adapters/local-storage-adapter.d.ts.map +1 -0
- package/dist/lib/storage/adapters/local-storage-adapter.js +189 -0
- package/dist/lib/storage/adapters/local-storage-adapter.js.map +1 -0
- package/dist/lib/storage/index.d.ts +3 -0
- package/dist/lib/storage/index.d.ts.map +1 -0
- package/dist/lib/storage/index.js +7 -0
- package/dist/lib/storage/index.js.map +1 -0
- package/dist/lib/storage/interfaces/index.d.ts +2 -0
- package/dist/lib/storage/interfaces/index.d.ts.map +1 -0
- package/dist/lib/storage/interfaces/index.js +1 -0
- package/dist/lib/storage/interfaces/index.js.map +1 -0
- package/dist/lib/storage/interfaces/storage.d.ts +91 -0
- package/dist/lib/storage/interfaces/storage.d.ts.map +1 -0
- package/dist/lib/storage/interfaces/storage.js +1 -0
- package/dist/lib/storage/interfaces/storage.js.map +1 -0
- package/dist/lib/storage/providers/storage.d.ts +43 -0
- package/dist/lib/storage/providers/storage.d.ts.map +1 -0
- package/dist/lib/storage/providers/storage.js +65 -0
- package/dist/lib/storage/providers/storage.js.map +1 -0
- package/dist/lib/types/asset.d.ts +73 -0
- package/dist/lib/types/asset.d.ts.map +1 -0
- package/dist/lib/types/asset.js +1 -0
- package/dist/lib/types/asset.js.map +1 -0
- package/dist/lib/types/auth.d.ts +62 -0
- package/dist/lib/types/auth.d.ts.map +1 -0
- package/dist/lib/types/auth.js +7 -5
- package/dist/lib/types/auth.js.map +1 -0
- package/dist/lib/types/config.d.ts +77 -0
- package/dist/lib/types/config.d.ts.map +1 -0
- package/dist/lib/types/config.js +1 -0
- package/dist/lib/types/config.js.map +1 -0
- package/dist/lib/types/document.d.ts +35 -0
- package/dist/lib/types/document.d.ts.map +1 -0
- package/dist/lib/types/document.js +1 -0
- package/dist/lib/types/document.js.map +1 -0
- package/dist/lib/types/filters.d.ts +186 -0
- package/dist/lib/types/filters.d.ts.map +1 -0
- package/dist/lib/types/filters.js +1 -0
- package/dist/lib/types/filters.js.map +1 -0
- package/dist/lib/types/index.d.ts +11 -0
- package/dist/lib/types/index.d.ts.map +1 -0
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/index.js.map +1 -0
- package/dist/lib/types/instance.d.ts +5 -0
- package/dist/lib/types/instance.d.ts.map +1 -0
- package/dist/lib/types/instance.js +3 -0
- package/dist/lib/types/instance.js.map +1 -0
- package/dist/lib/types/organization.d.ts +108 -0
- package/dist/lib/types/organization.d.ts.map +1 -0
- package/dist/lib/types/organization.js +1 -0
- package/dist/lib/types/organization.js.map +1 -0
- package/dist/lib/types/schemas.d.ts +179 -0
- package/dist/lib/types/schemas.d.ts.map +1 -0
- package/dist/lib/types/schemas.js +1 -0
- package/dist/lib/types/schemas.js.map +1 -0
- package/dist/lib/types/sidebar.d.ts +34 -0
- package/dist/lib/types/sidebar.d.ts.map +1 -0
- package/dist/lib/types/sidebar.js +1 -0
- package/dist/lib/types/sidebar.js.map +1 -0
- package/dist/lib/types/user.d.ts +14 -0
- package/dist/lib/types/user.d.ts.map +1 -0
- package/dist/lib/types/user.js +1 -0
- package/dist/lib/types/user.js.map +1 -0
- package/dist/lib/utils/asset-actions.d.ts +9 -0
- package/dist/lib/utils/asset-actions.d.ts.map +1 -0
- package/dist/lib/utils/asset-actions.js +28 -0
- package/dist/lib/utils/asset-actions.js.map +1 -0
- package/dist/lib/utils/content-hash.d.ts +22 -0
- package/dist/lib/utils/content-hash.d.ts.map +1 -0
- package/dist/lib/utils/content-hash.js +68 -0
- package/dist/lib/utils/content-hash.js.map +1 -0
- package/dist/lib/utils/default-orderings.d.ts +10 -0
- package/dist/lib/utils/default-orderings.d.ts.map +1 -0
- package/dist/lib/utils/default-orderings.js +64 -0
- package/dist/lib/utils/default-orderings.js.map +1 -0
- package/dist/lib/utils/element-events.d.ts +15 -0
- package/dist/lib/utils/element-events.d.ts.map +1 -0
- package/dist/lib/utils/element-events.js +17 -0
- package/dist/lib/utils/element-events.js.map +1 -0
- package/dist/lib/utils/field-defaults.d.ts +8 -0
- package/dist/lib/utils/field-defaults.d.ts.map +1 -0
- package/dist/lib/utils/field-defaults.js +21 -0
- package/dist/lib/utils/field-defaults.js.map +1 -0
- package/dist/lib/utils/image-url.d.ts +88 -0
- package/dist/lib/utils/image-url.d.ts.map +1 -0
- package/dist/lib/utils/image-url.js +167 -0
- package/dist/lib/utils/image-url.js.map +1 -0
- package/dist/lib/utils/index.d.ts +8 -0
- package/dist/lib/utils/index.d.ts.map +1 -0
- package/dist/lib/utils/index.js +13 -0
- package/dist/lib/utils/index.js.map +1 -0
- package/dist/lib/utils/initial-value-helpers.d.ts +50 -0
- package/dist/lib/utils/initial-value-helpers.d.ts.map +1 -0
- package/dist/lib/utils/initial-value-helpers.js +71 -0
- package/dist/lib/utils/initial-value-helpers.js.map +1 -0
- package/dist/lib/utils/logger.d.ts +9 -0
- package/dist/lib/utils/logger.d.ts.map +1 -0
- package/dist/lib/utils/logger.js +30 -0
- package/dist/lib/utils/logger.js.map +1 -0
- package/dist/lib/utils/slug.d.ts +13 -0
- package/dist/lib/utils/slug.d.ts.map +1 -0
- package/dist/lib/utils/slug.js +31 -0
- package/dist/lib/utils/slug.js.map +1 -0
- package/dist/lib/utils.d.ts +13 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/local-api/index.d.ts.map +1 -1
- package/dist/local-api/permissions.d.ts.map +1 -1
- package/dist/local-api/permissions.js +3 -4
- package/dist/routes/assets-bulk.d.ts +3 -0
- package/dist/routes/assets-bulk.d.ts.map +1 -0
- package/dist/routes/assets-bulk.js +48 -0
- package/dist/routes/assets-by-id.d.ts.map +1 -1
- package/dist/routes/assets-by-id.js +22 -55
- package/dist/routes/assets-cdn.d.ts.map +1 -1
- package/dist/routes/assets-cdn.js +12 -50
- package/dist/routes/assets-references-counts.d.ts +7 -0
- package/dist/routes/assets-references-counts.d.ts.map +1 -0
- package/dist/routes/assets-references-counts.js +31 -0
- package/dist/routes/assets-references.d.ts +7 -0
- package/dist/routes/assets-references.d.ts.map +1 -0
- package/dist/routes/assets-references.js +34 -0
- package/dist/routes/assets.d.ts.map +1 -1
- package/dist/routes/assets.js +27 -6
- package/dist/routes/documents-by-id.d.ts.map +1 -1
- package/dist/routes/documents-by-id.js +4 -3
- package/dist/routes/documents-publish.d.ts.map +1 -1
- package/dist/routes/documents-publish.js +3 -2
- package/dist/routes/documents-query.d.ts.map +1 -1
- package/dist/routes/documents-query.js +2 -1
- package/dist/routes/documents.d.ts.map +1 -1
- package/dist/routes/documents.js +3 -2
- package/dist/routes/organizations-by-id.d.ts.map +1 -1
- package/dist/routes/organizations-by-id.js +4 -3
- package/dist/routes/organizations-invitations.d.ts.map +1 -1
- package/dist/routes/organizations-invitations.js +5 -4
- package/dist/routes/organizations-members.d.ts.map +1 -1
- package/dist/routes/organizations-members.js +7 -6
- package/dist/routes/organizations-switch.d.ts.map +1 -1
- package/dist/routes/organizations-switch.js +2 -1
- package/dist/routes/organizations.d.ts.map +1 -1
- package/dist/routes/organizations.js +3 -2
- package/dist/routes/schemas-by-type.d.ts.map +1 -1
- package/dist/routes/schemas-by-type.js +3 -2
- package/dist/routes/user-preferences.d.ts.map +1 -1
- package/dist/routes/user-preferences.js +3 -2
- package/dist/routes-exports.d.ts +3 -0
- package/dist/routes-exports.d.ts.map +1 -1
- package/dist/routes-exports.js +3 -0
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +11 -0
- package/dist/schema-utils/validator.d.ts.map +1 -1
- package/dist/schema-utils/validator.js +4 -3
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -0
- package/dist/services/asset-service.d.ts.map +1 -1
- package/dist/services/asset-service.js +8 -7
- package/dist/storage/adapters/local-storage-adapter.d.ts.map +1 -1
- package/dist/storage/adapters/local-storage-adapter.js +5 -4
- package/dist/types/auth.d.ts +13 -1
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/auth.js +6 -5
- package/dist/types/config.d.ts +14 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/document.d.ts +1 -1
- package/dist/types/document.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/instance.d.ts +5 -0
- package/dist/types/instance.d.ts.map +1 -0
- package/dist/types/instance.js +2 -0
- package/dist/types/schemas.d.ts +1 -1
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/sidebar.d.ts +1 -0
- package/dist/types/sidebar.d.ts.map +1 -1
- package/dist/utils/asset-actions.d.ts +9 -0
- package/dist/utils/asset-actions.d.ts.map +1 -0
- package/dist/utils/asset-actions.js +27 -0
- package/dist/utils/element-events.d.ts +15 -0
- package/dist/utils/element-events.d.ts.map +1 -0
- package/dist/utils/element-events.js +16 -0
- package/dist/utils/image-url.d.ts.map +1 -1
- package/dist/utils/image-url.js +10 -9
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +29 -0
- package/package.json +69 -36
|
@@ -0,0 +1,1398 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
3
|
+
import { Input } from '@aphexcms/ui/shadcn/input';
|
|
4
|
+
import { Label } from '@aphexcms/ui/shadcn/label';
|
|
5
|
+
import { Separator } from '@aphexcms/ui/shadcn/separator';
|
|
6
|
+
import { Checkbox } from '@aphexcms/ui/shadcn/checkbox';
|
|
7
|
+
import * as Dialog from '@aphexcms/ui/shadcn/dialog';
|
|
8
|
+
import {
|
|
9
|
+
Upload,
|
|
10
|
+
Search,
|
|
11
|
+
Grid3x3,
|
|
12
|
+
List,
|
|
13
|
+
ArrowDownUp,
|
|
14
|
+
X,
|
|
15
|
+
Trash2,
|
|
16
|
+
Image as ImageIcon,
|
|
17
|
+
FileText,
|
|
18
|
+
FileImage,
|
|
19
|
+
ChevronLeft,
|
|
20
|
+
ChevronRight,
|
|
21
|
+
Download,
|
|
22
|
+
Link,
|
|
23
|
+
CheckCircle2,
|
|
24
|
+
AlertCircle,
|
|
25
|
+
SquareCheckBig
|
|
26
|
+
} from '@lucide/svelte';
|
|
27
|
+
import { page } from '$app/state';
|
|
28
|
+
import { goto } from '$app/navigation';
|
|
29
|
+
import { assets } from '../../api/assets';
|
|
30
|
+
import type { AssetReference } from '../../api/assets';
|
|
31
|
+
import type { Asset } from '../../types/asset';
|
|
32
|
+
import { toast } from 'svelte-sonner';
|
|
33
|
+
import { copyUrlToClipboard, downloadFile } from '../../utils/asset-actions';
|
|
34
|
+
import { cmsLogger } from '../../utils/logger';
|
|
35
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
36
|
+
|
|
37
|
+
interface Props {
|
|
38
|
+
/** When true, shows a "Select" button for picking an asset */
|
|
39
|
+
selectable?: boolean;
|
|
40
|
+
/** When true, allows selecting multiple assets (used with selectable) */
|
|
41
|
+
multiSelect?: boolean;
|
|
42
|
+
/** Callback when an asset is selected (single select mode) */
|
|
43
|
+
onSelect?: (asset: Asset) => void;
|
|
44
|
+
/** Callback when multiple assets are selected (multi select mode) */
|
|
45
|
+
onSelectMultiple?: (assets: Asset[]) => void;
|
|
46
|
+
/** Filter to specific asset type */
|
|
47
|
+
assetTypeFilter?: 'image' | 'file';
|
|
48
|
+
/** Number of assets per page */
|
|
49
|
+
pageSize?: number;
|
|
50
|
+
/** Whether this tab is currently active (triggers refetch when becoming active) */
|
|
51
|
+
active?: boolean;
|
|
52
|
+
/** Asset IDs already in use (shown with a tick indicator) */
|
|
53
|
+
existingAssetIds?: Set<string>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let {
|
|
57
|
+
selectable = false,
|
|
58
|
+
multiSelect = false,
|
|
59
|
+
onSelect,
|
|
60
|
+
onSelectMultiple,
|
|
61
|
+
assetTypeFilter,
|
|
62
|
+
pageSize = 30,
|
|
63
|
+
active = true,
|
|
64
|
+
existingAssetIds
|
|
65
|
+
}: Props = $props();
|
|
66
|
+
|
|
67
|
+
// State
|
|
68
|
+
let assetList = $state<Asset[]>([]);
|
|
69
|
+
let loading = $state(false);
|
|
70
|
+
let searchQuery = $state('');
|
|
71
|
+
let viewMode = $state<'grid' | 'list'>('grid');
|
|
72
|
+
let sortOrder = $state<'newest' | 'oldest' | 'name-asc' | 'name-desc'>('newest');
|
|
73
|
+
|
|
74
|
+
let selectedAsset = $state<Asset | null>(null);
|
|
75
|
+
let lightboxOpen = $state(false);
|
|
76
|
+
let currentPage = $state(1);
|
|
77
|
+
let totalPages = $state(1);
|
|
78
|
+
let totalAssets = $state(0);
|
|
79
|
+
|
|
80
|
+
// Upload state
|
|
81
|
+
let isUploading = $state(false);
|
|
82
|
+
let isDragging = $state(false);
|
|
83
|
+
let showUploadModal = $state(false);
|
|
84
|
+
let modalFileInputRef: HTMLInputElement;
|
|
85
|
+
let modalIsDragging = $state(false);
|
|
86
|
+
|
|
87
|
+
interface UploadQueueItem {
|
|
88
|
+
file: File;
|
|
89
|
+
status: 'pending' | 'uploading' | 'done' | 'failed';
|
|
90
|
+
}
|
|
91
|
+
let uploadQueue = $state<UploadQueueItem[]>([]);
|
|
92
|
+
|
|
93
|
+
// Detail editing state
|
|
94
|
+
let editTitle = $state('');
|
|
95
|
+
let editDescription = $state('');
|
|
96
|
+
let editAlt = $state('');
|
|
97
|
+
let editCreditLine = $state('');
|
|
98
|
+
let isSaving = $state(false);
|
|
99
|
+
|
|
100
|
+
// Bulk selection state
|
|
101
|
+
let selectMode = $state(false);
|
|
102
|
+
let selectedIds = $state<Set<string>>(
|
|
103
|
+
selectable && multiSelect && existingAssetIds ? new Set(existingAssetIds) : new Set()
|
|
104
|
+
);
|
|
105
|
+
let isBulkDeleting = $state(false);
|
|
106
|
+
|
|
107
|
+
// In selectable+multiSelect mode, always be in select mode
|
|
108
|
+
const isSelectMode = $derived(selectMode || (selectable && multiSelect));
|
|
109
|
+
|
|
110
|
+
function toggleSelectMode() {
|
|
111
|
+
selectMode = !selectMode;
|
|
112
|
+
if (!selectMode) {
|
|
113
|
+
selectedIds = new Set();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Reference tracking state
|
|
118
|
+
let referenceCounts = $state<Record<string, number>>({});
|
|
119
|
+
let detailTab = $state<'details' | 'references'>('details');
|
|
120
|
+
let selectedAssetRefs = $state<AssetReference[]>([]);
|
|
121
|
+
let loadingRefs = $state(false);
|
|
122
|
+
let selectedRefCount = $state(0);
|
|
123
|
+
|
|
124
|
+
// Debounced search
|
|
125
|
+
let searchTimeout: ReturnType<typeof setTimeout>;
|
|
126
|
+
|
|
127
|
+
function handleSearchInput(value: string) {
|
|
128
|
+
searchQuery = value;
|
|
129
|
+
clearTimeout(searchTimeout);
|
|
130
|
+
searchTimeout = setTimeout(() => {
|
|
131
|
+
currentPage = 1;
|
|
132
|
+
fetchAssets();
|
|
133
|
+
}, 300);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fetch assets
|
|
137
|
+
async function fetchAssets(page = currentPage) {
|
|
138
|
+
loading = true;
|
|
139
|
+
try {
|
|
140
|
+
const offset = (page - 1) * pageSize;
|
|
141
|
+
const result = await assets.list({
|
|
142
|
+
assetType: assetTypeFilter,
|
|
143
|
+
search: searchQuery || undefined,
|
|
144
|
+
limit: pageSize,
|
|
145
|
+
offset
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (result.success && result.data) {
|
|
149
|
+
assetList = result.data;
|
|
150
|
+
currentPage = page;
|
|
151
|
+
if (result.pagination) {
|
|
152
|
+
totalPages = result.pagination.totalPages;
|
|
153
|
+
totalAssets = result.pagination.total;
|
|
154
|
+
}
|
|
155
|
+
// Clear bulk selection on page change (but never in multi-select picker mode —
|
|
156
|
+
// selection is initialised once at mount and only changed by user interaction)
|
|
157
|
+
if (!(selectable && multiSelect)) {
|
|
158
|
+
selectedIds = new Set();
|
|
159
|
+
}
|
|
160
|
+
// Fetch reference counts for this page
|
|
161
|
+
fetchReferenceCounts(result.data.map((a) => a.id));
|
|
162
|
+
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
toast.error('Failed to fetch assets');
|
|
165
|
+
} finally {
|
|
166
|
+
loading = false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function goToPage(page: number) {
|
|
171
|
+
if (page < 1 || page > totalPages || page === currentPage) return;
|
|
172
|
+
fetchAssets(page);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Fetch reference counts for current page of assets
|
|
176
|
+
async function fetchReferenceCounts(assetIds: string[]) {
|
|
177
|
+
if (assetIds.length === 0) return;
|
|
178
|
+
try {
|
|
179
|
+
const result = await assets.getReferenceCounts(assetIds);
|
|
180
|
+
if (result.success && result.data) {
|
|
181
|
+
referenceCounts = { ...referenceCounts, ...result.data };
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
toast.error('Failed to fetch reference counts');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Fetch full references for a specific asset (sidebar)
|
|
189
|
+
async function fetchAssetReferences(assetId: string) {
|
|
190
|
+
loadingRefs = true;
|
|
191
|
+
try {
|
|
192
|
+
const result = await assets.getReferences(assetId);
|
|
193
|
+
if (result.success && result.data) {
|
|
194
|
+
selectedAssetRefs = result.data.references;
|
|
195
|
+
selectedRefCount = result.data.total;
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
toast.error('Failed to fetch asset references');
|
|
199
|
+
selectedAssetRefs = [];
|
|
200
|
+
selectedRefCount = 0;
|
|
201
|
+
} finally {
|
|
202
|
+
loadingRefs = false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Sort assets client-side
|
|
207
|
+
function sortAssets(list: Asset[]): Asset[] {
|
|
208
|
+
const sorted = [...list];
|
|
209
|
+
switch (sortOrder) {
|
|
210
|
+
case 'newest':
|
|
211
|
+
return sorted.sort(
|
|
212
|
+
(a, b) => new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime()
|
|
213
|
+
);
|
|
214
|
+
case 'oldest':
|
|
215
|
+
return sorted.sort(
|
|
216
|
+
(a, b) => new Date(a.createdAt || 0).getTime() - new Date(b.createdAt || 0).getTime()
|
|
217
|
+
);
|
|
218
|
+
case 'name-asc':
|
|
219
|
+
return sorted.sort((a, b) => a.originalFilename.localeCompare(b.originalFilename));
|
|
220
|
+
case 'name-desc':
|
|
221
|
+
return sorted.sort((a, b) => b.originalFilename.localeCompare(a.originalFilename));
|
|
222
|
+
default:
|
|
223
|
+
return sorted;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Pinned assets (already in array) — separate from the main sorted list
|
|
228
|
+
const pinnedAssets = $derived.by(() => {
|
|
229
|
+
if (!(selectable && multiSelect && existingAssetIds && existingAssetIds.size > 0)) return [];
|
|
230
|
+
return assetList.filter((a) => existingAssetIds!.has(a.id));
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const sortedAssets = $derived.by(() => {
|
|
234
|
+
if (selectable && multiSelect && existingAssetIds && existingAssetIds.size > 0) {
|
|
235
|
+
return sortAssets(assetList.filter((a) => !existingAssetIds!.has(a.id)));
|
|
236
|
+
}
|
|
237
|
+
return sortAssets(assetList);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Bulk selection derived (must be after sortedAssets)
|
|
241
|
+
const allSelected = $derived(
|
|
242
|
+
sortedAssets.length > 0 && sortedAssets.every((a) => selectedIds.has(a.id))
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
function toggleSelectAll() {
|
|
246
|
+
if (allSelected) {
|
|
247
|
+
selectedIds = new Set();
|
|
248
|
+
} else {
|
|
249
|
+
selectedIds = new Set(sortedAssets.map((a) => a.id));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function toggleSelect(id: string) {
|
|
254
|
+
const next = new SvelteSet(selectedIds);
|
|
255
|
+
if (next.has(id)) {
|
|
256
|
+
next.delete(id);
|
|
257
|
+
} else {
|
|
258
|
+
next.add(id);
|
|
259
|
+
}
|
|
260
|
+
selectedIds = next;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function confirmMultiSelect() {
|
|
264
|
+
if (onSelectMultiple) {
|
|
265
|
+
const selected = assetList.filter((a) => selectedIds.has(a.id));
|
|
266
|
+
onSelectMultiple(selected);
|
|
267
|
+
selectedIds = new Set();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function bulkDelete() {
|
|
272
|
+
if (selectedIds.size === 0) return;
|
|
273
|
+
|
|
274
|
+
// Fetch fresh reference counts before checking
|
|
275
|
+
const idsToCheck = [...selectedIds];
|
|
276
|
+
try {
|
|
277
|
+
const result = await assets.getReferenceCounts(idsToCheck);
|
|
278
|
+
if (result.success && result.data) {
|
|
279
|
+
referenceCounts = { ...referenceCounts, ...result.data };
|
|
280
|
+
}
|
|
281
|
+
} catch (err) {
|
|
282
|
+
toast.error('Failed to check references');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Check for referenced assets
|
|
286
|
+
const referencedAssets = idsToCheck.filter((id) => (referenceCounts[id] || 0) > 0);
|
|
287
|
+
if (referencedAssets.length > 0) {
|
|
288
|
+
toast.error(
|
|
289
|
+
`Cannot delete ${referencedAssets.length} asset${referencedAssets.length > 1 ? 's' : ''} — still referenced by documents. Remove the references first.`
|
|
290
|
+
);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const count = selectedIds.size;
|
|
295
|
+
if (!confirm(`Delete ${count} asset${count > 1 ? 's' : ''}? This cannot be undone.`)) return;
|
|
296
|
+
|
|
297
|
+
isBulkDeleting = true;
|
|
298
|
+
try {
|
|
299
|
+
const result = await assets.deleteBulk([...selectedIds]);
|
|
300
|
+
if (result.success) {
|
|
301
|
+
if (selectedAsset && selectedIds.has(selectedAsset.id)) {
|
|
302
|
+
selectedAsset = null;
|
|
303
|
+
}
|
|
304
|
+
selectedIds = new Set();
|
|
305
|
+
await fetchAssets();
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
toast.error('Failed to delete assets');
|
|
309
|
+
} finally {
|
|
310
|
+
isBulkDeleting = false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Upload files via modal queue
|
|
315
|
+
function addFilesToQueue(files: FileList | null) {
|
|
316
|
+
if (!files || files.length === 0) return;
|
|
317
|
+
const newItems: UploadQueueItem[] = Array.from(files).map((file) => ({
|
|
318
|
+
file,
|
|
319
|
+
status: 'pending' as const
|
|
320
|
+
}));
|
|
321
|
+
uploadQueue = [...uploadQueue, ...newItems];
|
|
322
|
+
processUploadQueue();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function processUploadQueue() {
|
|
326
|
+
if (isUploading) return;
|
|
327
|
+
isUploading = true;
|
|
328
|
+
|
|
329
|
+
for (let i = 0; i < uploadQueue.length; i++) {
|
|
330
|
+
if (uploadQueue[i]!.status !== 'pending') continue;
|
|
331
|
+
uploadQueue[i]!.status = 'uploading';
|
|
332
|
+
uploadQueue = [...uploadQueue]; // trigger reactivity
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
const formData = new FormData();
|
|
336
|
+
formData.append('file', uploadQueue[i]!.file);
|
|
337
|
+
const result = await assets.upload(formData);
|
|
338
|
+
uploadQueue[i]!.status = result.success ? 'done' : 'failed';
|
|
339
|
+
} catch {
|
|
340
|
+
uploadQueue[i]!.status = 'failed';
|
|
341
|
+
}
|
|
342
|
+
uploadQueue = [...uploadQueue];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
isUploading = false;
|
|
346
|
+
|
|
347
|
+
// If all done, refetch and close after a brief pause
|
|
348
|
+
if (uploadQueue.every((item) => item.status === 'done' || item.status === 'failed')) {
|
|
349
|
+
currentPage = 1;
|
|
350
|
+
await fetchAssets(1);
|
|
351
|
+
setTimeout(() => {
|
|
352
|
+
showUploadModal = false;
|
|
353
|
+
uploadQueue = [];
|
|
354
|
+
}, 800);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Drag and drop
|
|
359
|
+
function handleDragOver(e: DragEvent) {
|
|
360
|
+
e.preventDefault();
|
|
361
|
+
isDragging = true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function handleDragLeave(e: DragEvent) {
|
|
365
|
+
e.preventDefault();
|
|
366
|
+
isDragging = false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function handleDrop(e: DragEvent) {
|
|
370
|
+
e.preventDefault();
|
|
371
|
+
isDragging = false;
|
|
372
|
+
showUploadModal = true;
|
|
373
|
+
addFilesToQueue(e.dataTransfer?.files || null);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Select an asset for detail view — re-fetch to get fresh data
|
|
377
|
+
// Select an asset for detail view
|
|
378
|
+
function openAssetDetail(asset: Asset) {
|
|
379
|
+
const isSameAsset = selectedAsset?.id === asset.id;
|
|
380
|
+
|
|
381
|
+
selectedAsset = asset;
|
|
382
|
+
editTitle = asset.title || '';
|
|
383
|
+
editDescription = asset.description || '';
|
|
384
|
+
editAlt = asset.alt || '';
|
|
385
|
+
editCreditLine = asset.creditLine || '';
|
|
386
|
+
|
|
387
|
+
// Only reset references/tab when switching to a different asset
|
|
388
|
+
if (!isSameAsset) {
|
|
389
|
+
detailTab = 'details';
|
|
390
|
+
selectedAssetRefs = [];
|
|
391
|
+
selectedRefCount = referenceCounts[asset.id] || 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function closeAssetDetail() {
|
|
396
|
+
selectedAsset = null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Save metadata
|
|
400
|
+
async function saveMetadata() {
|
|
401
|
+
if (!selectedAsset) return;
|
|
402
|
+
isSaving = true;
|
|
403
|
+
try {
|
|
404
|
+
const result = await assets.update(selectedAsset.id, {
|
|
405
|
+
title: editTitle || undefined,
|
|
406
|
+
description: editDescription || undefined,
|
|
407
|
+
alt: editAlt || undefined,
|
|
408
|
+
creditLine: editCreditLine || undefined
|
|
409
|
+
});
|
|
410
|
+
if (result.success && result.data) {
|
|
411
|
+
// Update in list
|
|
412
|
+
assetList = assetList.map((a) => (a.id === selectedAsset!.id ? result.data! : a));
|
|
413
|
+
selectedAsset = result.data;
|
|
414
|
+
}
|
|
415
|
+
} catch (err) {
|
|
416
|
+
toast.error('Failed to save metadata');
|
|
417
|
+
} finally {
|
|
418
|
+
isSaving = false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Delete asset
|
|
423
|
+
async function deleteAsset(asset: Asset) {
|
|
424
|
+
const refCount = referenceCounts[asset.id] || 0;
|
|
425
|
+
if (refCount > 0) {
|
|
426
|
+
toast.error(
|
|
427
|
+
`Cannot delete "${asset.originalFilename}" — referenced by ${refCount} document${refCount > 1 ? 's' : ''}. Remove the references first.`
|
|
428
|
+
);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!confirm(`Delete "${asset.originalFilename}"? This cannot be undone.`)) return;
|
|
432
|
+
try {
|
|
433
|
+
const result = await assets.delete(asset.id);
|
|
434
|
+
if (result.success) {
|
|
435
|
+
if (selectedAsset?.id === asset.id) {
|
|
436
|
+
selectedAsset = null;
|
|
437
|
+
}
|
|
438
|
+
await fetchAssets();
|
|
439
|
+
}
|
|
440
|
+
} catch (err) {
|
|
441
|
+
toast.error('Failed to delete asset');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Copy URL state
|
|
446
|
+
let copiedUrl = $state(false);
|
|
447
|
+
|
|
448
|
+
async function copyAssetUrl(asset: Asset) {
|
|
449
|
+
const url = getThumbnailUrl(asset);
|
|
450
|
+
const success = await copyUrlToClipboard(url);
|
|
451
|
+
if (success) {
|
|
452
|
+
copiedUrl = true;
|
|
453
|
+
setTimeout(() => (copiedUrl = false), 2000);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function downloadAsset(asset: Asset) {
|
|
458
|
+
downloadFile(getThumbnailUrl(asset), asset.originalFilename);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Format file size
|
|
462
|
+
function formatSize(bytes: number): string {
|
|
463
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
464
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} kB`;
|
|
465
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Format date
|
|
469
|
+
function formatDate(date: Date | string | null): string {
|
|
470
|
+
if (!date) return '';
|
|
471
|
+
const d = new Date(date);
|
|
472
|
+
return d.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' });
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Get thumbnail URL
|
|
476
|
+
function getThumbnailUrl(asset: Asset): string {
|
|
477
|
+
return asset.url || `/media/${asset.id}/${asset.filename}`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Is image type
|
|
481
|
+
function isImage(asset: Asset): boolean {
|
|
482
|
+
return asset.assetType === 'image' || asset.mimeType.startsWith('image/');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Compute visible page numbers (show up to 5 pages with ellipsis)
|
|
486
|
+
const visiblePages = $derived.by(() => {
|
|
487
|
+
const pages: (number | '...')[] = [];
|
|
488
|
+
if (totalPages <= 7) {
|
|
489
|
+
for (let i = 1; i <= totalPages; i++) pages.push(i);
|
|
490
|
+
} else {
|
|
491
|
+
pages.push(1);
|
|
492
|
+
if (currentPage > 3) pages.push('...');
|
|
493
|
+
const start = Math.max(2, currentPage - 1);
|
|
494
|
+
const end = Math.min(totalPages - 1, currentPage + 1);
|
|
495
|
+
for (let i = start; i <= end; i++) pages.push(i);
|
|
496
|
+
if (currentPage < totalPages - 2) pages.push('...');
|
|
497
|
+
pages.push(totalPages);
|
|
498
|
+
}
|
|
499
|
+
return pages;
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Sort label
|
|
503
|
+
const sortLabel = $derived(
|
|
504
|
+
sortOrder === 'newest'
|
|
505
|
+
? 'Last created: Newest first'
|
|
506
|
+
: sortOrder === 'oldest'
|
|
507
|
+
? 'Last created: Oldest first'
|
|
508
|
+
: sortOrder === 'name-asc'
|
|
509
|
+
? 'Name: A-Z'
|
|
510
|
+
: 'Name: Z-A'
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
// Cycle sort
|
|
514
|
+
function cycleSort() {
|
|
515
|
+
const orders: (typeof sortOrder)[] = ['newest', 'oldest', 'name-asc', 'name-desc'];
|
|
516
|
+
const idx = orders.indexOf(sortOrder);
|
|
517
|
+
sortOrder = orders[(idx + 1) % orders.length]!;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Track org changes to refetch assets
|
|
521
|
+
let currentOrgId = $state<string | null>(null);
|
|
522
|
+
|
|
523
|
+
// Track whether we've been active before to detect tab switches
|
|
524
|
+
let wasActive = $state(false);
|
|
525
|
+
|
|
526
|
+
// Load on mount and refetch when org changes
|
|
527
|
+
$effect(() => {
|
|
528
|
+
const orgId = page.url.searchParams.get('orgId');
|
|
529
|
+
if (orgId !== currentOrgId) {
|
|
530
|
+
currentOrgId = orgId;
|
|
531
|
+
selectedAsset = null;
|
|
532
|
+
currentPage = 1;
|
|
533
|
+
fetchAssets(1);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Refetch when tab becomes active (switching from another tab)
|
|
538
|
+
$effect(() => {
|
|
539
|
+
if (active && wasActive) {
|
|
540
|
+
selectedAsset = null;
|
|
541
|
+
fetchAssets();
|
|
542
|
+
}
|
|
543
|
+
wasActive = active;
|
|
544
|
+
});
|
|
545
|
+
</script>
|
|
546
|
+
|
|
547
|
+
<div
|
|
548
|
+
class="flex h-full flex-col"
|
|
549
|
+
role="region"
|
|
550
|
+
ondragover={handleDragOver}
|
|
551
|
+
ondragleave={handleDragLeave}
|
|
552
|
+
ondrop={handleDrop}
|
|
553
|
+
>
|
|
554
|
+
<!-- Drag overlay -->
|
|
555
|
+
{#if isDragging}
|
|
556
|
+
<div
|
|
557
|
+
class="bg-primary/5 border-primary absolute inset-0 z-50 flex items-center justify-center border-2 border-dashed"
|
|
558
|
+
>
|
|
559
|
+
<div class="text-center">
|
|
560
|
+
<Upload class="text-primary mx-auto mb-2 h-12 w-12" />
|
|
561
|
+
<p class="text-primary text-lg font-medium">Drop files to upload</p>
|
|
562
|
+
</div>
|
|
563
|
+
</div>
|
|
564
|
+
{/if}
|
|
565
|
+
|
|
566
|
+
<!-- Header -->
|
|
567
|
+
<div class="border-border flex items-center justify-between border-b px-4 py-3 sm:px-6 sm:py-4">
|
|
568
|
+
<h2 class="text-base font-semibold sm:text-lg">Browse Assets</h2>
|
|
569
|
+
<Button
|
|
570
|
+
size="sm"
|
|
571
|
+
onclick={() => {
|
|
572
|
+
showUploadModal = true;
|
|
573
|
+
uploadQueue = [];
|
|
574
|
+
}}
|
|
575
|
+
>
|
|
576
|
+
<Upload size={16} class="sm:mr-2" />
|
|
577
|
+
<span class="hidden sm:inline">Upload assets</span>
|
|
578
|
+
</Button>
|
|
579
|
+
</div>
|
|
580
|
+
|
|
581
|
+
<!-- Toolbar -->
|
|
582
|
+
<div
|
|
583
|
+
class="border-border flex flex-wrap items-center gap-2 border-b px-4 py-2 sm:gap-3 sm:px-6 sm:py-3"
|
|
584
|
+
>
|
|
585
|
+
<div class="relative min-w-0 flex-1 sm:w-48 sm:flex-none">
|
|
586
|
+
<Search size={14} class="text-muted-foreground absolute top-1/2 left-2.5 -translate-y-1/2" />
|
|
587
|
+
<Input
|
|
588
|
+
placeholder="Search"
|
|
589
|
+
class="h-8 pl-8 text-sm"
|
|
590
|
+
value={searchQuery}
|
|
591
|
+
oninput={(e) => handleSearchInput((e.target as HTMLInputElement).value)}
|
|
592
|
+
/>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
{#if totalAssets > 0}
|
|
596
|
+
<span class="text-muted-foreground hidden text-xs sm:inline">
|
|
597
|
+
{(currentPage - 1) * pageSize + 1}–{Math.min(currentPage * pageSize, totalAssets)} of {totalAssets}
|
|
598
|
+
</span>
|
|
599
|
+
{/if}
|
|
600
|
+
<div class="hidden flex-1 sm:block"></div>
|
|
601
|
+
|
|
602
|
+
<!-- Page size -->
|
|
603
|
+
<div class="hidden items-center gap-1.5 sm:flex">
|
|
604
|
+
<span class="text-muted-foreground text-xs">Show</span>
|
|
605
|
+
<select
|
|
606
|
+
value={pageSize}
|
|
607
|
+
onchange={(e) => {
|
|
608
|
+
pageSize = parseInt((e.target as HTMLSelectElement).value);
|
|
609
|
+
currentPage = 1;
|
|
610
|
+
fetchAssets(1);
|
|
611
|
+
}}
|
|
612
|
+
class="border-input bg-background text-foreground h-7 rounded-md border px-1.5 text-xs"
|
|
613
|
+
>
|
|
614
|
+
<option value={10}>10</option>
|
|
615
|
+
<option value={20}>20</option>
|
|
616
|
+
<option value={30}>30</option>
|
|
617
|
+
<option value={50}>50</option>
|
|
618
|
+
<option value={100}>100</option>
|
|
619
|
+
</select>
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<!-- View toggle -->
|
|
623
|
+
<div class="bg-muted flex items-center rounded-md p-0.5">
|
|
624
|
+
<button
|
|
625
|
+
onclick={() => (viewMode = 'grid')}
|
|
626
|
+
class="rounded p-1.5 {viewMode === 'grid'
|
|
627
|
+
? 'bg-background shadow'
|
|
628
|
+
: 'text-muted-foreground'}"
|
|
629
|
+
title="Grid view"
|
|
630
|
+
>
|
|
631
|
+
<Grid3x3 size={14} />
|
|
632
|
+
</button>
|
|
633
|
+
<button
|
|
634
|
+
onclick={() => (viewMode = 'list')}
|
|
635
|
+
class="rounded p-1.5 {viewMode === 'list'
|
|
636
|
+
? 'bg-background shadow'
|
|
637
|
+
: 'text-muted-foreground'}"
|
|
638
|
+
title="List view"
|
|
639
|
+
>
|
|
640
|
+
<List size={14} />
|
|
641
|
+
</button>
|
|
642
|
+
</div>
|
|
643
|
+
|
|
644
|
+
<!-- Select mode toggle -->
|
|
645
|
+
{#if !selectable}
|
|
646
|
+
<button
|
|
647
|
+
onclick={toggleSelectMode}
|
|
648
|
+
class="rounded p-1.5 transition-colors {isSelectMode
|
|
649
|
+
? 'bg-primary text-primary-foreground'
|
|
650
|
+
: 'text-muted-foreground hover:text-foreground'}"
|
|
651
|
+
title={isSelectMode ? 'Exit select mode' : 'Select multiple'}
|
|
652
|
+
>
|
|
653
|
+
<SquareCheckBig size={14} />
|
|
654
|
+
</button>
|
|
655
|
+
{/if}
|
|
656
|
+
|
|
657
|
+
<!-- Sort -->
|
|
658
|
+
<button
|
|
659
|
+
onclick={cycleSort}
|
|
660
|
+
class="text-muted-foreground hover:text-foreground flex items-center gap-1 text-xs transition-colors sm:gap-1.5"
|
|
661
|
+
>
|
|
662
|
+
<ArrowDownUp size={14} />
|
|
663
|
+
<span class="hidden sm:inline">{sortLabel}</span>
|
|
664
|
+
</button>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<!-- Content area -->
|
|
668
|
+
<div class="flex flex-1 flex-col overflow-y-auto md:flex-row md:overflow-hidden">
|
|
669
|
+
<!-- Main content (hidden on mobile when asset detail is open) -->
|
|
670
|
+
<div class="min-h-0 flex-1 md:overflow-y-auto {selectedAsset ? 'hidden md:block' : ''}">
|
|
671
|
+
<svelte:boundary
|
|
672
|
+
onerror={(error) => cmsLogger.error('[MediaBrowser]', 'Render error:', error)}
|
|
673
|
+
>
|
|
674
|
+
{#if loading && assetList.length === 0}
|
|
675
|
+
<div class="flex h-full items-center justify-center">
|
|
676
|
+
<p class="text-muted-foreground">Loading assets...</p>
|
|
677
|
+
</div>
|
|
678
|
+
{:else if sortedAssets.length === 0}
|
|
679
|
+
<div class="flex h-full flex-col items-center justify-center gap-4">
|
|
680
|
+
<div class="bg-muted/50 flex h-16 w-16 items-center justify-center rounded-full">
|
|
681
|
+
<ImageIcon class="text-muted-foreground h-8 w-8" />
|
|
682
|
+
</div>
|
|
683
|
+
<div class="text-center">
|
|
684
|
+
<h3 class="mb-1 font-medium">No assets found</h3>
|
|
685
|
+
<p class="text-muted-foreground text-sm">
|
|
686
|
+
{searchQuery
|
|
687
|
+
? 'Try a different search term'
|
|
688
|
+
: 'Upload your first asset to get started'}
|
|
689
|
+
</p>
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
{:else}
|
|
693
|
+
<!-- Bulk action bar (shared for grid and list) -->
|
|
694
|
+
{#if selectable && multiSelect}
|
|
695
|
+
<div class="bg-muted border-border flex items-center gap-3 border-b px-4 py-2">
|
|
696
|
+
<span class="text-sm font-medium">
|
|
697
|
+
{selectedIds.size} selected
|
|
698
|
+
</span>
|
|
699
|
+
<Button variant="default" size="sm" onclick={confirmMultiSelect}>Done</Button>
|
|
700
|
+
</div>
|
|
701
|
+
{:else if selectedIds.size > 0}
|
|
702
|
+
<div class="bg-muted border-border flex items-center gap-3 border-b px-4 py-2">
|
|
703
|
+
<span class="text-sm font-medium">
|
|
704
|
+
{selectedIds.size} selected
|
|
705
|
+
</span>
|
|
706
|
+
<Button
|
|
707
|
+
variant="destructive"
|
|
708
|
+
size="sm"
|
|
709
|
+
onclick={bulkDelete}
|
|
710
|
+
disabled={isBulkDeleting}
|
|
711
|
+
>
|
|
712
|
+
<Trash2 size={14} class="mr-1.5" />
|
|
713
|
+
{isBulkDeleting ? 'Deleting...' : 'Delete'}
|
|
714
|
+
</Button>
|
|
715
|
+
<button
|
|
716
|
+
onclick={() => (selectedIds = new Set())}
|
|
717
|
+
class="text-muted-foreground hover:text-foreground text-sm transition-colors"
|
|
718
|
+
>
|
|
719
|
+
Clear selection
|
|
720
|
+
</button>
|
|
721
|
+
</div>
|
|
722
|
+
{/if}
|
|
723
|
+
{#if viewMode === 'grid'}
|
|
724
|
+
<!-- Grid View -->
|
|
725
|
+
<div class="grid grid-cols-2 gap-0.5 p-1 sm:grid-cols-5 xl:grid-cols-10">
|
|
726
|
+
{#each pinnedAssets as asset (asset.id)}
|
|
727
|
+
<button
|
|
728
|
+
onclick={() => openAssetDetail(asset)}
|
|
729
|
+
class="group relative flex flex-col overflow-hidden rounded-sm transition-colors {selectedIds.has(
|
|
730
|
+
asset.id
|
|
731
|
+
)
|
|
732
|
+
? 'ring-primary ring-2'
|
|
733
|
+
: selectedAsset?.id === asset.id
|
|
734
|
+
? 'ring-primary ring-2'
|
|
735
|
+
: 'hover:bg-muted/50'}"
|
|
736
|
+
>
|
|
737
|
+
<div class="bg-muted/30 relative aspect-square overflow-hidden">
|
|
738
|
+
{#if isImage(asset)}
|
|
739
|
+
<img
|
|
740
|
+
src={getThumbnailUrl(asset)}
|
|
741
|
+
alt={asset.alt || asset.originalFilename}
|
|
742
|
+
class="h-full w-full object-contain"
|
|
743
|
+
loading="lazy"
|
|
744
|
+
/>
|
|
745
|
+
{:else}
|
|
746
|
+
<div class="flex h-full items-center justify-center">
|
|
747
|
+
<FileText class="text-muted-foreground h-10 w-10" />
|
|
748
|
+
</div>
|
|
749
|
+
{/if}
|
|
750
|
+
<div class="absolute top-1.5 left-1.5">
|
|
751
|
+
<Checkbox
|
|
752
|
+
checked={selectedIds.has(asset.id)}
|
|
753
|
+
onCheckedChange={() => toggleSelect(asset.id)}
|
|
754
|
+
onclick={(e) => e.stopPropagation()}
|
|
755
|
+
/>
|
|
756
|
+
</div>
|
|
757
|
+
</div>
|
|
758
|
+
<div class="p-1.5">
|
|
759
|
+
<p class="text-muted-foreground truncate text-xs">
|
|
760
|
+
{asset.originalFilename}
|
|
761
|
+
</p>
|
|
762
|
+
</div>
|
|
763
|
+
</button>
|
|
764
|
+
{/each}
|
|
765
|
+
{#each sortedAssets as asset (asset.id)}
|
|
766
|
+
<button
|
|
767
|
+
onclick={() => {
|
|
768
|
+
if (selectable && multiSelect) {
|
|
769
|
+
openAssetDetail(asset);
|
|
770
|
+
} else if (isSelectMode) {
|
|
771
|
+
toggleSelect(asset.id);
|
|
772
|
+
} else if (selectable && onSelect) {
|
|
773
|
+
onSelect(asset);
|
|
774
|
+
} else {
|
|
775
|
+
openAssetDetail(asset);
|
|
776
|
+
}
|
|
777
|
+
}}
|
|
778
|
+
class="group relative flex flex-col overflow-hidden rounded-sm transition-colors {selectedIds.has(
|
|
779
|
+
asset.id
|
|
780
|
+
)
|
|
781
|
+
? 'ring-primary ring-2'
|
|
782
|
+
: selectedAsset?.id === asset.id
|
|
783
|
+
? 'ring-primary ring-2'
|
|
784
|
+
: 'hover:bg-muted/50'}"
|
|
785
|
+
>
|
|
786
|
+
<div class="bg-muted/30 relative aspect-square overflow-hidden">
|
|
787
|
+
{#if isImage(asset)}
|
|
788
|
+
<img
|
|
789
|
+
src={getThumbnailUrl(asset)}
|
|
790
|
+
alt={asset.alt || asset.originalFilename}
|
|
791
|
+
class="h-full w-full object-contain"
|
|
792
|
+
loading="lazy"
|
|
793
|
+
/>
|
|
794
|
+
{:else}
|
|
795
|
+
<div class="flex h-full items-center justify-center">
|
|
796
|
+
<FileText class="text-muted-foreground h-10 w-10" />
|
|
797
|
+
</div>
|
|
798
|
+
{/if}
|
|
799
|
+
<!-- Checkbox overlay (only in select mode) -->
|
|
800
|
+
{#if isSelectMode}
|
|
801
|
+
<div class="absolute top-1.5 left-1.5">
|
|
802
|
+
<Checkbox
|
|
803
|
+
checked={selectedIds.has(asset.id)}
|
|
804
|
+
onCheckedChange={() => toggleSelect(asset.id)}
|
|
805
|
+
onclick={(e) => e.stopPropagation()}
|
|
806
|
+
/>
|
|
807
|
+
</div>
|
|
808
|
+
{/if}
|
|
809
|
+
</div>
|
|
810
|
+
<div class="p-1.5">
|
|
811
|
+
<p class="text-muted-foreground truncate text-xs">
|
|
812
|
+
{asset.originalFilename}
|
|
813
|
+
</p>
|
|
814
|
+
</div>
|
|
815
|
+
</button>
|
|
816
|
+
{/each}
|
|
817
|
+
</div>
|
|
818
|
+
|
|
819
|
+
<!-- Pagination -->
|
|
820
|
+
{#if totalPages > 1}
|
|
821
|
+
<div class="border-border flex items-center justify-center gap-1 border-t px-4 py-3">
|
|
822
|
+
<button
|
|
823
|
+
onclick={() => goToPage(currentPage - 1)}
|
|
824
|
+
disabled={currentPage <= 1 || loading}
|
|
825
|
+
class="hover:bg-muted rounded p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
|
|
826
|
+
>
|
|
827
|
+
<ChevronLeft size={16} />
|
|
828
|
+
</button>
|
|
829
|
+
{#each visiblePages as pg}
|
|
830
|
+
{#if pg === '...'}
|
|
831
|
+
<span class="text-muted-foreground px-1.5 text-sm">...</span>
|
|
832
|
+
{:else}
|
|
833
|
+
<button
|
|
834
|
+
onclick={() => goToPage(pg)}
|
|
835
|
+
disabled={loading}
|
|
836
|
+
class="min-w-[32px] rounded px-2 py-1 text-sm font-medium transition-colors {pg ===
|
|
837
|
+
currentPage
|
|
838
|
+
? 'bg-foreground text-background'
|
|
839
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
|
|
840
|
+
>
|
|
841
|
+
{pg}
|
|
842
|
+
</button>
|
|
843
|
+
{/if}
|
|
844
|
+
{/each}
|
|
845
|
+
<button
|
|
846
|
+
onclick={() => goToPage(currentPage + 1)}
|
|
847
|
+
disabled={currentPage >= totalPages || loading}
|
|
848
|
+
class="hover:bg-muted rounded p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
|
|
849
|
+
>
|
|
850
|
+
<ChevronRight size={16} />
|
|
851
|
+
</button>
|
|
852
|
+
</div>
|
|
853
|
+
{/if}
|
|
854
|
+
{:else}
|
|
855
|
+
<!-- List View -->
|
|
856
|
+
<div class="w-full">
|
|
857
|
+
<!-- Table header -->
|
|
858
|
+
<div
|
|
859
|
+
class="bg-muted/30 border-border text-muted-foreground hidden items-center gap-4 border-b px-4 py-2 text-xs font-medium tracking-wider uppercase md:grid md:grid-cols-[auto_40px_1fr_100px_100px_80px_50px_100px]"
|
|
860
|
+
>
|
|
861
|
+
<div class="w-4">
|
|
862
|
+
<Checkbox checked={allSelected} onCheckedChange={toggleSelectAll} />
|
|
863
|
+
</div>
|
|
864
|
+
<div></div>
|
|
865
|
+
<div>Filename</div>
|
|
866
|
+
<div>Resolution</div>
|
|
867
|
+
<div>Mime type</div>
|
|
868
|
+
<div>Size</div>
|
|
869
|
+
<div>Refs</div>
|
|
870
|
+
<div>Last updated</div>
|
|
871
|
+
</div>
|
|
872
|
+
<!-- Mobile header -->
|
|
873
|
+
<div
|
|
874
|
+
class="bg-muted/30 border-border text-muted-foreground flex items-center gap-3 border-b px-4 py-2 text-xs font-medium tracking-wider uppercase md:hidden"
|
|
875
|
+
>
|
|
876
|
+
<div class="w-4">
|
|
877
|
+
<Checkbox checked={allSelected} onCheckedChange={toggleSelectAll} />
|
|
878
|
+
</div>
|
|
879
|
+
<div>Assets</div>
|
|
880
|
+
</div>
|
|
881
|
+
{#each sortedAssets as asset (asset.id)}
|
|
882
|
+
<!-- Desktop row -->
|
|
883
|
+
<button
|
|
884
|
+
onclick={() => {
|
|
885
|
+
if (selectable && multiSelect) {
|
|
886
|
+
openAssetDetail(asset);
|
|
887
|
+
} else if (isSelectMode) {
|
|
888
|
+
toggleSelect(asset.id);
|
|
889
|
+
} else if (selectable && onSelect) {
|
|
890
|
+
onSelect(asset);
|
|
891
|
+
} else {
|
|
892
|
+
openAssetDetail(asset);
|
|
893
|
+
}
|
|
894
|
+
}}
|
|
895
|
+
class="border-border hidden w-full items-center gap-4 border-b px-4 py-2 text-left transition-colors md:grid md:grid-cols-[auto_40px_1fr_100px_100px_80px_50px_100px] {selectedAsset?.id ===
|
|
896
|
+
asset.id
|
|
897
|
+
? 'bg-muted'
|
|
898
|
+
: selectedIds.has(asset.id)
|
|
899
|
+
? 'bg-muted/70'
|
|
900
|
+
: 'hover:bg-muted/50'}"
|
|
901
|
+
>
|
|
902
|
+
<div class="w-4">
|
|
903
|
+
<Checkbox
|
|
904
|
+
checked={selectedIds.has(asset.id)}
|
|
905
|
+
onCheckedChange={() => toggleSelect(asset.id)}
|
|
906
|
+
onclick={(e) => e.stopPropagation()}
|
|
907
|
+
/>
|
|
908
|
+
</div>
|
|
909
|
+
<div class="bg-muted/30 h-10 w-10 overflow-hidden rounded">
|
|
910
|
+
{#if isImage(asset)}
|
|
911
|
+
<img
|
|
912
|
+
src={getThumbnailUrl(asset)}
|
|
913
|
+
alt={asset.alt || asset.originalFilename}
|
|
914
|
+
class="h-full w-full object-cover"
|
|
915
|
+
loading="lazy"
|
|
916
|
+
/>
|
|
917
|
+
{:else}
|
|
918
|
+
<div class="flex h-full items-center justify-center">
|
|
919
|
+
<FileText class="text-muted-foreground h-4 w-4" />
|
|
920
|
+
</div>
|
|
921
|
+
{/if}
|
|
922
|
+
</div>
|
|
923
|
+
<div class="min-w-0">
|
|
924
|
+
<p class="truncate text-sm">{asset.originalFilename}</p>
|
|
925
|
+
</div>
|
|
926
|
+
<div class="text-muted-foreground text-xs">
|
|
927
|
+
{asset.width && asset.height ? `${asset.width}x${asset.height}` : '-'}
|
|
928
|
+
</div>
|
|
929
|
+
<div class="text-muted-foreground text-xs">{asset.mimeType}</div>
|
|
930
|
+
<div class="text-muted-foreground text-xs">{formatSize(asset.size)}</div>
|
|
931
|
+
<div class="text-muted-foreground text-xs">{referenceCounts[asset.id] || 0}</div>
|
|
932
|
+
<div class="text-muted-foreground text-xs">
|
|
933
|
+
{formatDate(asset.updatedAt || asset.createdAt)}
|
|
934
|
+
</div>
|
|
935
|
+
</button>
|
|
936
|
+
<!-- Mobile row -->
|
|
937
|
+
<button
|
|
938
|
+
onclick={() => {
|
|
939
|
+
if (selectable && multiSelect) {
|
|
940
|
+
openAssetDetail(asset);
|
|
941
|
+
} else if (isSelectMode) {
|
|
942
|
+
toggleSelect(asset.id);
|
|
943
|
+
} else if (selectable && onSelect) {
|
|
944
|
+
onSelect(asset);
|
|
945
|
+
} else {
|
|
946
|
+
openAssetDetail(asset);
|
|
947
|
+
}
|
|
948
|
+
}}
|
|
949
|
+
class="border-border flex w-full items-center gap-3 border-b px-4 py-2 text-left transition-colors md:hidden {selectedAsset?.id ===
|
|
950
|
+
asset.id
|
|
951
|
+
? 'bg-muted'
|
|
952
|
+
: selectedIds.has(asset.id)
|
|
953
|
+
? 'bg-muted/70'
|
|
954
|
+
: 'hover:bg-muted/50'}"
|
|
955
|
+
>
|
|
956
|
+
<div class="w-4">
|
|
957
|
+
<Checkbox
|
|
958
|
+
checked={selectedIds.has(asset.id)}
|
|
959
|
+
onCheckedChange={() => toggleSelect(asset.id)}
|
|
960
|
+
onclick={(e) => e.stopPropagation()}
|
|
961
|
+
/>
|
|
962
|
+
</div>
|
|
963
|
+
<div class="bg-muted/30 h-10 w-10 shrink-0 overflow-hidden rounded">
|
|
964
|
+
{#if isImage(asset)}
|
|
965
|
+
<img
|
|
966
|
+
src={getThumbnailUrl(asset)}
|
|
967
|
+
alt={asset.alt || asset.originalFilename}
|
|
968
|
+
class="h-full w-full object-cover"
|
|
969
|
+
loading="lazy"
|
|
970
|
+
/>
|
|
971
|
+
{:else}
|
|
972
|
+
<div class="flex h-full items-center justify-center">
|
|
973
|
+
<FileText class="text-muted-foreground h-4 w-4" />
|
|
974
|
+
</div>
|
|
975
|
+
{/if}
|
|
976
|
+
</div>
|
|
977
|
+
<div class="min-w-0 flex-1">
|
|
978
|
+
<p class="truncate text-sm">{asset.originalFilename}</p>
|
|
979
|
+
<p class="text-muted-foreground text-xs">{formatSize(asset.size)}</p>
|
|
980
|
+
</div>
|
|
981
|
+
</button>
|
|
982
|
+
{/each}
|
|
983
|
+
|
|
984
|
+
<!-- Pagination -->
|
|
985
|
+
{#if totalPages > 1}
|
|
986
|
+
<div
|
|
987
|
+
class="border-border flex items-center justify-center gap-1 border-t px-4 py-3"
|
|
988
|
+
>
|
|
989
|
+
<button
|
|
990
|
+
onclick={() => goToPage(currentPage - 1)}
|
|
991
|
+
disabled={currentPage <= 1 || loading}
|
|
992
|
+
class="hover:bg-muted rounded p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
|
|
993
|
+
>
|
|
994
|
+
<ChevronLeft size={16} />
|
|
995
|
+
</button>
|
|
996
|
+
{#each visiblePages as pg}
|
|
997
|
+
{#if pg === '...'}
|
|
998
|
+
<span class="text-muted-foreground px-1.5 text-sm">...</span>
|
|
999
|
+
{:else}
|
|
1000
|
+
<button
|
|
1001
|
+
onclick={() => goToPage(pg)}
|
|
1002
|
+
disabled={loading}
|
|
1003
|
+
class="min-w-[32px] rounded px-2 py-1 text-sm font-medium transition-colors {pg ===
|
|
1004
|
+
currentPage
|
|
1005
|
+
? 'bg-foreground text-background'
|
|
1006
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
|
|
1007
|
+
>
|
|
1008
|
+
{pg}
|
|
1009
|
+
</button>
|
|
1010
|
+
{/if}
|
|
1011
|
+
{/each}
|
|
1012
|
+
<button
|
|
1013
|
+
onclick={() => goToPage(currentPage + 1)}
|
|
1014
|
+
disabled={currentPage >= totalPages || loading}
|
|
1015
|
+
class="hover:bg-muted rounded p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
|
|
1016
|
+
>
|
|
1017
|
+
<ChevronRight size={16} />
|
|
1018
|
+
</button>
|
|
1019
|
+
</div>
|
|
1020
|
+
{/if}
|
|
1021
|
+
</div>
|
|
1022
|
+
{/if}
|
|
1023
|
+
{/if}
|
|
1024
|
+
|
|
1025
|
+
{#snippet failed(error, reset)}
|
|
1026
|
+
<div class="border-destructive/30 bg-destructive/5 rounded-md border p-4 text-center">
|
|
1027
|
+
<p class="text-destructive font-medium">Media browser encountered an error</p>
|
|
1028
|
+
<p class="text-muted-foreground mt-1 text-sm">{error instanceof Error ? error.message : 'Unknown error'}</p>
|
|
1029
|
+
<button
|
|
1030
|
+
class="bg-primary text-primary-foreground mt-3 rounded px-4 py-2 text-sm"
|
|
1031
|
+
onclick={reset}
|
|
1032
|
+
>
|
|
1033
|
+
Retry
|
|
1034
|
+
</button>
|
|
1035
|
+
</div>
|
|
1036
|
+
{/snippet}
|
|
1037
|
+
</svelte:boundary>
|
|
1038
|
+
</div>
|
|
1039
|
+
|
|
1040
|
+
<!-- Asset Detail Sidebar (extends page on mobile, side panel on desktop) -->
|
|
1041
|
+
{#if selectedAsset}
|
|
1042
|
+
<div
|
|
1043
|
+
class="bg-background border-border flex flex-col border-t md:w-[350px] md:shrink-0 md:overflow-y-auto md:border-t-0 md:border-l"
|
|
1044
|
+
>
|
|
1045
|
+
<!-- Header -->
|
|
1046
|
+
<div class="border-border flex items-center justify-between border-b px-4 py-3">
|
|
1047
|
+
<!-- Back button (mobile only) -->
|
|
1048
|
+
<button
|
|
1049
|
+
onclick={closeAssetDetail}
|
|
1050
|
+
class="text-muted-foreground hover:text-foreground flex items-center gap-1 text-sm transition-colors md:hidden"
|
|
1051
|
+
>
|
|
1052
|
+
<ChevronLeft size={16} />
|
|
1053
|
+
Back
|
|
1054
|
+
</button>
|
|
1055
|
+
<!-- Filename -->
|
|
1056
|
+
<p
|
|
1057
|
+
class="min-w-0 flex-1 truncate pl-2 text-sm font-medium md:pl-0"
|
|
1058
|
+
title={selectedAsset.originalFilename}
|
|
1059
|
+
>
|
|
1060
|
+
{selectedAsset.originalFilename}
|
|
1061
|
+
</p>
|
|
1062
|
+
<div class="flex items-center gap-1">
|
|
1063
|
+
{#if !selectable}
|
|
1064
|
+
<Button
|
|
1065
|
+
variant="ghost"
|
|
1066
|
+
size="sm"
|
|
1067
|
+
class="h-7 w-7 p-0"
|
|
1068
|
+
onclick={() => deleteAsset(selectedAsset!)}
|
|
1069
|
+
title="Delete asset"
|
|
1070
|
+
>
|
|
1071
|
+
<Trash2 size={14} class="text-destructive" />
|
|
1072
|
+
</Button>
|
|
1073
|
+
{/if}
|
|
1074
|
+
<Button
|
|
1075
|
+
variant="ghost"
|
|
1076
|
+
size="sm"
|
|
1077
|
+
class="hidden h-7 w-7 p-0 md:flex"
|
|
1078
|
+
onclick={closeAssetDetail}
|
|
1079
|
+
title="Close"
|
|
1080
|
+
>
|
|
1081
|
+
<X size={14} />
|
|
1082
|
+
</Button>
|
|
1083
|
+
</div>
|
|
1084
|
+
</div>
|
|
1085
|
+
|
|
1086
|
+
<!-- Preview (click to enlarge) -->
|
|
1087
|
+
<div class="p-4 pb-0">
|
|
1088
|
+
{#if isImage(selectedAsset)}
|
|
1089
|
+
<button
|
|
1090
|
+
onclick={() => (lightboxOpen = true)}
|
|
1091
|
+
class="bg-muted/30 mb-3 w-full cursor-zoom-in overflow-hidden rounded-lg"
|
|
1092
|
+
title="Click to enlarge"
|
|
1093
|
+
>
|
|
1094
|
+
<img
|
|
1095
|
+
src={getThumbnailUrl(selectedAsset)}
|
|
1096
|
+
alt={selectedAsset.alt || selectedAsset.originalFilename}
|
|
1097
|
+
class="w-full object-contain"
|
|
1098
|
+
style="max-height: 200px;"
|
|
1099
|
+
/>
|
|
1100
|
+
</button>
|
|
1101
|
+
{:else}
|
|
1102
|
+
<div
|
|
1103
|
+
class="bg-muted/30 mb-3 flex h-28 items-center justify-center overflow-hidden rounded-lg"
|
|
1104
|
+
>
|
|
1105
|
+
<FileText class="text-muted-foreground h-12 w-12" />
|
|
1106
|
+
</div>
|
|
1107
|
+
{/if}
|
|
1108
|
+
</div>
|
|
1109
|
+
|
|
1110
|
+
<!-- Tabs -->
|
|
1111
|
+
<div class="border-border flex border-b">
|
|
1112
|
+
<button
|
|
1113
|
+
onclick={() => (detailTab = 'details')}
|
|
1114
|
+
class="flex-1 px-4 py-2.5 text-sm font-medium transition-colors {detailTab === 'details'
|
|
1115
|
+
? 'border-foreground text-foreground border-b-2'
|
|
1116
|
+
: 'text-muted-foreground hover:text-foreground'}"
|
|
1117
|
+
>
|
|
1118
|
+
Details
|
|
1119
|
+
</button>
|
|
1120
|
+
<button
|
|
1121
|
+
onclick={() => {
|
|
1122
|
+
detailTab = 'references';
|
|
1123
|
+
if (selectedAssetRefs.length === 0 && selectedAsset) {
|
|
1124
|
+
fetchAssetReferences(selectedAsset.id);
|
|
1125
|
+
}
|
|
1126
|
+
}}
|
|
1127
|
+
class="flex-1 px-4 py-2.5 text-sm font-medium transition-colors {detailTab ===
|
|
1128
|
+
'references'
|
|
1129
|
+
? 'border-foreground text-foreground border-b-2'
|
|
1130
|
+
: 'text-muted-foreground hover:text-foreground'}"
|
|
1131
|
+
>
|
|
1132
|
+
References ({selectedRefCount})
|
|
1133
|
+
</button>
|
|
1134
|
+
</div>
|
|
1135
|
+
|
|
1136
|
+
<!-- Tab content -->
|
|
1137
|
+
<div class="flex-1 overflow-y-auto p-4">
|
|
1138
|
+
{#if detailTab === 'details'}
|
|
1139
|
+
<!-- Info -->
|
|
1140
|
+
<div class="mb-4 space-y-2 text-sm">
|
|
1141
|
+
<div class="flex justify-between">
|
|
1142
|
+
<span class="text-muted-foreground">Filename</span>
|
|
1143
|
+
<span
|
|
1144
|
+
class="max-w-[180px] truncate font-medium"
|
|
1145
|
+
title={selectedAsset.originalFilename}
|
|
1146
|
+
>
|
|
1147
|
+
{selectedAsset.originalFilename}
|
|
1148
|
+
</span>
|
|
1149
|
+
</div>
|
|
1150
|
+
<div class="flex justify-between">
|
|
1151
|
+
<span class="text-muted-foreground">Type</span>
|
|
1152
|
+
<span>{selectedAsset.mimeType}</span>
|
|
1153
|
+
</div>
|
|
1154
|
+
<div class="flex justify-between">
|
|
1155
|
+
<span class="text-muted-foreground">Size</span>
|
|
1156
|
+
<span>{formatSize(selectedAsset.size)}</span>
|
|
1157
|
+
</div>
|
|
1158
|
+
{#if selectedAsset.width && selectedAsset.height}
|
|
1159
|
+
<div class="flex justify-between">
|
|
1160
|
+
<span class="text-muted-foreground">Dimensions</span>
|
|
1161
|
+
<span>{selectedAsset.width} x {selectedAsset.height}</span>
|
|
1162
|
+
</div>
|
|
1163
|
+
{/if}
|
|
1164
|
+
<div class="flex justify-between">
|
|
1165
|
+
<span class="text-muted-foreground">Uploaded</span>
|
|
1166
|
+
<span>{formatDate(selectedAsset.createdAt)}</span>
|
|
1167
|
+
</div>
|
|
1168
|
+
</div>
|
|
1169
|
+
|
|
1170
|
+
<!-- Actions -->
|
|
1171
|
+
<div class="mb-4 flex gap-2">
|
|
1172
|
+
<Button
|
|
1173
|
+
variant="outline"
|
|
1174
|
+
size="sm"
|
|
1175
|
+
class="flex-1"
|
|
1176
|
+
onclick={() => downloadAsset(selectedAsset!)}
|
|
1177
|
+
>
|
|
1178
|
+
<Download size={14} class="mr-1.5" />
|
|
1179
|
+
Download
|
|
1180
|
+
</Button>
|
|
1181
|
+
<Button
|
|
1182
|
+
variant="outline"
|
|
1183
|
+
size="sm"
|
|
1184
|
+
class="flex-1"
|
|
1185
|
+
onclick={() => copyAssetUrl(selectedAsset!)}
|
|
1186
|
+
>
|
|
1187
|
+
<Link size={14} class="mr-1.5" />
|
|
1188
|
+
{copiedUrl ? 'Copied!' : 'Copy URL'}
|
|
1189
|
+
</Button>
|
|
1190
|
+
</div>
|
|
1191
|
+
|
|
1192
|
+
<Separator class="my-4" />
|
|
1193
|
+
|
|
1194
|
+
<!-- Metadata editing -->
|
|
1195
|
+
<div class="space-y-3">
|
|
1196
|
+
<div>
|
|
1197
|
+
<Label for="asset-title" class="text-xs">Title</Label>
|
|
1198
|
+
<Input
|
|
1199
|
+
id="asset-title"
|
|
1200
|
+
bind:value={editTitle}
|
|
1201
|
+
class="mt-1 h-8 text-sm"
|
|
1202
|
+
placeholder="Asset title"
|
|
1203
|
+
/>
|
|
1204
|
+
</div>
|
|
1205
|
+
<div>
|
|
1206
|
+
<Label for="asset-description" class="text-xs">Description</Label>
|
|
1207
|
+
<textarea
|
|
1208
|
+
id="asset-description"
|
|
1209
|
+
bind:value={editDescription}
|
|
1210
|
+
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring mt-1 flex w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
|
1211
|
+
rows="2"
|
|
1212
|
+
placeholder="Description"
|
|
1213
|
+
></textarea>
|
|
1214
|
+
</div>
|
|
1215
|
+
<div>
|
|
1216
|
+
<Label for="asset-alt" class="text-xs">Alt text</Label>
|
|
1217
|
+
<Input
|
|
1218
|
+
id="asset-alt"
|
|
1219
|
+
bind:value={editAlt}
|
|
1220
|
+
class="mt-1 h-8 text-sm"
|
|
1221
|
+
placeholder="Alternative text"
|
|
1222
|
+
/>
|
|
1223
|
+
</div>
|
|
1224
|
+
<div>
|
|
1225
|
+
<Label for="asset-credit" class="text-xs">Credit line</Label>
|
|
1226
|
+
<Input
|
|
1227
|
+
id="asset-credit"
|
|
1228
|
+
bind:value={editCreditLine}
|
|
1229
|
+
class="mt-1 h-8 text-sm"
|
|
1230
|
+
placeholder="Credit / attribution"
|
|
1231
|
+
/>
|
|
1232
|
+
</div>
|
|
1233
|
+
|
|
1234
|
+
<Button onclick={saveMetadata} disabled={isSaving} size="sm" class="w-full">
|
|
1235
|
+
{isSaving ? 'Saving...' : 'Save changes'}
|
|
1236
|
+
</Button>
|
|
1237
|
+
</div>
|
|
1238
|
+
{:else}
|
|
1239
|
+
<!-- References tab -->
|
|
1240
|
+
{#if loadingRefs}
|
|
1241
|
+
<p class="text-muted-foreground text-sm">Loading references...</p>
|
|
1242
|
+
{:else if selectedAssetRefs.length === 0}
|
|
1243
|
+
<p class="text-muted-foreground text-sm">Not used in any documents</p>
|
|
1244
|
+
{:else}
|
|
1245
|
+
<div class="space-y-1">
|
|
1246
|
+
{#each selectedAssetRefs as ref (ref.documentId)}
|
|
1247
|
+
<button
|
|
1248
|
+
onclick={() => {
|
|
1249
|
+
const params = new URLSearchParams(page.url.searchParams);
|
|
1250
|
+
params.set('docType', ref.type);
|
|
1251
|
+
params.set('docId', ref.documentId);
|
|
1252
|
+
params.set('view', 'structure');
|
|
1253
|
+
params.delete('action');
|
|
1254
|
+
goto(`/admin?${params.toString()}`);
|
|
1255
|
+
}}
|
|
1256
|
+
class="hover:bg-muted flex w-full items-center gap-3 rounded-md p-2.5 text-left transition-colors"
|
|
1257
|
+
>
|
|
1258
|
+
<div class="bg-muted flex h-9 w-9 shrink-0 items-center justify-center rounded">
|
|
1259
|
+
<FileText size={16} class="text-muted-foreground" />
|
|
1260
|
+
</div>
|
|
1261
|
+
<div class="min-w-0">
|
|
1262
|
+
<p class="truncate text-sm font-medium">{ref.title}</p>
|
|
1263
|
+
<p class="text-muted-foreground truncate text-xs">
|
|
1264
|
+
{ref.type}{ref.status ? ` · ${ref.status}` : ''}
|
|
1265
|
+
</p>
|
|
1266
|
+
</div>
|
|
1267
|
+
</button>
|
|
1268
|
+
{/each}
|
|
1269
|
+
</div>
|
|
1270
|
+
{/if}
|
|
1271
|
+
{/if}
|
|
1272
|
+
</div>
|
|
1273
|
+
</div>
|
|
1274
|
+
{/if}
|
|
1275
|
+
</div>
|
|
1276
|
+
</div>
|
|
1277
|
+
|
|
1278
|
+
<!-- Lightbox Modal -->
|
|
1279
|
+
{#if selectedAsset && isImage(selectedAsset)}
|
|
1280
|
+
<Dialog.Root bind:open={lightboxOpen}>
|
|
1281
|
+
<Dialog.Content
|
|
1282
|
+
showCloseButton={false}
|
|
1283
|
+
class="flex max-h-[90vh] max-w-[90vw] flex-col overflow-hidden p-0 sm:max-w-[90vw]"
|
|
1284
|
+
>
|
|
1285
|
+
<Dialog.Header class="border-border border-b px-4 py-3">
|
|
1286
|
+
<Dialog.Title class="truncate text-sm font-medium"
|
|
1287
|
+
>{selectedAsset.originalFilename}</Dialog.Title
|
|
1288
|
+
>
|
|
1289
|
+
</Dialog.Header>
|
|
1290
|
+
<div class="flex flex-1 items-center justify-center overflow-hidden p-4">
|
|
1291
|
+
<img
|
|
1292
|
+
src={getThumbnailUrl(selectedAsset)}
|
|
1293
|
+
alt={selectedAsset.alt || selectedAsset.originalFilename}
|
|
1294
|
+
class="max-h-[70vh] max-w-full object-contain"
|
|
1295
|
+
/>
|
|
1296
|
+
</div>
|
|
1297
|
+
<div class="border-border flex items-center justify-between border-t px-4 py-3">
|
|
1298
|
+
<div class="flex items-center gap-2">
|
|
1299
|
+
<Button variant="outline" size="sm" onclick={() => downloadAsset(selectedAsset!)}>
|
|
1300
|
+
<Download size={14} class="mr-1.5" />
|
|
1301
|
+
Download
|
|
1302
|
+
</Button>
|
|
1303
|
+
<Button variant="outline" size="sm" onclick={() => copyAssetUrl(selectedAsset!)}>
|
|
1304
|
+
<Link size={14} class="mr-1.5" />
|
|
1305
|
+
{copiedUrl ? 'Copied!' : 'Copy URL'}
|
|
1306
|
+
</Button>
|
|
1307
|
+
</div>
|
|
1308
|
+
<Button variant="outline" size="sm" onclick={() => (lightboxOpen = false)}>Close</Button>
|
|
1309
|
+
</div>
|
|
1310
|
+
</Dialog.Content>
|
|
1311
|
+
</Dialog.Root>
|
|
1312
|
+
{/if}
|
|
1313
|
+
|
|
1314
|
+
<!-- Upload Modal -->
|
|
1315
|
+
<Dialog.Root
|
|
1316
|
+
bind:open={showUploadModal}
|
|
1317
|
+
onOpenChange={(v) => {
|
|
1318
|
+
if (!v && !isUploading) {
|
|
1319
|
+
showUploadModal = false;
|
|
1320
|
+
}
|
|
1321
|
+
}}
|
|
1322
|
+
>
|
|
1323
|
+
<Dialog.Content class="max-w-lg">
|
|
1324
|
+
<Dialog.Header>
|
|
1325
|
+
<Dialog.Title>Upload Assets</Dialog.Title>
|
|
1326
|
+
</Dialog.Header>
|
|
1327
|
+
|
|
1328
|
+
<!-- Drop zone -->
|
|
1329
|
+
<div
|
|
1330
|
+
class="border-border mt-2 flex flex-col items-center justify-center rounded-lg border-2 border-dashed px-6 py-10 transition-colors {modalIsDragging
|
|
1331
|
+
? 'border-primary bg-primary/5'
|
|
1332
|
+
: 'hover:bg-muted/50'}"
|
|
1333
|
+
ondragover={(e) => {
|
|
1334
|
+
e.preventDefault();
|
|
1335
|
+
modalIsDragging = true;
|
|
1336
|
+
}}
|
|
1337
|
+
ondragleave={(e) => {
|
|
1338
|
+
e.preventDefault();
|
|
1339
|
+
modalIsDragging = false;
|
|
1340
|
+
}}
|
|
1341
|
+
ondrop={(e) => {
|
|
1342
|
+
e.preventDefault();
|
|
1343
|
+
modalIsDragging = false;
|
|
1344
|
+
addFilesToQueue(e.dataTransfer?.files || null);
|
|
1345
|
+
}}
|
|
1346
|
+
role="button"
|
|
1347
|
+
tabindex="0"
|
|
1348
|
+
onclick={() => modalFileInputRef?.click()}
|
|
1349
|
+
onkeydown={(e) => {
|
|
1350
|
+
if (e.key === 'Enter' || e.key === ' ') modalFileInputRef?.click();
|
|
1351
|
+
}}
|
|
1352
|
+
>
|
|
1353
|
+
<FileImage size={32} class="text-muted-foreground mb-3" />
|
|
1354
|
+
<p class="text-sm font-medium">
|
|
1355
|
+
{modalIsDragging ? 'Drop files here' : 'Drag and drop files here'}
|
|
1356
|
+
</p>
|
|
1357
|
+
<p class="text-muted-foreground mt-1 text-xs">or click to browse</p>
|
|
1358
|
+
</div>
|
|
1359
|
+
|
|
1360
|
+
<input
|
|
1361
|
+
bind:this={modalFileInputRef}
|
|
1362
|
+
type="file"
|
|
1363
|
+
multiple
|
|
1364
|
+
accept="image/*,.pdf,.txt"
|
|
1365
|
+
class="hidden"
|
|
1366
|
+
onchange={(e) => {
|
|
1367
|
+
const target = e.target as HTMLInputElement;
|
|
1368
|
+
addFilesToQueue(target.files);
|
|
1369
|
+
target.value = '';
|
|
1370
|
+
}}
|
|
1371
|
+
/>
|
|
1372
|
+
|
|
1373
|
+
<!-- Upload queue -->
|
|
1374
|
+
{#if uploadQueue.length > 0}
|
|
1375
|
+
<div class="mt-4 max-h-48 space-y-2 overflow-y-auto">
|
|
1376
|
+
{#each uploadQueue as item}
|
|
1377
|
+
<div class="border-border flex items-center gap-3 rounded-md border px-3 py-2">
|
|
1378
|
+
<div class="min-w-0 flex-1">
|
|
1379
|
+
<p class="truncate text-sm">{item.file.name}</p>
|
|
1380
|
+
<p class="text-muted-foreground text-xs">{formatSize(item.file.size)}</p>
|
|
1381
|
+
</div>
|
|
1382
|
+
{#if item.status === 'uploading'}
|
|
1383
|
+
<div
|
|
1384
|
+
class="border-primary h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-t-transparent"
|
|
1385
|
+
></div>
|
|
1386
|
+
{:else if item.status === 'done'}
|
|
1387
|
+
<CheckCircle2 size={16} class="shrink-0 text-green-500" />
|
|
1388
|
+
{:else if item.status === 'failed'}
|
|
1389
|
+
<AlertCircle size={16} class="text-destructive shrink-0" />
|
|
1390
|
+
{:else}
|
|
1391
|
+
<div class="bg-muted h-4 w-4 shrink-0 rounded-full"></div>
|
|
1392
|
+
{/if}
|
|
1393
|
+
</div>
|
|
1394
|
+
{/each}
|
|
1395
|
+
</div>
|
|
1396
|
+
{/if}
|
|
1397
|
+
</Dialog.Content>
|
|
1398
|
+
</Dialog.Root>
|