@stacksjs/defaults 0.70.207 → 0.70.208
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/TODO.md +21 -0
- package/ai/AGENTS.md +79 -0
- package/ai/README.md +45 -0
- package/ai/claude/launch.json +12 -0
- package/ai/skills/stacks-actions/SKILL.md +128 -0
- package/ai/skills/stacks-ai/SKILL.md +276 -0
- package/ai/skills/stacks-alias/SKILL.md +94 -0
- package/ai/skills/stacks-analytics/SKILL.md +91 -0
- package/ai/skills/stacks-api/SKILL.md +738 -0
- package/ai/skills/stacks-arrays/SKILL.md +277 -0
- package/ai/skills/stacks-auth/SKILL.md +422 -0
- package/ai/skills/stacks-auto-imports/SKILL.md +138 -0
- package/ai/skills/stacks-brandkit/SKILL.md +811 -0
- package/ai/skills/stacks-browse/SKILL.md +127 -0
- package/ai/skills/stacks-browse/scripts/browse.ts +492 -0
- package/ai/skills/stacks-browser/SKILL.md +145 -0
- package/ai/skills/stacks-buddy/SKILL.md +676 -0
- package/ai/skills/stacks-build/SKILL.md +163 -0
- package/ai/skills/stacks-cache/SKILL.md +125 -0
- package/ai/skills/stacks-calendar/SKILL.md +113 -0
- package/ai/skills/stacks-chat/SKILL.md +371 -0
- package/ai/skills/stacks-cli/SKILL.md +112 -0
- package/ai/skills/stacks-cloud/SKILL.md +192 -0
- package/ai/skills/stacks-cms/SKILL.md +217 -0
- package/ai/skills/stacks-collections/SKILL.md +82 -0
- package/ai/skills/stacks-commerce/SKILL.md +114 -0
- package/ai/skills/stacks-composables/SKILL.md +161 -0
- package/ai/skills/stacks-config/SKILL.md +144 -0
- package/ai/skills/stacks-configuration/SKILL.md +154 -0
- package/ai/skills/stacks-cron/SKILL.md +123 -0
- package/ai/skills/stacks-crosswind/SKILL.md +190 -0
- package/ai/skills/stacks-dashboard/SKILL.md +173 -0
- package/ai/skills/stacks-database/SKILL.md +286 -0
- package/ai/skills/stacks-datetime/SKILL.md +269 -0
- package/ai/skills/stacks-dependencies/SKILL.md +111 -0
- package/ai/skills/stacks-deploy/SKILL.md +102 -0
- package/ai/skills/stacks-design-brutalist/SKILL.md +110 -0
- package/ai/skills/stacks-design-minimalist/SKILL.md +115 -0
- package/ai/skills/stacks-design-output/SKILL.md +55 -0
- package/ai/skills/stacks-design-soft/SKILL.md +156 -0
- package/ai/skills/stacks-design-taste/SKILL.md +1012 -0
- package/ai/skills/stacks-design-taste-codex/SKILL.md +294 -0
- package/ai/skills/stacks-desktop/SKILL.md +78 -0
- package/ai/skills/stacks-development/SKILL.md +298 -0
- package/ai/skills/stacks-dns/SKILL.md +149 -0
- package/ai/skills/stacks-docs/SKILL.md +109 -0
- package/ai/skills/stacks-email/SKILL.md +236 -0
- package/ai/skills/stacks-enums/SKILL.md +100 -0
- package/ai/skills/stacks-env/SKILL.md +181 -0
- package/ai/skills/stacks-error-handling/SKILL.md +137 -0
- package/ai/skills/stacks-events/SKILL.md +228 -0
- package/ai/skills/stacks-faker/SKILL.md +158 -0
- package/ai/skills/stacks-git/SKILL.md +158 -0
- package/ai/skills/stacks-guard/SKILL.md +98 -0
- package/ai/skills/stacks-health/SKILL.md +75 -0
- package/ai/skills/stacks-http/SKILL.md +316 -0
- package/ai/skills/stacks-i18n/SKILL.md +198 -0
- package/ai/skills/stacks-image-to-code/SKILL.md +629 -0
- package/ai/skills/stacks-imagegen-mobile/SKILL.md +1474 -0
- package/ai/skills/stacks-imagegen-web/SKILL.md +998 -0
- package/ai/skills/stacks-investigate/SKILL.md +119 -0
- package/ai/skills/stacks-jobs/SKILL.md +136 -0
- package/ai/skills/stacks-lint/SKILL.md +184 -0
- package/ai/skills/stacks-listeners/SKILL.md +99 -0
- package/ai/skills/stacks-logging/SKILL.md +78 -0
- package/ai/skills/stacks-mail/SKILL.md +120 -0
- package/ai/skills/stacks-middleware/SKILL.md +249 -0
- package/ai/skills/stacks-migrations/SKILL.md +105 -0
- package/ai/skills/stacks-models/SKILL.md +267 -0
- package/ai/skills/stacks-new-feature/SKILL.md +206 -0
- package/ai/skills/stacks-notifications/SKILL.md +210 -0
- package/ai/skills/stacks-objects/SKILL.md +205 -0
- package/ai/skills/stacks-office-hours/SKILL.md +134 -0
- package/ai/skills/stacks-orm/SKILL.md +342 -0
- package/ai/skills/stacks-path/SKILL.md +118 -0
- package/ai/skills/stacks-payments/SKILL.md +373 -0
- package/ai/skills/stacks-plan-review/SKILL.md +152 -0
- package/ai/skills/stacks-plugins/SKILL.md +149 -0
- package/ai/skills/stacks-push/SKILL.md +376 -0
- package/ai/skills/stacks-query-builder/SKILL.md +128 -0
- package/ai/skills/stacks-queue/SKILL.md +559 -0
- package/ai/skills/stacks-realtime/SKILL.md +442 -0
- package/ai/skills/stacks-redesign/SKILL.md +182 -0
- package/ai/skills/stacks-registry/SKILL.md +58 -0
- package/ai/skills/stacks-repl/SKILL.md +80 -0
- package/ai/skills/stacks-retro/SKILL.md +122 -0
- package/ai/skills/stacks-review/SKILL.md +135 -0
- package/ai/skills/stacks-router/SKILL.md +231 -0
- package/ai/skills/stacks-routes/SKILL.md +105 -0
- package/ai/skills/stacks-scaffolding/SKILL.md +162 -0
- package/ai/skills/stacks-scheduler/SKILL.md +363 -0
- package/ai/skills/stacks-search-engine/SKILL.md +112 -0
- package/ai/skills/stacks-security/SKILL.md +129 -0
- package/ai/skills/stacks-security-audit/SKILL.md +128 -0
- package/ai/skills/stacks-server/SKILL.md +458 -0
- package/ai/skills/stacks-shell/SKILL.md +62 -0
- package/ai/skills/stacks-slug/SKILL.md +87 -0
- package/ai/skills/stacks-sms/SKILL.md +385 -0
- package/ai/skills/stacks-socials/SKILL.md +303 -0
- package/ai/skills/stacks-storage/SKILL.md +576 -0
- package/ai/skills/stacks-strings/SKILL.md +416 -0
- package/ai/skills/stacks-stx/SKILL.md +176 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +22 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +290 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +4 -0
- package/ai/skills/stacks-technical-diagrams/assets/template.html +1252 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +289 -0
- package/ai/skills/stacks-technical-diagrams/bunfig.toml +1 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +64 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +88 -0
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +75 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +70 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +41 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +62 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +371 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +295 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +91 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +384 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +84 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +257 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +13 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +230 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +88 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +103 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +481 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +68 -0
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +121 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +43 -0
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +221 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +241 -0
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +186 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +332 -0
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +297 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +28 -0
- package/ai/skills/stacks-testing/SKILL.md +129 -0
- package/ai/skills/stacks-tunnel/SKILL.md +125 -0
- package/ai/skills/stacks-types/SKILL.md +178 -0
- package/ai/skills/stacks-ui/SKILL.md +246 -0
- package/ai/skills/stacks-utils/SKILL.md +444 -0
- package/ai/skills/stacks-validation/SKILL.md +371 -0
- package/ai/skills/stacks-whois/SKILL.md +315 -0
- package/assets/world-map-data.ts +121 -0
- package/bootstrap.ts +114 -0
- package/docs/api-examples.md +46 -0
- package/docs/index.md +24 -0
- package/docs/markdown-examples.md +87 -0
- package/functions/analytics.ts +97 -0
- package/functions/api.ts +143 -0
- package/functions/auth.ts +208 -0
- package/functions/billing/payments.ts +149 -0
- package/functions/blog.ts +120 -0
- package/functions/cms/categorizables.ts +107 -0
- package/functions/cms/pages.ts +108 -0
- package/functions/cms/posts.ts +100 -0
- package/functions/cms/taggables.ts +102 -0
- package/functions/commerce/coupons.ts +128 -0
- package/functions/commerce/customers.ts +128 -0
- package/functions/commerce/gift-cards.ts +119 -0
- package/functions/commerce/orders.ts +112 -0
- package/functions/commerce/payments.ts +119 -0
- package/functions/commerce/products/categories.ts +123 -0
- package/functions/commerce/products/manufacturers.ts +123 -0
- package/functions/commerce/products/products.ts +123 -0
- package/functions/commerce/products/reviews.ts +123 -0
- package/functions/commerce/products/units.ts +123 -0
- package/functions/commerce/products/variants.ts +123 -0
- package/functions/commerce/shippings/delivery-routes.ts +123 -0
- package/functions/commerce/shippings/digital-deliveries.ts +119 -0
- package/functions/commerce/shippings/drivers.ts +123 -0
- package/functions/commerce/shippings/license-keys.ts +123 -0
- package/functions/commerce/shippings/shipping-methods.ts +123 -0
- package/functions/commerce/shippings/shipping-rates.ts +119 -0
- package/functions/commerce/shippings/shipping-zones.ts +119 -0
- package/functions/commerce/taxes.ts +119 -0
- package/functions/commerce/waitlist/products.ts +123 -0
- package/functions/commerce/waitlist/restaurants.ts +123 -0
- package/functions/craft.ts +107 -0
- package/functions/dashboard.ts +153 -0
- package/functions/inbox.ts +95 -0
- package/functions/jobs.ts +86 -0
- package/functions/monitoring/errors.ts +295 -0
- package/functions/queue.ts +111 -0
- package/functions/realtime/websockets.ts +96 -0
- package/functions/realtime.ts +52 -0
- package/functions/releases.ts +49 -0
- package/functions/search.ts +126 -0
- package/functions/toasts.ts +95 -0
- package/functions/uploads.ts +216 -0
- package/ide/.gitignore +4 -0
- package/ide/cursor/rules/auto-imports.mdc +574 -0
- package/ide/cursor/rules/code-style.mdc +12 -0
- package/ide/cursor/rules/documentation.mdc +9 -0
- package/ide/cursor/rules/error-handling-validation.mdc +11 -0
- package/ide/cursor/rules/key-conventions.mdc +11 -0
- package/ide/cursor/rules/project-structure.mdc +169 -0
- package/ide/cursor/rules/readme.mdc +318 -0
- package/ide/cursor/rules/syntax-formatting.mdc +9 -0
- package/ide/cursor/rules/testing.mdc +10 -0
- package/ide/cursor/rules/typescript.mdc +9 -0
- package/ide/cursor/rules/ui-styling.mdc +0 -0
- package/ide/dictionary.txt +397 -0
- package/ide/jetbrains/.fleet/settings.json +4 -0
- package/ide/jetbrains/.idea/.gitignore +5 -0
- package/ide/jetbrains/.idea/.name +1 -0
- package/ide/jetbrains/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/ide/jetbrains/.idea/jsLinters/eslint.xml +7 -0
- package/ide/jetbrains/.idea/modules.xml +8 -0
- package/ide/jetbrains/.idea/stacks.iml +22 -0
- package/ide/jetbrains/.idea/vcs.xml +6 -0
- package/ide/jetbrains/.idea/webResources.xml +14 -0
- package/ide/jetbrains/IntelliJ IDEA Global Settings +0 -0
- package/ide/jetbrains/installed.txt +4 -0
- package/ide/jetbrains/options/github-copilot.xml +5 -0
- package/ide/jetbrains/options/nodejs.xml +7 -0
- package/ide/jetbrains/options/templates.xml +124 -0
- package/ide/jetbrains/templates/JavaScript.xml +6 -0
- package/ide/jetbrains/templates/Stacks.xml +8 -0
- package/ide/vscode/.vscode/README.md +23 -0
- package/ide/vscode/.vscode/extensions.json +5 -0
- package/ide/vscode/.vscode/settings.json +176 -0
- package/ide/vscode/.vscode/stacks.code-snippets +181 -0
- package/ide/vscode/LICENSE.md +21 -0
- package/ide/vscode/README.md +36 -0
- package/ide/vscode/logo.png +0 -0
- package/ide/vscode/package.json +79 -0
- package/ide/zed/.zed/settings.json +139 -0
- package/lang/README.md +28 -0
- package/lang/de.yml +14 -0
- package/lang/en.yml +15 -0
- package/package.json +19 -3
- package/project/bootstrap +161 -0
- package/project/buddy +175 -0
- package/project/storage/framework/tsconfig.app.json +50 -0
- package/project/storage/framework/tsconfig.base.json +58 -0
- package/routes/core.ts +78 -0
- package/routes/dashboard-api.ts +233 -0
- package/routes/dashboard.ts +739 -0
- package/routes/email.ts +129 -0
- package/stores/[name].stx +16 -0
- package/stores/git.ts +71 -0
- package/stores/payment.ts +474 -0
- package/stores/queue.ts +43 -0
- package/stores/user.ts +29 -0
- package/stx-components-plugin.ts +25 -0
- package/styles/design-tokens.css +597 -0
- package/types/dashboard.ts +70 -0
- package/types/defaults.ts +438 -0
- package/vcs/github/CODE_OF_CONDUCT.md +83 -0
- package/vcs/github/CONTRIBUTING.md +187 -0
- package/vcs/github/EXPLANATIONS.md +41 -0
- package/vcs/github/FUNDING.yml +1 -0
- package/vcs/github/ISSUE_TEMPLATE/bug_report.yml +49 -0
- package/vcs/github/ISSUE_TEMPLATE/config.yml +10 -0
- package/vcs/github/ISSUE_TEMPLATE/feature_request.yml +44 -0
- package/vcs/github/SECURITY.md +70 -0
- package/vcs/github/renovate.json +5 -0
- package/vcs/github/stale.yml +14 -0
- package/vcs/github/workflows/README.md +16 -0
- package/vcs/github/workflows/ci.yml +114 -0
- package/vcs/github/workflows/export-size.yml +25 -0
- package/vcs/github/workflows/labeler.yml +13 -0
- package/vcs/github/workflows/release.yml +219 -0
- package/views/[...all].stx +8 -0
- package/views/about.md +3 -0
- package/views/auth/login.stx +1 -0
- package/views/auth/signup.stx +85 -0
- package/views/dashboard/.discovered-models.json +542 -0
- package/views/dashboard/AUDIT.md +904 -0
- package/views/dashboard/[...all].stx +89 -0
- package/views/dashboard/access-tokens/index.stx +177 -0
- package/views/dashboard/actions/index.stx +129 -0
- package/views/dashboard/analytics/blog/index.stx +356 -0
- package/views/dashboard/analytics/browsers/index.stx +263 -0
- package/views/dashboard/analytics/commerce/sales/index.stx +329 -0
- package/views/dashboard/analytics/commerce/web/index.stx +379 -0
- package/views/dashboard/analytics/countries/index.stx +289 -0
- package/views/dashboard/analytics/devices/index.stx +305 -0
- package/views/dashboard/analytics/events/index.stx +536 -0
- package/views/dashboard/analytics/index.stx +88 -0
- package/views/dashboard/analytics/marketing/index.stx +315 -0
- package/views/dashboard/analytics/pages/index.stx +271 -0
- package/views/dashboard/analytics/referrers/index.stx +319 -0
- package/views/dashboard/analytics/web/index.stx +356 -0
- package/views/dashboard/buddy/index.stx +198 -0
- package/views/dashboard/ci/index.stx +462 -0
- package/views/dashboard/cloud/index.stx +452 -0
- package/views/dashboard/commands/index.stx +295 -0
- package/views/dashboard/commerce/categories/index.stx +406 -0
- package/views/dashboard/commerce/coupons/index.stx +485 -0
- package/views/dashboard/commerce/customers/index.stx +410 -0
- package/views/dashboard/commerce/dashboard/index.stx +287 -0
- package/views/dashboard/commerce/delivery/delivery-routes.stx +305 -0
- package/views/dashboard/commerce/delivery/digital-delivery.stx +332 -0
- package/views/dashboard/commerce/delivery/drivers.stx +397 -0
- package/views/dashboard/commerce/delivery/index.stx +18 -0
- package/views/dashboard/commerce/delivery/license-keys.stx +380 -0
- package/views/dashboard/commerce/delivery/shipping-methods.stx +346 -0
- package/views/dashboard/commerce/delivery/shipping-rates.stx +338 -0
- package/views/dashboard/commerce/delivery/shipping-zones.stx +302 -0
- package/views/dashboard/commerce/delivery/tabs.ts +25 -0
- package/views/dashboard/commerce/gift-cards/index.stx +561 -0
- package/views/dashboard/commerce/manufacturers/index.stx +308 -0
- package/views/dashboard/commerce/orders/index.stx +247 -0
- package/views/dashboard/commerce/payments/index.stx +535 -0
- package/views/dashboard/commerce/pos/index.stx +653 -0
- package/views/dashboard/commerce/printers/devices/index.stx +273 -0
- package/views/dashboard/commerce/printers/receipts/index.stx +308 -0
- package/views/dashboard/commerce/products/detail.stx +563 -0
- package/views/dashboard/commerce/products/index.stx +370 -0
- package/views/dashboard/commerce/reviews/index.stx +656 -0
- package/views/dashboard/commerce/reviews/index.ts +0 -0
- package/views/dashboard/commerce/taxes/index.stx +377 -0
- package/views/dashboard/commerce/units/index.stx +317 -0
- package/views/dashboard/commerce/variants/index.stx +319 -0
- package/views/dashboard/commerce/waitlist/products.stx +942 -0
- package/views/dashboard/commerce/waitlist/restaurant.stx +1024 -0
- package/views/dashboard/components/index.stx +330 -0
- package/views/dashboard/composables/index.ts +19 -0
- package/views/dashboard/composables/useChart.ts +77 -0
- package/views/dashboard/composables/useNavigation.ts +99 -0
- package/views/dashboard/composables/useRole.ts +58 -0
- package/views/dashboard/composables/useTheme.ts +74 -0
- package/views/dashboard/content/authors/index.stx +171 -0
- package/views/dashboard/content/blog/index.stx +493 -0
- package/views/dashboard/content/categories/index.stx +169 -0
- package/views/dashboard/content/comments/index.stx +189 -0
- package/views/dashboard/content/dashboard.stx +383 -0
- package/views/dashboard/content/files/index.stx +706 -0
- package/views/dashboard/content/pages/index.stx +174 -0
- package/views/dashboard/content/posts/index.stx +269 -0
- package/views/dashboard/content/seo/index.stx +240 -0
- package/views/dashboard/content/tags/index.stx +167 -0
- package/views/dashboard/data/activity/index.stx +261 -0
- package/views/dashboard/data/dashboard/index.stx +116 -0
- package/views/dashboard/data/subscribers.stx +270 -0
- package/views/dashboard/data/teams.stx +275 -0
- package/views/dashboard/data/users.stx +279 -0
- package/views/dashboard/dependencies/index.stx +178 -0
- package/views/dashboard/deployments/[id].stx +234 -0
- package/views/dashboard/deployments/index.stx +9 -0
- package/views/dashboard/dns/index.stx +163 -0
- package/views/dashboard/environment/index.stx +283 -0
- package/views/dashboard/errors/index.stx +274 -0
- package/views/dashboard/forgot-password.stx +57 -0
- package/views/dashboard/functions/index.stx +159 -0
- package/views/dashboard/health/index.stx +248 -0
- package/views/dashboard/inbox/activity.stx +350 -0
- package/views/dashboard/inbox/index.stx +452 -0
- package/views/dashboard/inbox/settings.stx +290 -0
- package/views/dashboard/index.stx +252 -0
- package/views/dashboard/insights/index.stx +529 -0
- package/views/dashboard/jobs/[id].stx +430 -0
- package/views/dashboard/jobs/history.stx +222 -0
- package/views/dashboard/jobs/index.stx +214 -0
- package/views/dashboard/kanban/[id].stx +899 -0
- package/views/dashboard/kanban/index.stx +260 -0
- package/views/dashboard/layouts/default.stx +565 -0
- package/views/dashboard/layouts/guest.stx +10 -0
- package/views/dashboard/login.stx +48 -0
- package/views/dashboard/logs/index.stx +540 -0
- package/views/dashboard/mailboxes/index.stx +149 -0
- package/views/dashboard/management/permissions/index.stx +490 -0
- package/views/dashboard/marketing/campaigns/index.stx +604 -0
- package/views/dashboard/marketing/lists/index.stx +514 -0
- package/views/dashboard/marketing/reviews/index.stx +475 -0
- package/views/dashboard/marketing/social-posts/index.stx +560 -0
- package/views/dashboard/models/[model].stx +195 -0
- package/views/dashboard/models/index.stx +159 -0
- package/views/dashboard/monitoring/errors/index.stx +415 -0
- package/views/dashboard/notifications/dashboard.stx +383 -0
- package/views/dashboard/notifications/email.stx +336 -0
- package/views/dashboard/notifications/history.stx +373 -0
- package/views/dashboard/notifications/sms.stx +332 -0
- package/views/dashboard/packages/index.stx +195 -0
- package/views/dashboard/queries/[id].stx +381 -0
- package/views/dashboard/queries/history.stx +227 -0
- package/views/dashboard/queries/index.stx +335 -0
- package/views/dashboard/queries/slow.stx +318 -0
- package/views/dashboard/queue/index.stx +427 -0
- package/views/dashboard/realtime/index.stx +429 -0
- package/views/dashboard/register.stx +46 -0
- package/views/dashboard/releases/index.stx +389 -0
- package/views/dashboard/requests/index.stx +462 -0
- package/views/dashboard/serverless/index.stx +475 -0
- package/views/dashboard/servers/[id].stx +299 -0
- package/views/dashboard/servers/index.stx +483 -0
- package/views/dashboard/settings/billing.stx +50 -0
- package/views/dashboard/settings/index.stx +256 -0
- package/views/dashboard/settings/mail.stx +252 -0
- package/views/dashboard/stores/auth.ts +123 -0
- package/views/dashboard/stores/ci.ts +342 -0
- package/views/dashboard/stores/index.ts +129 -0
- package/views/dashboard/stores/kanban.ts +723 -0
- package/views/dashboard/stores/rbac.ts +350 -0
- package/views/dashboard/stx.config.ts +34 -0
- package/views/dashboard/teams/[id].stx +170 -0
- package/views/forum/category.stx +92 -0
- package/views/forum/index.stx +209 -0
- package/views/forum/new-topic.stx +113 -0
- package/views/forum/profile.stx +1 -0
- package/views/forum/topic.stx +143 -0
- package/views/hello-world/[name].stx +47 -0
- package/views/index.stx +21 -0
- package/views/system-tray/index.stx +147 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
@title('Dashboard - File Manager')
|
|
5
|
+
<script server>
|
|
6
|
+
// Seed the grid from a real Storage disk (stacksjs/stacks#245). Defaults to the
|
|
7
|
+
// `public` disk, which carries real media + public URLs so image/video previews
|
|
8
|
+
// render. Best-effort: any failure leaves the payload empty and the client
|
|
9
|
+
// script falls back to the built-in demo tree below.
|
|
10
|
+
let serverFileTreeJson = ''
|
|
11
|
+
try {
|
|
12
|
+
const { buildStorageFileTree } = await import('../../../../resources/functions/dashboard/files-tree')
|
|
13
|
+
const tree = await buildStorageFileTree({ disk: 'public' })
|
|
14
|
+
if (tree)
|
|
15
|
+
serverFileTreeJson = JSON.stringify(tree)
|
|
16
|
+
}
|
|
17
|
+
catch {}
|
|
18
|
+
</script>
|
|
19
|
+
<div class="flex flex-col h-full">
|
|
20
|
+
<div data-file-tree hidden>{{ serverFileTreeJson }}</div>
|
|
21
|
+
<div class="flex flex-1">
|
|
22
|
+
<div :class="sidePanelClass()">
|
|
23
|
+
<div :show="showSidePanel" class="flex flex-col h-full">
|
|
24
|
+
<div class="p-4 border-b border-black/5 dark:border-white/10">
|
|
25
|
+
<h3 class="font-semibold text-sm text-stone-500 tracking-wider uppercase dark:text-neutral-400">Storage</h3>
|
|
26
|
+
<div class="mt-2" style="height: 120px;"><canvas id="files_storage_chart"></canvas></div>
|
|
27
|
+
<div class="mt-2 text-stone-500 text-xs dark:text-neutral-400">
|
|
28
|
+
<div class="flex justify-between py-0.5"><span>Used</span><span>{{ usedLabel }}</span></div>
|
|
29
|
+
<div class="flex justify-between py-0.5"><span>Available</span><span>{{ availableLabel }}</span></div>
|
|
30
|
+
<div class="flex justify-between py-0.5 font-medium text-stone-700 dark:text-neutral-300"><span>Total</span><span>{{ totalLabel }}</span></div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="p-4 border-b border-black/5 dark:border-white/10">
|
|
35
|
+
<h3 class="font-semibold text-sm text-stone-500 tracking-wider uppercase dark:text-neutral-400">Storage by Type</h3>
|
|
36
|
+
<div class="mt-2" style="height: 120px;"><canvas id="files_type_chart"></canvas></div>
|
|
37
|
+
<div class="mt-2 text-stone-500 text-xs dark:text-neutral-400">
|
|
38
|
+
<div class="flex justify-between py-0.5"><span>Documents</span><span>{{ docsLabel }}</span></div>
|
|
39
|
+
<div class="flex justify-between py-0.5"><span>Images</span><span>{{ imgsLabel }}</span></div>
|
|
40
|
+
<div class="flex justify-between py-0.5"><span>Videos</span><span>{{ videosLabel }}</span></div>
|
|
41
|
+
<div class="flex justify-between py-0.5"><span>Other</span><span>{{ otherLabel }}</span></div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<nav class="overflow-y-auto flex-1 px-2 py-2">
|
|
46
|
+
<div class="space-y-px">
|
|
47
|
+
<button @click="navigateToRoot()" :class="navRowClass(currentFolderId() === root().id)">
|
|
48
|
+
<div class="h-[18px] w-[18px] text-blue-500 i-hugeicons-home-02"></div>
|
|
49
|
+
Home
|
|
50
|
+
</button>
|
|
51
|
+
<button :class="navRowClass(false)">
|
|
52
|
+
<div class="h-[18px] w-[18px] text-amber-400 i-hugeicons-star"></div>
|
|
53
|
+
Starred
|
|
54
|
+
<span class="ml-auto tabular-nums text-[12px] text-stone-400 dark:text-neutral-500">{{ starredCount }}</span>
|
|
55
|
+
</button>
|
|
56
|
+
<button :class="navRowClass(false)">
|
|
57
|
+
<div class="h-[18px] w-[18px] text-emerald-500 i-hugeicons-share-01"></div>
|
|
58
|
+
Shared
|
|
59
|
+
<span class="ml-auto tabular-nums text-[12px] text-stone-400 dark:text-neutral-500">{{ sharedCount }}</span>
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="mt-5">
|
|
63
|
+
<h3 class="mb-1 px-2.5 font-semibold text-[11px] text-stone-400 dark:text-neutral-500">Folders</h3>
|
|
64
|
+
<div class="space-y-px">
|
|
65
|
+
<template :for="folder in topLevelFolders">
|
|
66
|
+
<button @click="navigateToFolder(folder)" :class="navRowClass(currentFolderId() === folder.id)">
|
|
67
|
+
<div class="h-[18px] w-[18px] text-amber-400 i-hugeicons-folder-02"></div>
|
|
68
|
+
{{ folder.name }}
|
|
69
|
+
<span class="ml-auto tabular-nums text-[12px] text-stone-400 dark:text-neutral-500">{{ (folder.items ?? []).length }}</span>
|
|
70
|
+
</button>
|
|
71
|
+
</template>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</nav>
|
|
75
|
+
|
|
76
|
+
<div class="p-3 border-black/5 border-t dark:border-white/10">
|
|
77
|
+
<button @click="openUploadModal()" class="flex gap-2 items-center justify-center px-4 py-2 w-full font-medium text-[13px] text-white bg-blue-500 hover:bg-blue-600 rounded-lg shadow-sm">
|
|
78
|
+
<div class="mr-2 h-5 w-5 i-hugeicons-upload-01"></div>
|
|
79
|
+
Upload Files
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="flex flex-1 flex-col bg-white dark:bg-neutral-900">
|
|
86
|
+
<div class="p-4 bg-white/70 dark:bg-neutral-900/50 border-b border-black/5 dark:border-white/10 backdrop-blur-xl">
|
|
87
|
+
<div class="flex items-center justify-between">
|
|
88
|
+
<button @click="toggleSidePanel()" class="p-2 text-gray-500 dark:text-neutral-400 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded-md">
|
|
89
|
+
<div class="h-5 w-5 i-hugeicons-menu-01"></div>
|
|
90
|
+
</button>
|
|
91
|
+
|
|
92
|
+
<div class="ml-4 max-w-lg w-full lg:max-w-xs">
|
|
93
|
+
<div class="relative">
|
|
94
|
+
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
|
95
|
+
<div class="h-5 w-5 text-gray-400 i-hugeicons-search-01"></div>
|
|
96
|
+
</div>
|
|
97
|
+
<input x-model="searchQuery" type="search" placeholder="Search files and folders..." class="block placeholder-gray-500 dark:placeholder-gray-400 pl-10 pr-3 py-2 w-full leading-5 text-gray-900 sm:text-sm dark:text-white bg-white dark:bg-neutral-700 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:outline-none focus:ring-blue-500" />
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="flex items-center space-x-2">
|
|
102
|
+
<div class="flex gap-0.5 p-0.5 bg-black/[0.05] dark:bg-white/[0.06] rounded-lg">
|
|
103
|
+
<button @click="setViewMode('grid')" :class="gridViewClass()">
|
|
104
|
+
<div class="h-5 w-5 i-hugeicons-grid-view"></div>
|
|
105
|
+
</button>
|
|
106
|
+
<button @click="setViewMode('list')" :class="listViewClass()">
|
|
107
|
+
<div class="h-5 w-5 i-hugeicons-right-to-left-list-number"></div>
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<select x-model="sortBy" class="block pl-3 pr-10 py-2 w-full text-base text-gray-900 sm:text-sm dark:text-white bg-white dark:bg-neutral-700 border-gray-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:outline-none focus:ring-blue-500">
|
|
112
|
+
<option value="name">Name</option>
|
|
113
|
+
<option value="lastModified">Date</option>
|
|
114
|
+
<option value="size">Size</option>
|
|
115
|
+
</select>
|
|
116
|
+
|
|
117
|
+
<button @click="toggleSortOrder()" class="p-2 text-gray-500 dark:text-neutral-400 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded-md">
|
|
118
|
+
<div :class="sortOrderIconClass()"></div>
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div class="flex items-center space-x-2">
|
|
123
|
+
<button @click="openCreateFolderModal()" class="inline-flex items-center px-3 py-2 font-medium text-sm text-white bg-indigo-600 hover:bg-indigo-700 rounded-md">
|
|
124
|
+
<div class="mr-2 h-4 w-4 i-hugeicons-folder-add"></div>
|
|
125
|
+
New Folder
|
|
126
|
+
</button>
|
|
127
|
+
<button @click="openUploadModal()" class="inline-flex items-center px-3 py-2 font-medium text-sm text-white bg-indigo-600 hover:bg-indigo-700 rounded-md">
|
|
128
|
+
<div class="mr-2 h-4 w-4 i-hugeicons-upload-01"></div>
|
|
129
|
+
Upload
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<nav class="flex mt-4">
|
|
135
|
+
<ol class="flex items-center space-x-1">
|
|
136
|
+
<template :for="(path, index) in currentPath">
|
|
137
|
+
<li>
|
|
138
|
+
<div class="flex items-center">
|
|
139
|
+
<span :show="index > 0" class="mx-1 text-gray-400 dark:text-neutral-500">/</span>
|
|
140
|
+
<button @click="navigateToPath(index)" class="font-medium text-gray-500 text-sm dark:text-neutral-400 hover:text-gray-700 dark:hover:text-gray-300">{{ path }}</button>
|
|
141
|
+
</div>
|
|
142
|
+
</li>
|
|
143
|
+
</template>
|
|
144
|
+
</ol>
|
|
145
|
+
</nav>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div class="overflow-auto flex-1 p-4 bg-stone-50/50 dark:bg-neutral-900">
|
|
149
|
+
<div :show="filteredItems().length === 0" class="flex flex-col items-center justify-center h-full text-center">
|
|
150
|
+
<div class="h-16 w-16 text-gray-400 dark:text-neutral-600 i-hugeicons-folder-02"></div>
|
|
151
|
+
<h3 class="mt-2 font-medium text-gray-900 text-sm dark:text-white">No items</h3>
|
|
152
|
+
<p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">{{ emptyHint }}</p>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div :show="filteredItems().length > 0 && viewMode() === 'grid'" class="grid gap-4 grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
|
|
156
|
+
<template :for="item in filteredItems">
|
|
157
|
+
<div @click="onItemClick(item)" :class="gridCardClass(item)">
|
|
158
|
+
<MediaCard :item="item" />
|
|
159
|
+
<div class="flex absolute right-2 top-2 space-x-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
160
|
+
<button @click="onStarClick(item, $event)" :class="gridStarButtonClass(item)">
|
|
161
|
+
<div class="h-4 w-4 i-hugeicons-star"></div>
|
|
162
|
+
</button>
|
|
163
|
+
<button @click="onShareClick(item, $event)" :class="gridShareButtonClass(item)">
|
|
164
|
+
<div class="h-4 w-4 i-hugeicons-share-01"></div>
|
|
165
|
+
</button>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</template>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div :show="filteredItems().length > 0 && viewMode() === 'list'" class="overflow-hidden ring-1 ring-stone-200/80 rounded-xl dark:ring-neutral-700 shadow-sm">
|
|
172
|
+
<table class="min-w-full divide-stone-100 divide-y dark:divide-neutral-700">
|
|
173
|
+
<thead class="bg-stone-50/80 dark:bg-neutral-800">
|
|
174
|
+
<tr>
|
|
175
|
+
<th class="pl-6 pr-3 py-3.5 font-semibold text-left text-stone-500 text-xs tracking-wider uppercase dark:text-neutral-400">Name</th>
|
|
176
|
+
<th class="px-3 py-3.5 font-semibold text-left text-stone-500 text-xs tracking-wider uppercase dark:text-neutral-400">Size</th>
|
|
177
|
+
<th class="px-3 py-3.5 font-semibold text-left text-stone-500 text-xs tracking-wider uppercase dark:text-neutral-400">Modified</th>
|
|
178
|
+
<th class="relative pl-3 pr-6 py-3.5"><span class="sr-only">Actions</span></th>
|
|
179
|
+
</tr>
|
|
180
|
+
</thead>
|
|
181
|
+
<tbody class="bg-white dark:bg-neutral-800 divide-stone-100 divide-y dark:divide-neutral-700">
|
|
182
|
+
<template :for="item in filteredItems">
|
|
183
|
+
<tr @click="onItemClick(item)" class="hover:bg-stone-50/50 dark:hover:bg-neutral-700/50 transition-colors cursor-pointer">
|
|
184
|
+
<td class="pl-6 pr-3 py-3.5 text-sm whitespace-nowrap">
|
|
185
|
+
<div class="flex items-center">
|
|
186
|
+
<div :class="rowIconClass(item)"></div>
|
|
187
|
+
<div class="font-medium text-stone-800 dark:text-white">{{ item.name }}</div>
|
|
188
|
+
</div>
|
|
189
|
+
</td>
|
|
190
|
+
<td class="px-3 py-3.5 text-sm text-stone-500 whitespace-nowrap dark:text-neutral-400">{{ item.type === 'folder' ? '-' : formatFileSize(item.size ?? 0) }}</td>
|
|
191
|
+
<td class="px-3 py-3.5 text-sm text-stone-500 whitespace-nowrap dark:text-neutral-400">{{ formatDate(item.lastModified) }}</td>
|
|
192
|
+
<td class="relative pl-3 pr-6 py-3.5 font-medium text-right text-sm whitespace-nowrap">
|
|
193
|
+
<div class="flex justify-end space-x-2">
|
|
194
|
+
<button @click="onStarClick(item, $event)" :class="starButtonClass(item)"><div class="h-5 w-5 i-hugeicons-star"></div></button>
|
|
195
|
+
<button @click="onShareClick(item, $event)" :class="shareButtonClass(item)"><div class="h-5 w-5 i-hugeicons-share-01"></div></button>
|
|
196
|
+
<button @click="onDeleteClick(item, $event)" class="text-gray-400 hover:text-red-500"><div class="h-5 w-5 i-hugeicons-waste"></div></button>
|
|
197
|
+
</div>
|
|
198
|
+
</td>
|
|
199
|
+
</tr>
|
|
200
|
+
</template>
|
|
201
|
+
</tbody>
|
|
202
|
+
</table>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<!-- Meema-style file info / preview slide-over -->
|
|
209
|
+
<div :show="showInfoPanel" class="fixed inset-0 z-20">
|
|
210
|
+
<div class="absolute inset-0 bg-gray-900/40 dark:bg-black/50" @click="closeInfoPanel()"></div>
|
|
211
|
+
<aside class="flex absolute inset-y-0 right-0 flex-col max-w-sm w-full bg-white/80 dark:bg-neutral-900/70 border-black/5 border-l dark:border-white/10 shadow-xl backdrop-blur-2xl">
|
|
212
|
+
<div class="flex items-center justify-between p-4 border-b border-black/5 dark:border-white/10">
|
|
213
|
+
<h3 class="font-semibold text-sm text-stone-500 tracking-wider uppercase dark:text-neutral-400">Details</h3>
|
|
214
|
+
<button @click="closeInfoPanel()" class="p-1.5 text-gray-400 dark:text-neutral-500 hover:text-gray-600 dark:hover:text-neutral-300 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded-md">
|
|
215
|
+
<div class="h-5 w-5 i-hugeicons-cancel-01"></div>
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="overflow-auto flex-1 p-5">
|
|
219
|
+
<MediaInfoPanel :item="selectedItem()" />
|
|
220
|
+
</div>
|
|
221
|
+
</aside>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div :show="showCreateFolderModal" class="fixed inset-0 overflow-y-auto z-10">
|
|
225
|
+
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-neutral-900 dark:bg-opacity-75" @click="closeCreateFolderModal()"></div>
|
|
226
|
+
<div class="flex sm:block items-end justify-center pb-20 pt-4 px-4 sm:p-0 min-h-screen text-center">
|
|
227
|
+
<span class="hidden sm:inline-block sm:h-screen sm:align-middle">​</span>
|
|
228
|
+
<div class="inline-block overflow-hidden pb-4 pt-5 px-4 sm:my-8 sm:p-6 sm:max-w-lg sm:w-full text-left bg-white dark:bg-neutral-800 rounded-lg shadow-xl align-bottom sm:align-middle">
|
|
229
|
+
<h3 class="font-medium leading-6 text-gray-900 text-lg dark:text-white">Create New Folder</h3>
|
|
230
|
+
<p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">Enter a name for the new folder.</p>
|
|
231
|
+
<input x-model="newFolderName" type="text" placeholder="Folder name" class="block mt-4 w-full sm:text-sm dark:text-white dark:bg-neutral-700 border-gray-300 rounded-md dark:border-neutral-600 focus:border-indigo-500 focus:ring-indigo-500 shadow-sm" />
|
|
232
|
+
<div class="sm:flex sm:flex-row-reverse mt-5">
|
|
233
|
+
<button type="button" @click="createNewFolder()" class="inline-flex justify-center px-4 py-2 sm:ml-3 w-full sm:w-auto font-medium text-base text-white sm:text-sm bg-indigo-600 hover:bg-indigo-700 rounded-md shadow-sm">Create</button>
|
|
234
|
+
<button type="button" @click="closeCreateFolderModal()" class="inline-flex justify-center mt-3 px-4 py-2 sm:mt-0 w-full sm:w-auto font-medium text-base text-gray-700 sm:text-sm dark:text-neutral-200 bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 border border-gray-300 rounded-md dark:border-neutral-600 shadow-sm">Cancel</button>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div :show="showUploadModal" class="fixed inset-0 overflow-y-auto z-10">
|
|
241
|
+
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-neutral-900 dark:bg-opacity-75" @click="closeUploadModal()"></div>
|
|
242
|
+
<div class="flex sm:block items-end justify-center pb-20 pt-4 px-4 sm:p-0 min-h-screen text-center">
|
|
243
|
+
<span class="hidden sm:inline-block sm:h-screen sm:align-middle">​</span>
|
|
244
|
+
<div class="inline-block overflow-hidden pb-4 pt-5 px-4 sm:my-8 sm:p-6 sm:max-w-lg sm:w-full text-left bg-white dark:bg-neutral-800 rounded-lg shadow-xl align-bottom sm:align-middle">
|
|
245
|
+
<h3 class="font-medium leading-6 text-gray-900 text-lg dark:text-white">Upload Files</h3>
|
|
246
|
+
<p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">Select files to upload to the current folder.</p>
|
|
247
|
+
<div class="flex justify-center mt-4 pb-6 pt-5 px-6 border-2 border-dashed border-gray-300 rounded-md dark:border-neutral-600">
|
|
248
|
+
<div class="space-y-1 text-center">
|
|
249
|
+
<div class="mx-auto h-12 w-12 text-gray-400 dark:text-neutral-500 i-hugeicons-upload-01"></div>
|
|
250
|
+
<div class="flex text-gray-600 text-sm dark:text-neutral-400">
|
|
251
|
+
<label for="file-upload" class="relative font-medium text-indigo-600 dark:text-indigo-400 hover:text-indigo-500 bg-white dark:bg-neutral-800 rounded-md cursor-pointer">
|
|
252
|
+
<span>Upload a file</span>
|
|
253
|
+
<input id="file-upload" type="file" class="sr-only" multiple />
|
|
254
|
+
</label>
|
|
255
|
+
<p class="pl-1">or drag and drop</p>
|
|
256
|
+
</div>
|
|
257
|
+
<p class="text-gray-500 text-xs dark:text-neutral-400">PNG, JPG, GIF, PDF, DOCX up to 10MB</p>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
<div class="sm:flex sm:flex-row-reverse mt-5">
|
|
261
|
+
<button type="button" class="inline-flex justify-center px-4 py-2 sm:ml-3 w-full sm:w-auto font-medium text-base text-white sm:text-sm bg-indigo-600 hover:bg-indigo-700 rounded-md shadow-sm">Upload</button>
|
|
262
|
+
<button type="button" @click="closeUploadModal()" class="inline-flex justify-center mt-3 px-4 py-2 sm:mt-0 w-full sm:w-auto font-medium text-base text-gray-700 sm:text-sm dark:text-neutral-200 bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 border border-gray-300 rounded-md dark:border-neutral-600 shadow-sm">Cancel</button>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<div :show="showDeleteConfirmation" class="fixed inset-0 overflow-y-auto z-10">
|
|
269
|
+
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-neutral-900 dark:bg-opacity-75" @click="closeDeleteConfirmation()"></div>
|
|
270
|
+
<div class="flex sm:block items-end justify-center pb-20 pt-4 px-4 sm:p-0 min-h-screen text-center">
|
|
271
|
+
<span class="hidden sm:inline-block sm:h-screen sm:align-middle">​</span>
|
|
272
|
+
<div class="inline-block overflow-hidden pb-4 pt-5 px-4 sm:my-8 sm:p-6 sm:max-w-lg sm:w-full text-left bg-white dark:bg-neutral-800 rounded-lg shadow-xl align-bottom sm:align-middle">
|
|
273
|
+
<h3 class="font-medium leading-6 text-gray-900 text-lg dark:text-white">Delete Item</h3>
|
|
274
|
+
<p class="mt-2 text-gray-500 text-sm dark:text-neutral-400">Are you sure you want to delete this item? This action cannot be undone.</p>
|
|
275
|
+
<div class="sm:flex sm:flex-row-reverse mt-5">
|
|
276
|
+
<button type="button" @click="deleteSelectedItems()" class="inline-flex justify-center px-4 py-2 sm:ml-3 w-full sm:w-auto font-medium text-base text-white sm:text-sm bg-red-600 hover:bg-red-700 rounded-md shadow-sm">Delete</button>
|
|
277
|
+
<button type="button" @click="closeDeleteConfirmation()" class="inline-flex justify-center mt-3 px-4 py-2 sm:mt-0 w-full sm:w-auto font-medium text-base text-gray-700 sm:text-sm dark:text-neutral-200 bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 border border-gray-300 rounded-md dark:border-neutral-600 shadow-sm">Cancel</button>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<script client>
|
|
285
|
+
|
|
286
|
+
interface FileItem {
|
|
287
|
+
id: number
|
|
288
|
+
name: string
|
|
289
|
+
type: string
|
|
290
|
+
size?: number
|
|
291
|
+
path: string
|
|
292
|
+
lastModified: string
|
|
293
|
+
starred: boolean
|
|
294
|
+
shared: boolean
|
|
295
|
+
// Present on real Storage-backed items (#245) so MediaCard can render true
|
|
296
|
+
// previews; absent on the demo tree.
|
|
297
|
+
mime_type?: string
|
|
298
|
+
url?: string
|
|
299
|
+
items?: FileItem[]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
interface ChartLike { destroy: () => void }
|
|
303
|
+
type ChartCtor = new (canvas: HTMLElement, config: { type: string, data: unknown, options?: unknown }) => ChartLike
|
|
304
|
+
|
|
305
|
+
const fileTypeIcons: Record<string, string> = {
|
|
306
|
+
pdf: 'i-hugeicons-pdf-01', doc: 'i-hugeicons-doc-01', docx: 'i-hugeicons-doc-01',
|
|
307
|
+
xls: 'i-hugeicons-xls-01', xlsx: 'i-hugeicons-xls-01',
|
|
308
|
+
ppt: 'i-hugeicons-ppt-01', pptx: 'i-hugeicons-ppt-01',
|
|
309
|
+
jpg: 'i-hugeicons-jpg-01', jpeg: 'i-hugeicons-jpg-01',
|
|
310
|
+
png: 'i-hugeicons-png-01', gif: 'i-hugeicons-gif-01', svg: 'i-hugeicons-svg-01',
|
|
311
|
+
txt: 'i-hugeicons-txt-01', zip: 'i-hugeicons-zip-01',
|
|
312
|
+
mp3: 'i-hugeicons-mp3-01', mp4: 'i-hugeicons-mp-4-01',
|
|
313
|
+
folder: 'i-hugeicons-folder-02', default: 'i-hugeicons-file-02',
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const rootFolder: FileItem = {
|
|
317
|
+
id: 1,
|
|
318
|
+
name: 'Root',
|
|
319
|
+
type: 'folder',
|
|
320
|
+
path: '/',
|
|
321
|
+
lastModified: '2023-12-01T10:00:00',
|
|
322
|
+
starred: false,
|
|
323
|
+
shared: false,
|
|
324
|
+
items: [
|
|
325
|
+
{ id: 2, name: 'Documents', type: 'folder', path: '/Documents', lastModified: '2023-12-01T10:00:00', starred: true, shared: true, items: [
|
|
326
|
+
{ id: 6, name: 'Project Proposal.pdf', type: 'pdf', size: 2500000, path: '/Documents/Project Proposal.pdf', lastModified: '2023-12-05T14:30:00', starred: true, shared: true },
|
|
327
|
+
{ id: 7, name: 'Meeting Notes.docx', type: 'docx', size: 350000, path: '/Documents/Meeting Notes.docx', lastModified: '2023-12-10T09:15:00', starred: false, shared: false },
|
|
328
|
+
{ id: 8, name: 'Budget.xlsx', type: 'xlsx', size: 1200000, path: '/Documents/Budget.xlsx', lastModified: '2023-12-08T16:45:00', starred: false, shared: true },
|
|
329
|
+
] },
|
|
330
|
+
{ id: 3, name: 'Images', type: 'folder', path: '/Images', lastModified: '2023-12-02T11:30:00', starred: false, shared: false, items: [
|
|
331
|
+
{ id: 9, name: 'Profile Photo.jpg', type: 'jpg', size: 1800000, path: '/Images/Profile Photo.jpg', lastModified: '2023-12-03T13:20:00', starred: true, shared: false },
|
|
332
|
+
{ id: 10, name: 'Banner.png', type: 'png', size: 2700000, path: '/Images/Banner.png', lastModified: '2023-12-04T10:10:00', starred: false, shared: true },
|
|
333
|
+
{ id: 11, name: 'Icon.svg', type: 'svg', size: 150000, path: '/Images/Icon.svg', lastModified: '2023-12-05T09:05:00', starred: false, shared: false },
|
|
334
|
+
] },
|
|
335
|
+
{ id: 4, name: 'Videos', type: 'folder', path: '/Videos', lastModified: '2023-12-03T14:00:00', starred: false, shared: false, items: [
|
|
336
|
+
{ id: 12, name: 'Product Demo.mp4', type: 'mp4', size: 15000000, path: '/Videos/Product Demo.mp4', lastModified: '2023-12-06T15:30:00', starred: false, shared: true },
|
|
337
|
+
{ id: 13, name: 'Tutorial.mp4', type: 'mp4', size: 25000000, path: '/Videos/Tutorial.mp4', lastModified: '2023-12-07T11:45:00', starred: true, shared: true },
|
|
338
|
+
] },
|
|
339
|
+
{ id: 5, name: 'Downloads', type: 'folder', path: '/Downloads', lastModified: '2023-12-04T09:00:00', starred: false, shared: false, items: [
|
|
340
|
+
{ id: 14, name: 'Software Update.zip', type: 'zip', size: 45000000, path: '/Downloads/Software Update.zip', lastModified: '2023-12-08T10:20:00', starred: false, shared: false },
|
|
341
|
+
{ id: 15, name: 'E-book.pdf', type: 'pdf', size: 3500000, path: '/Downloads/E-book.pdf', lastModified: '2023-12-09T14:10:00', starred: true, shared: false },
|
|
342
|
+
] },
|
|
343
|
+
],
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Prefer the server-injected real Storage tree (stacksjs/stacks#245); fall back
|
|
347
|
+
// to the demo tree above when it's absent (empty disk, listing failed, or the
|
|
348
|
+
// server block couldn't resolve). The payload is HTML-escaped JSON in a hidden
|
|
349
|
+
// element — same server->client hand-off the dashboard sidebar uses.
|
|
350
|
+
function readInjectedFileTree(): FileItem | null {
|
|
351
|
+
try {
|
|
352
|
+
const node = document.querySelector('[data-file-tree]')
|
|
353
|
+
const raw = node?.textContent?.trim()
|
|
354
|
+
if (!raw)
|
|
355
|
+
return null
|
|
356
|
+
const parsed = JSON.parse(raw)
|
|
357
|
+
return parsed && Array.isArray(parsed.items) && parsed.items.length > 0 ? parsed as FileItem : null
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return null
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const initialRoot = readInjectedFileTree() ?? rootFolder
|
|
365
|
+
const root = state<FileItem>(initialRoot)
|
|
366
|
+
const currentFolderId = state<number>(initialRoot.id)
|
|
367
|
+
const searchQuery = state('')
|
|
368
|
+
const sortBy = state<'name' | 'lastModified' | 'size'>('name')
|
|
369
|
+
const sortOrder = state<'asc' | 'desc'>('asc')
|
|
370
|
+
const viewMode = state<'grid' | 'list'>('grid')
|
|
371
|
+
const showSidePanel = state(true)
|
|
372
|
+
const showUploadModal = state(false)
|
|
373
|
+
const showCreateFolderModal = state(false)
|
|
374
|
+
const showDeleteConfirmation = state(false)
|
|
375
|
+
const newFolderName = state('')
|
|
376
|
+
const itemToDelete = state<FileItem | null>(null)
|
|
377
|
+
// Selected file for the Meema-style info/preview panel. Defaults to a neutral
|
|
378
|
+
// placeholder (never null) so the panel component always receives a valid item
|
|
379
|
+
// even while hidden; onItemClick swaps in the real file.
|
|
380
|
+
const selectedItem = state<FileItem>({ id: 0, name: '', type: 'file', path: '/', lastModified: '', starred: false } as FileItem)
|
|
381
|
+
const showInfoPanel = state(false)
|
|
382
|
+
|
|
383
|
+
const storageStats = { total: 100 * 1024 ** 3, used: 45 * 1024 ** 3, available: 55 * 1024 ** 3 }
|
|
384
|
+
const storageByType = { documents: 15 * 1024 ** 3, images: 10 * 1024 ** 3, videos: 18 * 1024 ** 3, other: 2 * 1024 ** 3 }
|
|
385
|
+
|
|
386
|
+
function findFolderById(folder: FileItem, id: number): FileItem | null {
|
|
387
|
+
if (folder.id === id)
|
|
388
|
+
return folder
|
|
389
|
+
for (const child of folder.items ?? []) {
|
|
390
|
+
if (child.type === 'folder') {
|
|
391
|
+
const found = findFolderById(child, id)
|
|
392
|
+
if (found)
|
|
393
|
+
return found
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function findPathToId(folder: FileItem, id: number, path: string[] = []): string[] | null {
|
|
400
|
+
const next = [...path, folder.name]
|
|
401
|
+
if (folder.id === id)
|
|
402
|
+
return next
|
|
403
|
+
for (const child of folder.items ?? []) {
|
|
404
|
+
if (child.type === 'folder') {
|
|
405
|
+
const found = findPathToId(child, id, next)
|
|
406
|
+
if (found)
|
|
407
|
+
return found
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return null
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const currentFolder = derived(() => findFolderById(root(), currentFolderId()) ?? root())
|
|
414
|
+
const currentPath = derived(() => findPathToId(root(), currentFolderId()) ?? ['Root'])
|
|
415
|
+
|
|
416
|
+
const filteredItems = derived(() => {
|
|
417
|
+
let items = [...(currentFolder().items ?? [])]
|
|
418
|
+
const q = searchQuery().toLowerCase()
|
|
419
|
+
if (q)
|
|
420
|
+
items = items.filter(item => item.name.toLowerCase().includes(q))
|
|
421
|
+
items.sort((a, b) => {
|
|
422
|
+
if (sortBy() === 'name')
|
|
423
|
+
return sortOrder() === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)
|
|
424
|
+
if (sortBy() === 'lastModified')
|
|
425
|
+
return sortOrder() === 'asc' ? new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime() : new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
|
|
426
|
+
const sa = a.size ?? 0
|
|
427
|
+
const sb = b.size ?? 0
|
|
428
|
+
return sortOrder() === 'asc' ? sa - sb : sb - sa
|
|
429
|
+
})
|
|
430
|
+
return items
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
function countByPredicate(folder: FileItem, predicate: (item: FileItem) => boolean): number {
|
|
434
|
+
let count = 0
|
|
435
|
+
for (const item of folder.items ?? []) {
|
|
436
|
+
if (predicate(item))
|
|
437
|
+
count++
|
|
438
|
+
if (item.type === 'folder')
|
|
439
|
+
count += countByPredicate(item, predicate)
|
|
440
|
+
}
|
|
441
|
+
return count
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const starredCount = derived(() => countByPredicate(root(), i => i.starred))
|
|
445
|
+
const sharedCount = derived(() => countByPredicate(root(), i => i.shared))
|
|
446
|
+
const topLevelFolders = derived(() => (root().items ?? []).filter(i => i.type === 'folder'))
|
|
447
|
+
|
|
448
|
+
// Each derived/function below returns a COMPLETE class string (static + dynamic)
|
|
449
|
+
// because stx's `{{ ... }}` placeholder is not reactively bound inside a `class`
|
|
450
|
+
// attribute — only inside text content. The `:class="fn()"` directive is the
|
|
451
|
+
// supported reactive form, so any class that mixes static and dynamic tokens
|
|
452
|
+
// has to be assembled in JS and applied via :class.
|
|
453
|
+
// Native macOS sidebar vibrancy: a translucent material over the window
|
|
454
|
+
// background rather than a flat fill, matching the desktop shell's macos-themed
|
|
455
|
+
// global sidebar (titlebarHidden window). stacksjs — native File Manager.
|
|
456
|
+
const sidePanelBase = 'border-r border-black/[0.06] dark:border-white/[0.08] bg-[#F5F3EE]/70 dark:bg-neutral-900/40 backdrop-blur-2xl transition-all duration-300'
|
|
457
|
+
const sidePanelClass = derived(() => `${sidePanelBase} ${showSidePanel() ? 'w-64' : 'w-0 overflow-hidden'}`)
|
|
458
|
+
|
|
459
|
+
const sortOrderIconClass = derived(() => `${sortOrder() === 'asc' ? 'i-hugeicons-arrow-up-02' : 'i-hugeicons-arrow-down-02'} h-5 w-5`)
|
|
460
|
+
|
|
461
|
+
// Native macOS segmented control: the active segment is an elevated white pill
|
|
462
|
+
// on a recessed track (see the track container in the toolbar), not a filled
|
|
463
|
+
// accent button.
|
|
464
|
+
const segmentBase = 'inline-flex items-center px-2.5 py-1.5 rounded-md text-sm font-medium transition-colors'
|
|
465
|
+
const segmentActive = 'bg-white dark:bg-neutral-700 text-stone-800 dark:text-white shadow-sm'
|
|
466
|
+
const segmentInactive = 'text-stone-500 dark:text-neutral-400 hover:text-stone-700 dark:hover:text-neutral-200'
|
|
467
|
+
const gridViewClass = derived(() => `${segmentBase} ${viewMode() === 'grid' ? segmentActive : segmentInactive}`)
|
|
468
|
+
const listViewClass = derived(() => `${segmentBase} ${viewMode() === 'list' ? segmentActive : segmentInactive}`)
|
|
469
|
+
|
|
470
|
+
// Native macOS source-list row: a rounded selection pill on the active row
|
|
471
|
+
// (light translucent fill), colored SF-style icons, plain trailing counts.
|
|
472
|
+
const navRowBase = 'group flex items-center gap-2.5 px-2.5 py-1.5 w-full text-left font-medium text-[13px] rounded-lg transition-colors'
|
|
473
|
+
function navRowClass(active: boolean): string {
|
|
474
|
+
return active
|
|
475
|
+
? `${navRowBase} bg-black/[0.06] dark:bg-white/[0.09] text-stone-900 dark:text-white`
|
|
476
|
+
: `${navRowBase} text-stone-600 dark:text-neutral-300 hover:bg-black/[0.04] dark:hover:bg-white/[0.06]`
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Native selection: the open file gets a Finder-style accent ring, updated
|
|
480
|
+
// reactively as the info panel opens/closes on different items.
|
|
481
|
+
const gridCardBase = 'group relative overflow-hidden rounded-lg border shadow-sm hover:shadow-lg duration-200 transition-all cursor-pointer bg-white dark:bg-neutral-800'
|
|
482
|
+
const gridCardClass = (item: FileItem) => `${gridCardBase} ${showInfoPanel() && selectedItem().id === item.id
|
|
483
|
+
? 'border-indigo-400 dark:border-indigo-500 ring-2 ring-indigo-500/30'
|
|
484
|
+
: 'border-gray-200 dark:border-neutral-700 hover:border-indigo-300 dark:hover:border-indigo-500/50'}`
|
|
485
|
+
|
|
486
|
+
const rowIconClass = (item: FileItem) => `${itemIconClass(item)} h-5 w-5 mr-3`
|
|
487
|
+
const gridStarButtonClass = (item: FileItem) => `p-1 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded-full ${starButtonClass(item)}`
|
|
488
|
+
const gridShareButtonClass = (item: FileItem) => `p-1 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded-full ${shareButtonClass(item)}`
|
|
489
|
+
|
|
490
|
+
const usedLabel = derived(() => formatFileSize(storageStats.used))
|
|
491
|
+
const availableLabel = derived(() => formatFileSize(storageStats.available))
|
|
492
|
+
const totalLabel = derived(() => formatFileSize(storageStats.total))
|
|
493
|
+
const docsLabel = derived(() => formatFileSize(storageByType.documents))
|
|
494
|
+
const imgsLabel = derived(() => formatFileSize(storageByType.images))
|
|
495
|
+
const videosLabel = derived(() => formatFileSize(storageByType.videos))
|
|
496
|
+
const otherLabel = derived(() => formatFileSize(storageByType.other))
|
|
497
|
+
|
|
498
|
+
const emptyHint = derived(() => searchQuery() ? 'No items match your search.' : 'This folder is empty.')
|
|
499
|
+
|
|
500
|
+
function formatFileSize(bytes: number) {
|
|
501
|
+
if (bytes === 0)
|
|
502
|
+
return '0 Bytes'
|
|
503
|
+
const k = 1024
|
|
504
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
|
505
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
506
|
+
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function formatDate(dateString: string) {
|
|
510
|
+
return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function getFileIcon(type: string) {
|
|
514
|
+
return fileTypeIcons[type] ?? fileTypeIcons.default
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function itemIconClass(item: FileItem) {
|
|
518
|
+
return item.type === 'folder'
|
|
519
|
+
? 'i-hugeicons-folder-02 text-indigo-400 dark:text-indigo-300'
|
|
520
|
+
: `${getFileIcon(item.type)} text-gray-500 dark:text-neutral-400`
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function starButtonClass(item: FileItem) {
|
|
524
|
+
return item.starred ? 'text-yellow-500' : 'text-gray-400 dark:text-neutral-500 hover:text-yellow-500'
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function shareButtonClass(item: FileItem) {
|
|
528
|
+
return item.shared ? 'text-indigo-500' : 'text-gray-400 dark:text-neutral-500 hover:text-indigo-500'
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function navigateToRoot() {
|
|
532
|
+
currentFolderId.set(root().id)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function navigateToFolder(folder: FileItem) {
|
|
536
|
+
currentFolderId.set(folder.id)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function navigateToPath(index: number) {
|
|
540
|
+
const path = currentPath()
|
|
541
|
+
if (index >= path.length - 1)
|
|
542
|
+
return
|
|
543
|
+
const targetName = path[index]
|
|
544
|
+
let folder = root()
|
|
545
|
+
for (let i = 1; i <= index; i++) {
|
|
546
|
+
const next = (folder.items ?? []).find(it => it.type === 'folder' && it.name === path[i])
|
|
547
|
+
if (next)
|
|
548
|
+
folder = next
|
|
549
|
+
}
|
|
550
|
+
if (folder.name === targetName || index === 0)
|
|
551
|
+
currentFolderId.set(folder.id)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function toggleSidePanel() {
|
|
555
|
+
showSidePanel.set(!showSidePanel())
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function setViewMode(mode: 'grid' | 'list') {
|
|
559
|
+
viewMode.set(mode)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function toggleSortOrder() {
|
|
563
|
+
sortOrder.set(sortOrder() === 'asc' ? 'desc' : 'asc')
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function onItemClick(item: FileItem) {
|
|
567
|
+
if (item.type === 'folder') {
|
|
568
|
+
navigateToFolder(item)
|
|
569
|
+
return
|
|
570
|
+
}
|
|
571
|
+
selectedItem.set(item)
|
|
572
|
+
showInfoPanel.set(true)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function closeInfoPanel() {
|
|
576
|
+
showInfoPanel.set(false)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function onStarClick(item: FileItem, event: Event) {
|
|
580
|
+
event.stopPropagation()
|
|
581
|
+
toggleFlag(item.id, 'starred')
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function onShareClick(item: FileItem, event: Event) {
|
|
585
|
+
event.stopPropagation()
|
|
586
|
+
toggleFlag(item.id, 'shared')
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function onDeleteClick(item: FileItem, event: Event) {
|
|
590
|
+
event.stopPropagation()
|
|
591
|
+
itemToDelete.set(item)
|
|
592
|
+
showDeleteConfirmation.set(true)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function toggleFlag(id: number, key: 'starred' | 'shared') {
|
|
596
|
+
function visit(folder: FileItem): FileItem {
|
|
597
|
+
return {
|
|
598
|
+
...folder,
|
|
599
|
+
items: (folder.items ?? []).map((child) => {
|
|
600
|
+
if (child.id === id)
|
|
601
|
+
return { ...child, [key]: !child[key] }
|
|
602
|
+
if (child.type === 'folder')
|
|
603
|
+
return visit(child)
|
|
604
|
+
return child
|
|
605
|
+
}),
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
root.set(visit(root()))
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function openCreateFolderModal() {
|
|
612
|
+
newFolderName.set('')
|
|
613
|
+
showCreateFolderModal.set(true)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function closeCreateFolderModal() {
|
|
617
|
+
showCreateFolderModal.set(false)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function openUploadModal() {
|
|
621
|
+
showUploadModal.set(true)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function closeUploadModal() {
|
|
625
|
+
showUploadModal.set(false)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function closeDeleteConfirmation() {
|
|
629
|
+
showDeleteConfirmation.set(false)
|
|
630
|
+
itemToDelete.set(null)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function createNewFolder() {
|
|
634
|
+
const name = newFolderName().trim()
|
|
635
|
+
if (!name)
|
|
636
|
+
return
|
|
637
|
+
const newFolder: FileItem = {
|
|
638
|
+
id: Date.now(),
|
|
639
|
+
name,
|
|
640
|
+
type: 'folder',
|
|
641
|
+
path: `${currentFolder().path}/${name}`,
|
|
642
|
+
lastModified: new Date().toISOString(),
|
|
643
|
+
starred: false,
|
|
644
|
+
shared: false,
|
|
645
|
+
items: [],
|
|
646
|
+
}
|
|
647
|
+
function visit(folder: FileItem): FileItem {
|
|
648
|
+
if (folder.id === currentFolderId())
|
|
649
|
+
return { ...folder, items: [...(folder.items ?? []), newFolder] }
|
|
650
|
+
return { ...folder, items: (folder.items ?? []).map(child => child.type === 'folder' ? visit(child) : child) }
|
|
651
|
+
}
|
|
652
|
+
root.set(visit(root()))
|
|
653
|
+
showCreateFolderModal.set(false)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function deleteSelectedItems() {
|
|
657
|
+
const target = itemToDelete()
|
|
658
|
+
if (!target) {
|
|
659
|
+
closeDeleteConfirmation()
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
function visit(folder: FileItem): FileItem {
|
|
663
|
+
return {
|
|
664
|
+
...folder,
|
|
665
|
+
items: (folder.items ?? []).filter(c => c.id !== target.id).map(c => c.type === 'folder' ? visit(c) : c),
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
root.set(visit(root()))
|
|
669
|
+
closeDeleteConfirmation()
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
let chartHandles: ChartLike[] = []
|
|
673
|
+
|
|
674
|
+
onMount(async () => {
|
|
675
|
+
const { Chart, registerables } = await import('@stacksjs/charts')
|
|
676
|
+
Chart.register(...registerables)
|
|
677
|
+
const Ctor = Chart as unknown as ChartCtor
|
|
678
|
+
|
|
679
|
+
const doughnutOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } }
|
|
680
|
+
|
|
681
|
+
const handles: Array<ChartLike | null> = [
|
|
682
|
+
useChart({
|
|
683
|
+
Chart: Ctor,
|
|
684
|
+
id: 'files_storage_chart',
|
|
685
|
+
type: 'doughnut',
|
|
686
|
+
data: { labels: ['Used', 'Available'], datasets: [{ backgroundColor: ['rgba(54, 162, 235, 0.6)', 'rgba(200, 200, 200, 0.6)'], borderColor: ['rgba(54, 162, 235, 1)', 'rgba(200, 200, 200, 1)'], borderWidth: 1, data: [storageStats.used, storageStats.available] }] },
|
|
687
|
+
options: doughnutOptions,
|
|
688
|
+
}) as ChartLike | null,
|
|
689
|
+
useChart({
|
|
690
|
+
Chart: Ctor,
|
|
691
|
+
id: 'files_type_chart',
|
|
692
|
+
type: 'doughnut',
|
|
693
|
+
data: { labels: ['Documents', 'Images', 'Videos', 'Other'], datasets: [{ backgroundColor: ['rgba(255, 99, 132, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(255, 206, 86, 0.6)', 'rgba(75, 192, 192, 0.6)'], borderColor: ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)'], borderWidth: 1, data: [storageByType.documents, storageByType.images, storageByType.videos, storageByType.other] }] },
|
|
694
|
+
options: doughnutOptions,
|
|
695
|
+
}) as ChartLike | null,
|
|
696
|
+
]
|
|
697
|
+
chartHandles = handles.filter((h): h is ChartLike => h !== null)
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
onDestroy(() => {
|
|
701
|
+
chartHandles.forEach(h => h.destroy())
|
|
702
|
+
chartHandles = []
|
|
703
|
+
})
|
|
704
|
+
</script>
|
|
705
|
+
|
|
706
|
+
@endsection
|