mirror of
https://github.com/appwrite/console.git
synced 2026-04-07 19:17:46 +00:00
merge: feat-documentsdb into feat-vectordb, update SDK to d27fbd5
This commit is contained in:
@@ -4,5 +4,4 @@ PUBLIC_APPWRITE_MULTI_REGION=false
|
||||
PUBLIC_APPWRITE_ENDPOINT=http://localhost/v1
|
||||
PUBLIC_STRIPE_KEY=
|
||||
PUBLIC_GROWTH_ENDPOINT=
|
||||
PUBLIC_CONSOLE_EMAIL_VERIFICATION=false
|
||||
PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=true
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
"PUBLIC_CONSOLE_MODE=cloud"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS="
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=true"
|
||||
"PUBLIC_CONSOLE_EMAIL_VERIFICATION=true"
|
||||
"PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=false"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY }}"
|
||||
@@ -84,7 +83,6 @@ jobs:
|
||||
"PUBLIC_CONSOLE_MODE=cloud"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS="
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=true"
|
||||
"PUBLIC_CONSOLE_EMAIL_VERIFICATION=false"
|
||||
"PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=false"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}"
|
||||
@@ -124,7 +122,6 @@ jobs:
|
||||
build-args: |
|
||||
"PUBLIC_CONSOLE_MODE=self-hosted"
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=false"
|
||||
"PUBLIC_CONSOLE_EMAIL_VERIFICATION=false"
|
||||
"PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=true"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS="
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
@@ -164,7 +161,6 @@ jobs:
|
||||
build-args: |
|
||||
"PUBLIC_CONSOLE_MODE=cloud"
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=false"
|
||||
"PUBLIC_CONSOLE_EMAIL_VERIFICATION=false"
|
||||
"PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=false"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS="
|
||||
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}"
|
||||
|
||||
@@ -1,138 +1,242 @@
|
||||
# Appwrite Console - Copilot Instructions
|
||||
# Appwrite Console
|
||||
|
||||
## Repository Overview
|
||||
SvelteKit web dashboard for Appwrite. Manages projects, databases, functions, auth, storage, messaging, and sites. Static SPA (no SSR) served behind Nginx at `/console`.
|
||||
|
||||
Appwrite Console is the web-based GUI for the Appwrite backend-as-a-service platform. Single-page application built with
|
||||
**Svelte 5 + SvelteKit 2**, **TypeScript** (not strict mode), **Vite 7**, tested with **Vitest + Playwright**. Package
|
||||
manager/runtime: **Bun** (Node 20+ optional for tooling). ~1500 files with extensive component-based architecture.
|
||||
## Commands
|
||||
|
||||
## Critical Build & Test Commands
|
||||
All commands use **bun** (not pnpm/npm). Use `bun run <script>` consistently — `bun run build` is required to avoid invoking bun's built-in bundler.
|
||||
|
||||
### Setup (REQUIRED before any commands)
|
||||
| Command | Purpose |
|
||||
| ------------------------- | --------------------------------------------- |
|
||||
| `bun run dev` | Dev server (port 3000) |
|
||||
| `bun run build` | Production build (custom `build.js` via Vite) |
|
||||
| `bun run check` | `svelte-kit sync && svelte-check` |
|
||||
| `bun run format` | Prettier write + cache |
|
||||
| `bun run lint` | Prettier check + ESLint |
|
||||
| `bun run tests` | Unit + E2E |
|
||||
| `bun run test:unit` | Vitest (TZ=EST) |
|
||||
| `bun run test:unit-watch` | Vitest watch mode |
|
||||
| `bun run test:e2e` | Playwright |
|
||||
| `bun run clean` | Remove node_modules, .svelte-kit, reinstall |
|
||||
|
||||
1. **Install Bun**:
|
||||
- Linux & macOS: `curl -fsSL https://bun.sh/install | bash`
|
||||
- Windows: `powershell -c "irm bun.sh/install.ps1 | iex"`
|
||||
2. **Create .env**: `cp .env.example .env` (configure `PUBLIC_APPWRITE_ENDPOINT` and `PUBLIC_CONSOLE_MODE`)
|
||||
3. **Configure network access** (if using GitHub Actions or restricted environments):
|
||||
- Ensure firewall/proxy allows access to: `pkg.pr.new`, `pkg.vc`, `registry.npmjs.org`
|
||||
- These domains are required for dependencies: `@appwrite.io/console`, `@appwrite.io/pink-icons-svelte`,
|
||||
`@appwrite.io/pink-svelte`
|
||||
- In GitHub Actions: Ensure Bun is installed and registry access is configured
|
||||
- If network errors persist, check proxy settings: `npm config get proxy` and `npm config get https-proxy`
|
||||
4. **Install dependencies**: `bun install --frozen-lockfile` (if pkg.pr.new/pkg.vc fail due to network restrictions,
|
||||
installation may still succeed with cached versions)
|
||||
**Always run before committing:** `bun run format && bun run check && bun run lint && bun run tests && bun run build`
|
||||
|
||||
### Development Commands
|
||||
## CI checks (`.github/workflows/tests.yml`)
|
||||
|
||||
**Standard workflow**: `check` → `lint` → `test` → `build` (before committing)
|
||||
`bun audit --audit-level high` -> `bun check` -> `bun lint` -> `bun test:unit` -> `bun run build`. Uses frozen lockfile.
|
||||
|
||||
- `bun run check` - TypeScript/Svelte validation (~30-60s)
|
||||
- `bun run lint` - ESLint check (~10-20s)
|
||||
- `bun run format` - Auto-fix Prettier formatting
|
||||
- `bun run test` - Vitest unit tests with TZ=EST (~10-30s)
|
||||
- `bun run build` - Production build via build.js (~60-120s)
|
||||
- `bun run dev` - Dev server on port 3000
|
||||
- `bun run preview` - Preview build on port 4173
|
||||
- `bun run e2e` - Playwright tests (needs `bunx playwright install --with-deps chromium` first, ~120s+)
|
||||
## Stack
|
||||
|
||||
**CI Pipeline** (`.github/workflows/tests.yml`): audit → install → check → lint → test → build
|
||||
- **Framework:** SvelteKit 2 + Svelte 5, TypeScript (strict: false), `@sveltejs/adapter-static`
|
||||
- **Bundler:** Vite 7 (overridden to `rolldown-vite`)
|
||||
- **Design system:** `@appwrite.io/pink-svelte`, `@appwrite.io/pink-icons-svelte`
|
||||
- **UI primitives:** Melt UI (`@melt-ui/svelte` with preprocessor)
|
||||
- **API client:** `@appwrite.io/console` SDK (pinned to GitHub commit)
|
||||
- **Code editing:** CodeMirror 6
|
||||
- **Charts:** ECharts 5
|
||||
- **3D:** Three.js via Threlte
|
||||
- **Payments:** Stripe
|
||||
- **AI:** Vercel AI SDK (`@ai-sdk/svelte`)
|
||||
- **Testing:** Vitest + @testing-library/svelte (unit), Playwright (E2E)
|
||||
- **Error tracking:** Sentry (`@sentry/sveltekit`)
|
||||
- **Analytics:** Plausible + custom Growth endpoint
|
||||
|
||||
## Project Structure
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── lib/ # Reusable logic ($lib alias)
|
||||
│ ├── components/ # Feature components (billing, domains, permissions, etc.)
|
||||
│ ├── elements/ # Basic UI elements
|
||||
│ ├── helpers/ # Utility functions (array, date, string, etc.)
|
||||
│ ├── stores/ # Svelte stores for state
|
||||
│ ├── sdk/ # Appwrite SDK wrappers
|
||||
│ └── constants.ts, flags.ts, system.ts
|
||||
├── routes/
|
||||
│ ├── (console)/ # Auth-required routes
|
||||
│ │ ├── organization-[organization]/
|
||||
│ │ └── project-[region]-[project]/ # databases, functions, messaging, storage
|
||||
│ └── (public)/ # Public routes (login, register, auth callbacks)
|
||||
├── themes/ # Theme definitions ($themes alias)
|
||||
└── app.html, hooks.{client,server}.ts, service-worker.ts
|
||||
### Route structure (`src/routes/`)
|
||||
|
||||
SvelteKit file-based routing with layout groups:
|
||||
|
||||
- `(public)/` -- unauthenticated routes: `(guest)/` (login, register), auth (OAuth, magic URL), invite, recover, card, functions/sites deploy, hackathon, templates
|
||||
- `(console)/` -- authenticated console (projects, orgs, account, onboarding)
|
||||
- `(authenticated)/` -- post-login flows (MFA, Git authorization)
|
||||
|
||||
Dynamic segments: `project-[region]-[project]`, `organization-[organization]`
|
||||
|
||||
### Route file conventions
|
||||
|
||||
Each route can have:
|
||||
|
||||
- `+page.svelte` -- page component
|
||||
- `+page.ts` -- client-side load function
|
||||
- `+layout.svelte` / `+layout.ts` -- layout wrappers
|
||||
- `store.ts` -- route-scoped state
|
||||
- Feature components colocated alongside (e.g. `table.svelte`, `create.svelte`)
|
||||
|
||||
### Path aliases
|
||||
|
||||
| Alias | Path |
|
||||
| ----------- | ------------------------------------------------------------------------------- |
|
||||
| `$lib` | `src/lib` (SvelteKit built-in) |
|
||||
| `$routes` | `src/routes` |
|
||||
| `$themes` | `src/themes` |
|
||||
| `$database` | `src/routes/(console)/project-[region]-[project]/databases/database-[database]` |
|
||||
|
||||
### Library (`src/lib/`)
|
||||
|
||||
| Directory | Contents |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------ |
|
||||
| `components/` | Feature components (billing, permissions, filters, etc.) -- barrel-exported via `index.ts` |
|
||||
| `elements/forms/` | Form inputs (text, email, phone, OTP, file, geometry, etc.) |
|
||||
| `elements/table/` | Table components |
|
||||
| `layout/` | Shell, Container, Wizard, Breadcrumbs, Navigation -- barrel-exported via `index.ts` |
|
||||
| `stores/` | Svelte stores for global state |
|
||||
| `helpers/` | Utilities (array, date, object, numbers, string, validation) |
|
||||
| `sdk/` | Custom SDK extensions (billing, usage, sources) |
|
||||
| `actions/` | Svelte actions and analytics tracking |
|
||||
| `charts/` | Chart visualization components -- barrel-exported via `index.ts` |
|
||||
| `commandCenter/` | Command palette |
|
||||
| `images/` | SVG assets (logos, illustrations, empty states) |
|
||||
| `data/` | Static data (testimonials) |
|
||||
| `profiles/` | CSS profiles and theming |
|
||||
| `mock/` | Mock data for development |
|
||||
|
||||
### Imports
|
||||
|
||||
Components use **barrel exports** -- always import from the directory `index.ts`:
|
||||
|
||||
```typescript
|
||||
import { Card, Modal, Steps } from '$lib/components';
|
||||
import { Shell, Container } from '$lib/layout';
|
||||
import { InputText, Button, Form } from '$lib/elements/forms';
|
||||
```
|
||||
|
||||
**SvelteKit conventions**: `+page.svelte` (component), `+page.ts` (data loader), `+layout.svelte` (wrapper),
|
||||
`+error.svelte` (errors). Groups like `(console)` organize routes without affecting URLs. Dynamic params: `[param]`.
|
||||
### Svelte 5 migration (in progress)
|
||||
|
||||
## Key Configuration
|
||||
~500 files still use legacy Svelte 4 syntax, ~240 migrated to runes. **When touching a file, migrate it to runes if practical.** Don't mix syntaxes within a single component.
|
||||
|
||||
**svelte.config.js**: Adapter = static SPA (fallback: index.html), base path `/console`, aliases: `$lib`, `$routes`,
|
||||
`$themes`
|
||||
**vite.config.ts**: Dev port 3000
|
||||
**tsconfig.json**: Extends `.svelte-kit/tsconfig.json`, **NOT strict mode** (`strict: false`)
|
||||
**eslint.config.js**: Flat config (ESLint 9+), many rules disabled (see TODOs)
|
||||
**.prettierrc**: 4 spaces, single quotes, 100 char width, no trailing commas
|
||||
Legacy (Svelte 4):
|
||||
|
||||
## Testing
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
export let items: Item[] = [];
|
||||
export let disabled = false;
|
||||
$: count = items.length;
|
||||
</script>
|
||||
```
|
||||
|
||||
**Unit (Bun test)**: Tests in `src/lib/helpers/*.test.ts`, run with `TZ=EST` (timezone matters). Setup mocks SvelteKit (
|
||||
`$app/*`) in `bun-test-setup.ts` via `bunfig.toml`.
|
||||
**E2E (Playwright)**: Tests in `e2e/journeys/*.spec.ts`, needs build+preview on port 4173, retries 3x, timeout 120s,
|
||||
Chromium only.
|
||||
Runes (Svelte 5 -- preferred for new and modified code):
|
||||
|
||||
## Common Pitfalls
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
// $bindable() enables two-way binding so parent components can mutate items directly
|
||||
let { items = $bindable(), disabled = false }: Props = $props();
|
||||
let selected = $state<string | null>(null);
|
||||
const count = $derived(items.length);
|
||||
const filtered = $derived.by(() => items.filter((i) => i.active));
|
||||
|
||||
1. **Blank page in dev**: Disable ad blockers if seeing "Failed to fetch dynamically imported module" (known SvelteKit
|
||||
issue)
|
||||
2. **Network errors on install**:
|
||||
- pkg.pr.new/pkg.vc deps may fail due to firewall/proxy restrictions
|
||||
- Check access: `curl -I https://pkg.pr.new` and `curl -I https://pkg.vc`
|
||||
- Configure proxy if needed: `npm config set proxy http://proxy:port` and
|
||||
`npm config set https-proxy http://proxy:port`
|
||||
- GitHub Actions: Ensure runner has internet access and Bun is installed
|
||||
- Local dev: Often safe to continue with cached versions if network fails
|
||||
3. **OOM on build**: Set `NODE_OPTIONS=--max_old_space_size=8192` (like Dockerfile does)
|
||||
4. **Test failures**: Always use `bun run test` (sets TZ=EST), not `bun test` directly
|
||||
5. **TS errors not showing**: Run `bun run check` explicitly (dev server doesn't always surface them)
|
||||
6. **Format vs lint conflicts**: Run `bun run format` before `bun run lint`
|
||||
7. **E2E timeouts**: Wait 120s for preview server startup, tests auto-retry 3x
|
||||
8. **Stale build**: Clear `.svelte-kit` if changes not reflected: `rm -rf .svelte-kit && bun run build`
|
||||
$effect(() => {
|
||||
console.log('selected changed:', selected);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## Code Conventions
|
||||
### SDK usage (`src/lib/stores/sdk.ts`)
|
||||
|
||||
- Imports: Use `$lib`, `$routes`, `$themes` aliases
|
||||
- Components: PascalCase, in `src/lib/components/[feature]/`
|
||||
- Helpers: Pure functions in `src/lib/helpers/`
|
||||
- Types: Inline or `.d.ts`, not `.types.ts` files
|
||||
- Comments: Minimal, use for TODOs or complex logic
|
||||
- TypeScript: Not strict mode, `any` tolerated
|
||||
Four client instances: `clientConsole` (console API), `scopedConsoleClient` (region-scoped console API, used by `forConsoleIn()`), `clientProject` (project API, admin mode), `clientRealtime` (realtime subscriptions). Region-aware endpoints with subdomain routing (fra., nyc., syd., sfo., sgp., tor.).
|
||||
|
||||
## Workflow
|
||||
```typescript
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
1. Run Appwrite backend locally (see [docs](https://appwrite.io/docs/advanced/self-hosting))
|
||||
2. Configure `.env` with backend endpoint
|
||||
3. `bun install --frozen-lockfile`
|
||||
4. `bun run dev` (hot reload on port 3000)
|
||||
5. Before commit: `bun run check && bun run format && bun run lint && bun run test && bun run build`
|
||||
6. **Take screenshots**: For any UI changes, capture screenshots and include them in the PR description or comments
|
||||
before finalizing
|
||||
// Console-level operations
|
||||
await sdk.forConsole.account.get();
|
||||
|
||||
## Required Pre-Completion Checklist
|
||||
// Region-scoped console operations
|
||||
await sdk.forConsoleIn(region).projects.get({ projectId });
|
||||
|
||||
**CRITICAL**: Before finishing any work or marking a task complete, agents MUST run the following commands in order and
|
||||
ensure all pass:
|
||||
// Project-level operations (admin mode)
|
||||
await sdk.forProject(region, projectId).tablesDB.listTables();
|
||||
```
|
||||
|
||||
1. **`bun run format`** - Auto-fix all formatting issues
|
||||
2. **`bun run check`** - Verify TypeScript/Svelte types (must show 0 errors, 0 warnings)
|
||||
3. **`bun run lint`** - Check code style (ignore pre-existing issues in files you didn't modify)
|
||||
4. **`bun run test`** - Run all unit tests (all tests must pass)
|
||||
5. **`bun run build`** - Ensure production build succeeds
|
||||
### Database types (feat-dedicated-db)
|
||||
|
||||
If any command fails:
|
||||
The databases feature unifies multiple database backends behind a polymorph API (`$database/(entity)/helpers/sdk.ts`):
|
||||
|
||||
- **Format/Lint**: Run `bun run format` to auto-fix, then re-check
|
||||
- **Type errors**: Fix all TypeScript errors in files you modified
|
||||
- **Test failures**: Fix failing tests or ensure failures are unrelated to your changes
|
||||
- **Build failures**: Debug and resolve build issues before proceeding
|
||||
| Type | Entity | Field | Record | Status |
|
||||
| ------------- | ---------- | --------- | -------- | ----------------------- |
|
||||
| `tablesdb` | table | column | row | Implemented |
|
||||
| `documentsdb` | collection | attribute | document | Implemented |
|
||||
| `vectorsdb` | -- | -- | -- | Not yet implemented |
|
||||
| `dedicateddb` | table | column | row | Cross-repo (cloud/edge) |
|
||||
|
||||
**Never skip these checks** - they are mandatory quality gates before any work is considered complete.
|
||||
- `useDatabaseSdk()` returns a unified interface regardless of backing type
|
||||
- `useTerminology()` returns singular/plural names for the current database type
|
||||
|
||||
**Trust these instructions** - only search if incomplete/incorrect. See CONTRIBUTING.md for PR conventions. Use
|
||||
`--frozen-lockfile` always. Docker builds: multi-stage, final image is nginx serving static files from `/console` path.
|
||||
### Data loading
|
||||
|
||||
Load functions declare dependencies for cache invalidation via `depends()`:
|
||||
|
||||
```typescript
|
||||
export const load: LayoutLoad = async ({ depends, parent, params }) => {
|
||||
depends(Dependencies.DATABASE);
|
||||
return { database: await sdk.forProject(...).tablesDB.get(...) };
|
||||
};
|
||||
```
|
||||
|
||||
Invalidate with `await invalidate(Dependencies.DATABASE)` after mutations. The `Dependencies` enum in `src/lib/constants.ts` defines 66+ keys for fine-grained cache invalidation (e.g. `DATABASES`, `TABLES`, `FUNCTIONS`, `USERS`, `DEPLOYMENTS`).
|
||||
|
||||
### State management
|
||||
|
||||
Stores in `src/lib/stores/` -- writable, derived, and "conservative" (selective update via `createConservative()` from `$lib/helpers/stores`) patterns. Key stores: `app`, `user`, `organization`, `projects`, `billing`, `wizard`, `notifications`, `sdk`.
|
||||
|
||||
### Wizard pattern (`$lib/stores/wizard`)
|
||||
|
||||
Modal wizard flow: `wizard.start(Component, media?, step?, props?)` to open, `wizard.hide()` to close. Methods: `setInterceptor(callback)` for async pre-step validation, `setNextDisabled(bool)` for flow control, `setStep(n)` / `updateStep(cb)` for navigation, `showCover(Component)` for overlays.
|
||||
|
||||
### Notifications (`$lib/stores/notifications`)
|
||||
|
||||
```typescript
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
addNotification({ type: 'error', message: error.message });
|
||||
```
|
||||
|
||||
Types: `'success' | 'error' | 'info' | 'warning'`. Auto-dismisses after 6s. Max 5 visible.
|
||||
|
||||
### Analytics (`$lib/actions/analytics`)
|
||||
|
||||
Plausible + custom Growth endpoint. Track events via `trackEvent(Click.* | Submit.*, data)` and errors via `trackError(exception, Submit.*)`. Respects `navigator.doNotTrack`.
|
||||
|
||||
### Theming and modes
|
||||
|
||||
Four theme variants in `src/themes/`: `light`, `dark`, `light-cloud`, `dark-cloud`. Resolved based on `isCloud` flag and user preference. Two modes (`src/lib/system.ts`): `cloud` and `self-hosted`, set via `PUBLIC_CONSOLE_MODE` env var. Gate features using `isCloud` (for cloud-only) or `isSelfHosted` (for self-hosted-only).
|
||||
|
||||
## Code style
|
||||
|
||||
- **Formatter:** Prettier -- 4 spaces, single quotes, no trailing commas, 100 char width, bracket same line
|
||||
- **Prefer Svelte 5 runes** in new and modified code (`$props()`, `$state()`, `$derived()`, `$effect()`)
|
||||
- Types from `@appwrite.io/console` SDK (`Models`, `Query`, enums) -- don't redefine what the SDK provides
|
||||
- Error handling: try/catch with `addNotification()` for user-facing errors, `trackError()` for analytics
|
||||
- Queries use the SDK's `Query` builder: `Query.equal()`, `Query.limit()`, `Query.offset()`, etc.
|
||||
- Mark tech debt with `@todo` annotations, never `@fixme`
|
||||
- Don't add new dependencies without consulting the team
|
||||
|
||||
## Environment variables
|
||||
|
||||
Set via `.env` (copy `.env.example`). All prefixed with `PUBLIC_` for SvelteKit:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| ------------------------------------ | --------------------- | ------------------------------ |
|
||||
| `PUBLIC_CONSOLE_MODE` | `self-hosted` | `cloud` or `self-hosted` |
|
||||
| `PUBLIC_APPWRITE_ENDPOINT` | `http://localhost/v1` | API endpoint |
|
||||
| `PUBLIC_APPWRITE_MULTI_REGION` | `false` | Multi-region support |
|
||||
| `PUBLIC_STRIPE_KEY` | -- | Stripe public key (cloud only) |
|
||||
| `PUBLIC_GROWTH_ENDPOINT` | -- | Analytics endpoint |
|
||||
| `PUBLIC_CONSOLE_FEATURE_FLAGS` | -- | Feature flags |
|
||||
| `PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS` | `true` | Mock AI in dev |
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **Blank page in dev:** Disable ad blockers if seeing "Failed to fetch dynamically imported module"
|
||||
- **OOM on build:** Set `NODE_OPTIONS=--max_old_space_size=8192`
|
||||
- **Test failures:** Always use `bun run tests` (runs test:unit with TZ=EST, plus test:e2e), not `bun test` directly
|
||||
- **TS errors not showing:** Run `bun run check` explicitly (dev server doesn't always surface them)
|
||||
- **Format vs lint conflicts:** Run `bun run format` before `bun run lint`
|
||||
- **Stale build:** Clear `.svelte-kit` if changes not reflected: `rm -rf .svelte-kit && bun run build`
|
||||
|
||||
## Branch naming
|
||||
|
||||
`TYPE-ISSUE_ID-DESCRIPTION` (e.g. `feat-548-add-backup-ui`). Types: feat, fix, doc, cicd, refactor.
|
||||
|
||||
## Cross-repo context
|
||||
|
||||
The `feat-dedicated-db` feature spans cloud, edge, and console. When modifying API contracts or response models, check the other repos for breaking changes.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!-- Loads AGENTS.md into AI assistant context -->
|
||||
|
||||
@AGENTS.md
|
||||
@@ -17,7 +17,6 @@ ADD ./static /app/static
|
||||
ARG PUBLIC_CONSOLE_MODE
|
||||
ARG PUBLIC_CONSOLE_FEATURE_FLAGS
|
||||
ARG PUBLIC_APPWRITE_MULTI_REGION
|
||||
ARG PUBLIC_CONSOLE_EMAIL_VERIFICATION
|
||||
ARG PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS
|
||||
ARG PUBLIC_APPWRITE_ENDPOINT
|
||||
ARG PUBLIC_GROWTH_ENDPOINT
|
||||
@@ -31,7 +30,6 @@ ENV PUBLIC_GROWTH_ENDPOINT=$PUBLIC_GROWTH_ENDPOINT
|
||||
ENV PUBLIC_CONSOLE_MODE=$PUBLIC_CONSOLE_MODE
|
||||
ENV PUBLIC_CONSOLE_FEATURE_FLAGS=$PUBLIC_CONSOLE_FEATURE_FLAGS
|
||||
ENV PUBLIC_APPWRITE_MULTI_REGION=$PUBLIC_APPWRITE_MULTI_REGION
|
||||
ENV PUBLIC_CONSOLE_EMAIL_VERIFICATION=$PUBLIC_CONSOLE_EMAIL_VERIFICATION
|
||||
ENV PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=$PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS
|
||||
ENV PUBLIC_STRIPE_KEY=$PUBLIC_STRIPE_KEY
|
||||
ENV PUBLIC_CONSOLE_FINGERPRINT_KEY=$PUBLIC_CONSOLE_FINGERPRINT_KEY
|
||||
|
||||
@@ -28,7 +28,6 @@ async function main() {
|
||||
logEnv('MULTI REGION', env?.PUBLIC_APPWRITE_MULTI_REGION);
|
||||
logEnv('APPWRITE ENDPOINT', env?.PUBLIC_APPWRITE_ENDPOINT, 'relative');
|
||||
logEnv('GROWTH ENDPOINT', env?.PUBLIC_GROWTH_ENDPOINT);
|
||||
logEnv('CONSOLE EMAIL VERIFICATION', env?.PUBLIC_CONSOLE_EMAIL_VERIFICATION);
|
||||
logEnv('CONSOLE MOCK AI SUGGESTIONS', env?.PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS);
|
||||
log();
|
||||
logDelimiter();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "@appwrite/console",
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@e259e61",
|
||||
"@appwrite.io/console": "github:appwrite/sdk-for-console#d27fbd5",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
@@ -24,22 +24,22 @@
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@plausible-analytics/tracker": "^0.4.4",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^8.38.0",
|
||||
"@sentry/sveltekit": "^8.55.1",
|
||||
"@stripe/stripe-js": "^3.5.0",
|
||||
"@threlte/core": "^8.3.1",
|
||||
"@threlte/extras": "^9.7.1",
|
||||
"ai": "^6.0.67",
|
||||
"analytics": "^0.8.16",
|
||||
"@threlte/core": "^8.5.2",
|
||||
"@threlte/extras": "^9.13.0",
|
||||
"ai": "^6.0.138",
|
||||
"analytics": "^0.8.19",
|
||||
"codemirror-json5": "^1.0.3",
|
||||
"cron-parser": "^4.9.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dayjs": "^1.11.20",
|
||||
"deep-equal": "^2.2.3",
|
||||
"echarts": "^5.6.0",
|
||||
"flatted": "^3.4.2",
|
||||
"ignore": "^6.0.2",
|
||||
"json5": "^2.2.3",
|
||||
"nanoid": "^5.1.5",
|
||||
"nanotar": "^0.1.1",
|
||||
"nanoid": "^5.1.7",
|
||||
"nanotar": "^0.3.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"remarkable": "^2.0.1",
|
||||
"svelte-confetti": "^1.4.0",
|
||||
@@ -47,59 +47,62 @@
|
||||
"tippy.js": "^6.3.7",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.1",
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@eslint/compat": "^1.4.1",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.86.5",
|
||||
"@playwright/test": "^1.55.1",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.49.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
"@melt-ui/svelte": "^0.86.6",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.55.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/svelte": "^5.3.1",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@types/three": "^0.182.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"color": "^5.0.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-prettier": "^10.1.0",
|
||||
"eslint-plugin-svelte": "^3.3.3",
|
||||
"globals": "^16.0.0",
|
||||
"color": "^5.0.3",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.16.0",
|
||||
"globals": "^16.5.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"kleur": "^4.1.5",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"sass": "^1.86.0",
|
||||
"svelte": "^5.25.3",
|
||||
"svelte-check": "^4.1.5",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-svelte": "^3.5.1",
|
||||
"sass": "^1.98.0",
|
||||
"svelte": "^5.55.0",
|
||||
"svelte-check": "^4.4.5",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.2",
|
||||
"tldts": "^7.0.7",
|
||||
"svelte-sequential-preprocessor": "^2.0.3",
|
||||
"tldts": "^7.0.27",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^7.0.6",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"vite": "^7.3.1",
|
||||
"vitest": "^3.2.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": {
|
||||
"brace-expansion": ">=5.0.5",
|
||||
"cookie": "^0.7.0",
|
||||
"flatted": "^3.4.2",
|
||||
"immutable": "^5.1.5",
|
||||
"minimatch": "10.2.3",
|
||||
"picomatch": "^4.0.4",
|
||||
"vite": "npm:rolldown-vite@latest",
|
||||
"yaml": "^1.10.3",
|
||||
},
|
||||
"packages": {
|
||||
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
|
||||
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.66", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A=="],
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.87", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.22", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-knLx/VY0u5KAZGgrTorWCTbEnwK3oCCdm8yjxVQm3s14erqVo60SP08dsFWm+xNULPTusftQGVD/l0/hx5QOHg=="],
|
||||
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
@@ -123,7 +126,7 @@
|
||||
|
||||
"@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="],
|
||||
|
||||
"@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@e259e61", { "dependencies": { "json-bigint": "1.0.0" } }],
|
||||
"@appwrite.io/console": ["@appwrite.io/console@github:appwrite/sdk-for-console#d27fbd5", { "dependencies": { "json-bigint": "1.0.0" } }, "appwrite-sdk-for-console-d27fbd5"],
|
||||
|
||||
"@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="],
|
||||
|
||||
@@ -157,11 +160,11 @@
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
|
||||
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
@@ -177,7 +180,7 @@
|
||||
|
||||
"@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="],
|
||||
|
||||
"@codemirror/language": ["@codemirror/language@6.12.2", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg=="],
|
||||
"@codemirror/language": ["@codemirror/language@6.12.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA=="],
|
||||
|
||||
"@codemirror/lint": ["@codemirror/lint@6.9.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA=="],
|
||||
|
||||
@@ -185,7 +188,7 @@
|
||||
|
||||
"@codemirror/state": ["@codemirror/state@6.6.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ=="],
|
||||
|
||||
"@codemirror/view": ["@codemirror/view@6.40.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg=="],
|
||||
"@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
||||
|
||||
"@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="],
|
||||
|
||||
@@ -199,11 +202,11 @@
|
||||
|
||||
"@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.9.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w=="],
|
||||
"@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="],
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
@@ -269,7 +272,7 @@
|
||||
|
||||
"@melt-ui/svelte": ["@melt-ui/svelte@0.86.6", "", { "dependencies": { "@floating-ui/core": "^1.3.1", "@floating-ui/dom": "^1.4.5", "@internationalized/date": "^3.5.0", "dequal": "^2.0.3", "focus-trap": "^7.5.2", "nanoid": "^5.0.4" }, "peerDependencies": { "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.118" } }, "sha512-Jer+M7DgIwT5IHfTayb4Iw/fkkxWNmC/mqn/nMh9JrbPbkxmyabfLQnhJ+JDn5HK77f84j34lubO3iqFtYAfMg=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
@@ -373,7 +376,7 @@
|
||||
|
||||
"@plausible-analytics/tracker": ["@plausible-analytics/tracker@0.4.4", "", {}, "sha512-fz0NOYUEYXtg1TBaPEEvtcBq3FfmLFuTe1VZw4M8sTWX129br5dguu3M15+plOQnc181ShYe67RfwhKgK89VnA=="],
|
||||
|
||||
"@playwright/test": ["@playwright/test@1.58.2", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="],
|
||||
"@playwright/test": ["@playwright/test@1.59.1", "", { "dependencies": { "playwright": "1.59.1" }, "bin": { "playwright": "cli.js" } }, "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg=="],
|
||||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
||||
@@ -409,17 +412,17 @@
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="],
|
||||
|
||||
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@8.55.0", "", { "dependencies": { "@sentry/core": "8.55.0" } }, "sha512-ROgqtQfpH/82AQIpESPqPQe0UyWywKJsmVIqi3c5Fh+zkds5LUxnssTj3yNd1x+kxaPDVB023jAP+3ibNgeNDw=="],
|
||||
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@8.55.1", "", { "dependencies": { "@sentry/core": "8.55.1" } }, "sha512-SipXiwVhJrxzy3/4kf+YIFmpYlLKtGSRD+er7SBCcuSBtv31Fee8IXMDvk+bq24gRXxyjOLUmT//GGXjy2LL6w=="],
|
||||
|
||||
"@sentry-internal/feedback": ["@sentry-internal/feedback@8.55.0", "", { "dependencies": { "@sentry/core": "8.55.0" } }, "sha512-cP3BD/Q6pquVQ+YL+rwCnorKuTXiS9KXW8HNKu4nmmBAyf7urjs+F6Hr1k9MXP5yQ8W3yK7jRWd09Yu6DHWOiw=="],
|
||||
"@sentry-internal/feedback": ["@sentry-internal/feedback@8.55.1", "", { "dependencies": { "@sentry/core": "8.55.1" } }, "sha512-9iFHaT/ijtzB0ffZhXMnt2rPNIXO/dDiCL1G1Bc55rQMPXgawR9AIaAWciyqQjYcbL1DDOhWbzdVqB+kVs5gXw=="],
|
||||
|
||||
"@sentry-internal/replay": ["@sentry-internal/replay@8.55.0", "", { "dependencies": { "@sentry-internal/browser-utils": "8.55.0", "@sentry/core": "8.55.0" } }, "sha512-roCDEGkORwolxBn8xAKedybY+Jlefq3xYmgN2fr3BTnsXjSYOPC7D1/mYqINBat99nDtvgFvNfRcZPiwwZ1hSw=="],
|
||||
"@sentry-internal/replay": ["@sentry-internal/replay@8.55.1", "", { "dependencies": { "@sentry-internal/browser-utils": "8.55.1", "@sentry/core": "8.55.1" } }, "sha512-XaX6r8pXeX47rfiQrSQUwkgxHsDkOKzIT++zfTwrmveVlYSqAhp3x+AKhxAXGmKG62wlmAKQz54GJKcG4cgyKQ=="],
|
||||
|
||||
"@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@8.55.0", "", { "dependencies": { "@sentry-internal/replay": "8.55.0", "@sentry/core": "8.55.0" } }, "sha512-nIkfgRWk1091zHdu4NbocQsxZF1rv1f7bbp3tTIlZYbrH62XVZosx5iHAuZG0Zc48AETLE7K4AX9VGjvQj8i9w=="],
|
||||
"@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@8.55.1", "", { "dependencies": { "@sentry-internal/replay": "8.55.1", "@sentry/core": "8.55.1" } }, "sha512-2sKRu96Qe70y6TiYdYbwkhg4um2prgzH/ZJRItuoSEAjPjoFYYlP+1qjE2CcBw4RPS8/PimV7SFheSaeZs2GCw=="],
|
||||
|
||||
"@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@2.22.6", "", {}, "sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ=="],
|
||||
|
||||
"@sentry/browser": ["@sentry/browser@8.55.0", "", { "dependencies": { "@sentry-internal/browser-utils": "8.55.0", "@sentry-internal/feedback": "8.55.0", "@sentry-internal/replay": "8.55.0", "@sentry-internal/replay-canvas": "8.55.0", "@sentry/core": "8.55.0" } }, "sha512-1A31mCEWCjaMxJt6qGUK+aDnLDcK6AwLAZnqpSchNysGni1pSn1RWSmk9TBF8qyTds5FH8B31H480uxMPUJ7Cw=="],
|
||||
"@sentry/browser": ["@sentry/browser@8.55.1", "", { "dependencies": { "@sentry-internal/browser-utils": "8.55.1", "@sentry-internal/feedback": "8.55.1", "@sentry-internal/replay": "8.55.1", "@sentry-internal/replay-canvas": "8.55.1", "@sentry/core": "8.55.1" } }, "sha512-OEn2eg8h3Mr7BmBGQ28BqbWehYA/NklZ0pAZB1FypPPl+kMd85AbaRdGTnaSjgmpc8bKbBO64edq4Y14sbCs5w=="],
|
||||
|
||||
"@sentry/bundler-plugin-core": ["@sentry/bundler-plugin-core@2.22.6", "", { "dependencies": { "@babel/core": "^7.18.5", "@sentry/babel-plugin-component-annotate": "2.22.6", "@sentry/cli": "^2.36.1", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^9.3.2", "magic-string": "0.30.8", "unplugin": "1.0.1" } }, "sha512-1esQdgSUCww9XAntO4pr7uAM5cfGhLsgTK9MEwAKNfvpMYJi9NUTYa3A7AZmdA8V6107Lo4OD7peIPrDRbaDCg=="],
|
||||
|
||||
@@ -441,15 +444,15 @@
|
||||
|
||||
"@sentry/cli-win32-x64": ["@sentry/cli-win32-x64@2.58.5", "", { "os": "win32", "cpu": "x64" }, "sha512-IZf+XIMiQwj+5NzqbOQfywlOitmCV424Vtf9c+ep61AaVScUFD1TSrQbOcJJv5xGxhlxNOMNgMeZhdexdzrKZg=="],
|
||||
|
||||
"@sentry/core": ["@sentry/core@8.55.0", "", {}, "sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA=="],
|
||||
"@sentry/core": ["@sentry/core@8.55.1", "", {}, "sha512-0ea+yDOgaijR3ba2al1QZxY0bZ9MBZq2a0G+2A0uCBpBkiXnpLFGVAo9UAlEikN1C4M8ROZYiuFU7yZCqacgLQ=="],
|
||||
|
||||
"@sentry/node": ["@sentry/node@8.55.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/instrumentation-amqplib": "^0.46.0", "@opentelemetry/instrumentation-connect": "0.43.0", "@opentelemetry/instrumentation-dataloader": "0.16.0", "@opentelemetry/instrumentation-express": "0.47.0", "@opentelemetry/instrumentation-fastify": "0.44.1", "@opentelemetry/instrumentation-fs": "0.19.0", "@opentelemetry/instrumentation-generic-pool": "0.43.0", "@opentelemetry/instrumentation-graphql": "0.47.0", "@opentelemetry/instrumentation-hapi": "0.45.1", "@opentelemetry/instrumentation-http": "0.57.1", "@opentelemetry/instrumentation-ioredis": "0.47.0", "@opentelemetry/instrumentation-kafkajs": "0.7.0", "@opentelemetry/instrumentation-knex": "0.44.0", "@opentelemetry/instrumentation-koa": "0.47.0", "@opentelemetry/instrumentation-lru-memoizer": "0.44.0", "@opentelemetry/instrumentation-mongodb": "0.51.0", "@opentelemetry/instrumentation-mongoose": "0.46.0", "@opentelemetry/instrumentation-mysql": "0.45.0", "@opentelemetry/instrumentation-mysql2": "0.45.0", "@opentelemetry/instrumentation-nestjs-core": "0.44.0", "@opentelemetry/instrumentation-pg": "0.50.0", "@opentelemetry/instrumentation-redis-4": "0.46.0", "@opentelemetry/instrumentation-tedious": "0.18.0", "@opentelemetry/instrumentation-undici": "0.10.0", "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.28.0", "@prisma/instrumentation": "5.22.0", "@sentry/core": "8.55.0", "@sentry/opentelemetry": "8.55.0", "import-in-the-middle": "^1.11.2" } }, "sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg=="],
|
||||
"@sentry/node": ["@sentry/node@8.55.1", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/instrumentation-amqplib": "^0.46.0", "@opentelemetry/instrumentation-connect": "0.43.0", "@opentelemetry/instrumentation-dataloader": "0.16.0", "@opentelemetry/instrumentation-express": "0.47.0", "@opentelemetry/instrumentation-fastify": "0.44.1", "@opentelemetry/instrumentation-fs": "0.19.0", "@opentelemetry/instrumentation-generic-pool": "0.43.0", "@opentelemetry/instrumentation-graphql": "0.47.0", "@opentelemetry/instrumentation-hapi": "0.45.1", "@opentelemetry/instrumentation-http": "0.57.1", "@opentelemetry/instrumentation-ioredis": "0.47.0", "@opentelemetry/instrumentation-kafkajs": "0.7.0", "@opentelemetry/instrumentation-knex": "0.44.0", "@opentelemetry/instrumentation-koa": "0.47.0", "@opentelemetry/instrumentation-lru-memoizer": "0.44.0", "@opentelemetry/instrumentation-mongodb": "0.51.0", "@opentelemetry/instrumentation-mongoose": "0.46.0", "@opentelemetry/instrumentation-mysql": "0.45.0", "@opentelemetry/instrumentation-mysql2": "0.45.0", "@opentelemetry/instrumentation-nestjs-core": "0.44.0", "@opentelemetry/instrumentation-pg": "0.50.0", "@opentelemetry/instrumentation-redis-4": "0.46.0", "@opentelemetry/instrumentation-tedious": "0.18.0", "@opentelemetry/instrumentation-undici": "0.10.0", "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.28.0", "@prisma/instrumentation": "5.22.0", "@sentry/core": "8.55.1", "@sentry/opentelemetry": "8.55.1", "import-in-the-middle": "^1.11.2" } }, "sha512-s8ydn/OxZFIxc9Fvt23gJkrXkCvPnUu2bDKwjQBCx0M1b4DdNdp4FaimT6B9reya7buj+tsNkZAoT11KqFhG/g=="],
|
||||
|
||||
"@sentry/opentelemetry": ["@sentry/opentelemetry@8.55.0", "", { "dependencies": { "@sentry/core": "8.55.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.28.0" } }, "sha512-UvatdmSr3Xf+4PLBzJNLZ2JjG1yAPWGe/VrJlJAqyTJ2gKeTzgXJJw8rp4pbvNZO8NaTGEYhhO+scLUj0UtLAQ=="],
|
||||
"@sentry/opentelemetry": ["@sentry/opentelemetry@8.55.1", "", { "dependencies": { "@sentry/core": "8.55.1" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.28.0" } }, "sha512-ipiM/k3Hzt8visoBfkDb4AQBWHkJeou3SjoPec7NlDabH/Jj8x6VlK5Hex4z+WOv99rRy+5MUtga/CZnOjvh0A=="],
|
||||
|
||||
"@sentry/svelte": ["@sentry/svelte@8.55.0", "", { "dependencies": { "@sentry/browser": "8.55.0", "@sentry/core": "8.55.0", "magic-string": "^0.30.0" }, "peerDependencies": { "svelte": "3.x || 4.x || 5.x" } }, "sha512-8xQ3RHOUq21f40LWn5eJEgg6rLQfZ+8oBdKLkg03b3SwvfdBs9CrlPkvhhmxdZZslmcGr6ewl0t5WT9ea8Ydlw=="],
|
||||
"@sentry/svelte": ["@sentry/svelte@8.55.1", "", { "dependencies": { "@sentry/browser": "8.55.1", "@sentry/core": "8.55.1", "magic-string": "^0.30.0" }, "peerDependencies": { "svelte": "3.x || 4.x || 5.x" } }, "sha512-INuwEscDDkHY3rnZyYiRCBtI8vz9qkTprTpegMC01W7mWE2vh9bNft8V/c41una8m8m6JLREy+1eYRvd/uDr3A=="],
|
||||
|
||||
"@sentry/sveltekit": ["@sentry/sveltekit@8.55.0", "", { "dependencies": { "@sentry/core": "8.55.0", "@sentry/node": "8.55.0", "@sentry/opentelemetry": "8.55.0", "@sentry/svelte": "8.55.0", "@sentry/vite-plugin": "2.22.6", "magic-string": "0.30.7", "magicast": "0.2.8", "sorcery": "1.0.0" }, "peerDependencies": { "@sveltejs/kit": "1.x || 2.x", "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-fhjv4hn/y/4olSuZLBzQZbD20EcguIzgSYmarc8P/kn9ZVkO5onNDIqgDP0wmFrGVs5ihCPl/gGn9gXV0cXUjQ=="],
|
||||
"@sentry/sveltekit": ["@sentry/sveltekit@8.55.1", "", { "dependencies": { "@sentry/core": "8.55.1", "@sentry/node": "8.55.1", "@sentry/opentelemetry": "8.55.1", "@sentry/svelte": "8.55.1", "@sentry/vite-plugin": "2.22.6", "magic-string": "0.30.7", "magicast": "0.2.8", "sorcery": "1.0.0" }, "peerDependencies": { "@sveltejs/kit": "1.x || 2.x", "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-Kt05HMaWvbtvUYFF5gmZmNBY2Wn1kJnyTXDjYusTxsAtM9RfU05UbOSqedBcOqhSALohZUwHA4/QO3lqgXEsAg=="],
|
||||
|
||||
"@sentry/vite-plugin": ["@sentry/vite-plugin@2.22.6", "", { "dependencies": { "@sentry/bundler-plugin-core": "2.22.6", "unplugin": "1.0.1" } }, "sha512-zIieP1VLWQb3wUjFJlwOAoaaJygJhXeUoGd0e/Ha2RLb2eW2S+4gjf6y6NqyY71tZ74LYVZKg/4prB6FAZSMXQ=="],
|
||||
|
||||
@@ -481,11 +484,11 @@
|
||||
|
||||
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.19", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA=="],
|
||||
"@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="],
|
||||
|
||||
"@tanstack/svelte-virtual": ["@tanstack/svelte-virtual@3.13.22", "", { "dependencies": { "@tanstack/virtual-core": "3.13.22" }, "peerDependencies": { "svelte": "^3.48.0 || ^4.0.0 || ^5.0.0" } }, "sha512-ge1CUEbyWzJzuQnC+vtePEkO0vyA6lINCozpgXmCH39QCwCNC50MErang6n541vK4Poi8eADbDL4qb5afPv4rw=="],
|
||||
"@tanstack/svelte-virtual": ["@tanstack/svelte-virtual@3.13.23", "", { "dependencies": { "@tanstack/virtual-core": "3.13.23" }, "peerDependencies": { "svelte": "^3.48.0 || ^4.0.0 || ^5.0.0" } }, "sha512-kUFdKtevYPhuv9bN2/NhE8zCr6pO3lP2a4xYwyzwqvNZim0EMHdq9nKMFgS4rMHE9Iacdj4+PwMYDfRZ45a/+Q=="],
|
||||
|
||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.22", "", {}, "sha512-isuUGKsc5TAPDoHSbWTbl1SCil54zOS2MiWz/9GCWHPUQOvNTQx8qJEWC7UWR0lShhbK0Lmkcf0SZYxvch7G3g=="],
|
||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.23", "", {}, "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg=="],
|
||||
|
||||
"@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
|
||||
|
||||
@@ -499,9 +502,9 @@
|
||||
|
||||
"@threejs-kit/instanced-sprite-mesh": ["@threejs-kit/instanced-sprite-mesh@2.5.1", "", { "dependencies": { "diet-sprite": "^0.0.1", "earcut": "^2.2.4", "maath": "^0.10.7", "three-instanced-uniforms-mesh": "^0.52.4", "troika-three-utils": "^0.52.4" }, "peerDependencies": { "three": ">=0.170.0" } }, "sha512-pmt1ALRhbHhCJQTj2FuthH6PeLIeaM4hOuS2JO3kWSwlnvx/9xuUkjFR3JOi/myMqsH7pSsLIROSaBxDfttjeA=="],
|
||||
|
||||
"@threlte/core": ["@threlte/core@8.4.1", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.160" } }, "sha512-wjYhAQmT3nHwtLDJUYFpf1wW0n/igjCzrShw9vOtHx1SMtvxzaEigZMseNHn8wfzQuoOxhp/ufFJJnM2+HdHjQ=="],
|
||||
"@threlte/core": ["@threlte/core@8.5.5", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.160" } }, "sha512-jxKalZ3S/ViAjd83txoRdL6/pQmit48a0wl1ZS8VqBr1Vf6ZlNJq2tAieAieb42ICC/OcVPbuJd1Mmms5AC9pQ=="],
|
||||
|
||||
"@threlte/extras": ["@threlte/extras@9.8.1", "", { "dependencies": { "@threejs-kit/instanced-sprite-mesh": "^2.5.1", "camera-controls": "^3.1.2", "three-mesh-bvh": "^0.9.1", "three-perf": "^1.0.11", "three-viewport-gizmo": "^2.2.0", "troika-three-text": "^0.52.4" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.160" } }, "sha512-GNpCtTugDR5JJCYIUJ28P6Onr19z43GqAUBGlUEu05aNaJ4PVyz7em7SRwHNBQEpIjBx/tLVNdd7XoCUKRSFVg=="],
|
||||
"@threlte/extras": ["@threlte/extras@9.14.2", "", { "dependencies": { "@threejs-kit/instanced-sprite-mesh": "^2.5.1", "camera-controls": "^3.1.2", "three-mesh-bvh": "^0.9.1", "three-perf": "^1.0.11", "three-viewport-gizmo": "^2.2.0", "troika-three-text": "^0.52.4" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.160" } }, "sha512-XyxlH+musIYQa70+vN0VtPP7g4tlc/YFKxQscWlroxOQz7flhUJlPtLbt72JU/6JWvwoxWsAD46qR/djrqr1Sw=="],
|
||||
|
||||
"@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="],
|
||||
|
||||
@@ -557,25 +560,25 @@
|
||||
|
||||
"@types/webxr": ["@types/webxr@0.5.24", "", {}, "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.57.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/type-utils": "8.57.0", "@typescript-eslint/utils": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.57.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ=="],
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g=="],
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.0", "@typescript-eslint/types": "^8.57.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w=="],
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0" } }, "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw=="],
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA=="],
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/utils": "8.57.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ=="],
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.57.0", "", {}, "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg=="],
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.0", "@typescript-eslint/tsconfig-utils": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q=="],
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ=="],
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg=="],
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
@@ -607,7 +610,7 @@
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"ai": ["ai@6.0.116", "", { "dependencies": { "@ai-sdk/gateway": "3.0.66", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA=="],
|
||||
"ai": ["ai@6.0.145", "", { "dependencies": { "@ai-sdk/gateway": "3.0.87", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.22", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RbMiFsPZxE4uf5Hhs8rscp5bIwvjQOrqS5dQGWNVRHGM947QZgkKX7Ih5hto8MK/7xkbtneoOZruZ8oSLO828A=="],
|
||||
|
||||
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
||||
|
||||
@@ -641,7 +644,7 @@
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.8", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ=="],
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw=="],
|
||||
|
||||
"bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
|
||||
|
||||
@@ -649,11 +652,11 @@
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
|
||||
"brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
@@ -667,7 +670,7 @@
|
||||
|
||||
"camera-controls": ["camera-controls@3.1.2", "", { "peerDependencies": { "three": ">=0.126.1" } }, "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001779", "", {}, "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
@@ -703,7 +706,7 @@
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
|
||||
|
||||
@@ -801,7 +804,7 @@
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="],
|
||||
"delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
@@ -825,7 +828,7 @@
|
||||
|
||||
"echarts": ["echarts@5.6.0", "", { "dependencies": { "tslib": "2.3.0", "zrender": "5.6.1" } }, "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.313", "", {}, "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="],
|
||||
|
||||
"emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="],
|
||||
|
||||
@@ -849,7 +852,7 @@
|
||||
|
||||
"eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="],
|
||||
|
||||
"eslint-plugin-svelte": ["eslint-plugin-svelte@3.15.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.6.1", "@jridgewell/sourcemap-codec": "^1.5.0", "esutils": "^2.0.3", "globals": "^16.0.0", "known-css-properties": "^0.37.0", "postcss": "^8.4.49", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^7.0.0", "semver": "^7.6.3", "svelte-eslint-parser": "^1.4.0" }, "peerDependencies": { "eslint": "^8.57.1 || ^9.0.0 || ^10.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-k4Nsjs3bHujeEnnckoTM4mFYR1e8Mb9l2rTwNdmYiamA+Tjzn8X+2F+fuSP2w4VbXYhn2bmySyACQYdmUDW2Cg=="],
|
||||
"eslint-plugin-svelte": ["eslint-plugin-svelte@3.17.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.6.1", "@jridgewell/sourcemap-codec": "^1.5.0", "esutils": "^2.0.3", "globals": "^16.0.0", "known-css-properties": "^0.37.0", "postcss": "^8.4.49", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^7.0.0", "semver": "^7.6.3", "svelte-eslint-parser": "^1.4.0" }, "peerDependencies": { "eslint": "^8.57.1 || ^9.0.0 || ^10.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-sF6wgd5FLS2P8CCaOy2HdYYYEcZ6TwL251dLHUkNmtLnWECk1Dwc+j6VeulmmnFxr7Xs0WNtjweOA+bJ0PnaFw=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
@@ -913,7 +916,7 @@
|
||||
|
||||
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
|
||||
|
||||
"fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="],
|
||||
"fuse.js": ["fuse.js@7.2.0", "", {}, "sha512-zf4vdcIGpjNKTuXwug33Hm2okqX6a0t2ZEbez+o9oBJQSNhVJ5AqERfeiRD3r8HcLqP66MrjdkmzxrncbAOTUQ=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
@@ -1137,7 +1140,7 @@
|
||||
|
||||
"nanoid": ["nanoid@5.1.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ=="],
|
||||
|
||||
"nanotar": ["nanotar@0.1.1", "", {}, "sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ=="],
|
||||
"nanotar": ["nanotar@0.3.0", "", {}, "sha512-Kv2JYYiCzt16Kt5QwAc9BFG89xfPNBx+oQL4GQXD9nLqPkZBiNaqaCWtwnbk/q7UVsTYevvM1b0UF8zmEI4pCg=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
@@ -1145,7 +1148,7 @@
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
||||
"node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
@@ -1193,9 +1196,9 @@
|
||||
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="],
|
||||
"playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="],
|
||||
|
||||
"playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="],
|
||||
"playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="],
|
||||
|
||||
"popmotion": ["popmotion@11.0.5", "", { "dependencies": { "framesync": "6.1.2", "hey-listen": "^1.0.8", "style-value-types": "5.1.2", "tslib": "2.4.0" } }, "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA=="],
|
||||
|
||||
@@ -1263,7 +1266,7 @@
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
|
||||
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-beta.53", "", { "dependencies": { "@oxc-project/types": "=0.101.0", "@rolldown/pluginutils": "1.0.0-beta.53" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.53", "@rolldown/binding-darwin-arm64": "1.0.0-beta.53", "@rolldown/binding-darwin-x64": "1.0.0-beta.53", "@rolldown/binding-freebsd-x64": "1.0.0-beta.53", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.53", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.53", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.53", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.53", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.53", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.53", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.53", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.53", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.53" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw=="],
|
||||
|
||||
@@ -1277,7 +1280,7 @@
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"sass": ["sass@1.98.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A=="],
|
||||
"sass": ["sass@1.99.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q=="],
|
||||
|
||||
"saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
|
||||
|
||||
@@ -1285,7 +1288,7 @@
|
||||
|
||||
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="],
|
||||
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
@@ -1343,9 +1346,9 @@
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"svelte": ["svelte@5.53.12", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-4x/uk4rQe/d7RhfvS8wemTfNjQ0bJbKvamIzRBfTe2eHHjzBZ7PZicUQrC2ryj83xxEacfA1zHKd1ephD1tAxA=="],
|
||||
"svelte": ["svelte@5.55.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw=="],
|
||||
|
||||
"svelte-check": ["svelte-check@4.4.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw=="],
|
||||
"svelte-check": ["svelte-check@4.4.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-kP1zG81EWaFe9ZyTv4ZXv44Csi6Pkdpb7S3oj6m+K2ec/IcDg/a8LsFsnVLqm2nxtkSwsd5xPj/qFkTBgXHXjg=="],
|
||||
|
||||
"svelte-confetti": ["svelte-confetti@1.4.0", "", { "peerDependencies": { "svelte": "^4.0.0" } }, "sha512-B0woNwpsFGwhkEoP48BIDQgvW0bMxPhavLVD+E+tsTWevlpr1aiz1S2wA8ArIXX957BiaZWHRHKmI5/pFRDbdg=="],
|
||||
|
||||
@@ -1393,9 +1396,9 @@
|
||||
|
||||
"tippy.js": ["tippy.js@6.3.7", "", { "dependencies": { "@popperjs/core": "^2.9.0" } }, "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ=="],
|
||||
|
||||
"tldts": ["tldts@7.0.25", "", { "dependencies": { "tldts-core": "^7.0.25" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w=="],
|
||||
"tldts": ["tldts@7.0.27", "", { "dependencies": { "tldts-core": "^7.0.27" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg=="],
|
||||
|
||||
"tldts-core": ["tldts-core@7.0.25", "", {}, "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw=="],
|
||||
"tldts-core": ["tldts-core@7.0.27", "", {}, "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
@@ -1413,7 +1416,7 @@
|
||||
|
||||
"troika-worker-utils": ["troika-worker-utils@0.52.0", "", {}, "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
@@ -1423,7 +1426,7 @@
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.57.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.57.0", "@typescript-eslint/parser": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/utils": "8.57.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA=="],
|
||||
"typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="],
|
||||
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
|
||||
@@ -1453,7 +1456,7 @@
|
||||
|
||||
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="],
|
||||
"vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
|
||||
|
||||
"vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
|
||||
|
||||
@@ -1487,7 +1490,7 @@
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
|
||||
"ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
|
||||
|
||||
"xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
|
||||
|
||||
@@ -1497,7 +1500,7 @@
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
|
||||
"yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
@@ -1505,13 +1508,13 @@
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||
|
||||
"zrender": ["zrender@5.6.1", "", { "dependencies": { "tslib": "2.3.0" } }, "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
|
||||
"@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B2OTFcRw/Pdka9ZTjpXv6T6qZ6RruRuLokyb8HwW+aoW9ndJ3YasA3/mVswyJw7VMBF8ofXgqvcrCt9KYvFifg=="],
|
||||
|
||||
"@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@1.0.11", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-CPyImHGiT3svyfmvPvAFTianZzWFtm0qK82XjwlQIA1C3IQ2iku/PMQXi7aFyrX0TyMh3VTkJPB03tjU2VXVrw=="],
|
||||
|
||||
@@ -1555,13 +1558,15 @@
|
||||
|
||||
"@sentry/sveltekit/magic-string": ["magic-string@0.30.7", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA=="],
|
||||
|
||||
"@testing-library/jest-dom/aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||
|
||||
"@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
|
||||
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B2OTFcRw/Pdka9ZTjpXv6T6qZ6RruRuLokyb8HwW+aoW9ndJ3YasA3/mVswyJw7VMBF8ofXgqvcrCt9KYvFifg=="],
|
||||
|
||||
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
@@ -1609,8 +1614,6 @@
|
||||
|
||||
"tough-cookie/tldts/tldts-core": ["tldts-core@6.1.86", "", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="],
|
||||
|
||||
"unplugin/chokidar/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||
|
||||
"unplugin/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"unplugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
+40
-37
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@e259e61",
|
||||
"@appwrite.io/console": "github:appwrite/sdk-for-console#d27fbd5",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
@@ -38,22 +38,22 @@
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@plausible-analytics/tracker": "^0.4.4",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^8.38.0",
|
||||
"@sentry/sveltekit": "^8.55.1",
|
||||
"@stripe/stripe-js": "^3.5.0",
|
||||
"@threlte/core": "^8.3.1",
|
||||
"@threlte/extras": "^9.7.1",
|
||||
"ai": "^6.0.67",
|
||||
"analytics": "^0.8.16",
|
||||
"@threlte/core": "^8.5.2",
|
||||
"@threlte/extras": "^9.13.0",
|
||||
"ai": "^6.0.138",
|
||||
"analytics": "^0.8.19",
|
||||
"codemirror-json5": "^1.0.3",
|
||||
"cron-parser": "^4.9.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dayjs": "^1.11.20",
|
||||
"deep-equal": "^2.2.3",
|
||||
"echarts": "^5.6.0",
|
||||
"flatted": "^3.4.2",
|
||||
"ignore": "^6.0.2",
|
||||
"json5": "^2.2.3",
|
||||
"nanoid": "^5.1.5",
|
||||
"nanotar": "^0.1.1",
|
||||
"nanoid": "^5.1.7",
|
||||
"nanotar": "^0.3.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"remarkable": "^2.0.1",
|
||||
"svelte-confetti": "^1.4.0",
|
||||
@@ -61,51 +61,54 @@
|
||||
"tippy.js": "^6.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.1",
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@eslint/compat": "^1.4.1",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.86.5",
|
||||
"@playwright/test": "^1.55.1",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.49.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
"@melt-ui/svelte": "^0.86.6",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.55.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/svelte": "^5.3.1",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@types/three": "^0.182.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"color": "^5.0.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-prettier": "^10.1.0",
|
||||
"eslint-plugin-svelte": "^3.3.3",
|
||||
"globals": "^16.0.0",
|
||||
"color": "^5.0.3",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.16.0",
|
||||
"globals": "^16.5.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"kleur": "^4.1.5",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"sass": "^1.86.0",
|
||||
"svelte": "^5.25.3",
|
||||
"svelte-check": "^4.1.5",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-svelte": "^3.5.1",
|
||||
"sass": "^1.98.0",
|
||||
"svelte": "^5.55.0",
|
||||
"svelte-check": "^4.4.5",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.2",
|
||||
"tldts": "^7.0.7",
|
||||
"svelte-sequential-preprocessor": "^2.0.3",
|
||||
"tldts": "^7.0.27",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^7.0.6",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"vite": "^7.3.1",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"overrides": {
|
||||
"vite": "npm:rolldown-vite@latest",
|
||||
"minimatch": "10.2.3",
|
||||
"brace-expansion": ">=5.0.5",
|
||||
"immutable": "^5.1.5",
|
||||
"flatted": "^3.4.2",
|
||||
"picomatch": "^4.0.4"
|
||||
"yaml": "^1.10.3",
|
||||
"picomatch": "^4.0.4",
|
||||
"cookie": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+7021
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -1,6 +1,7 @@
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
import { isCloud, isProd } from '$lib/system';
|
||||
import { AppwriteException } from '@appwrite.io/console';
|
||||
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
|
||||
import type { HandleClientError } from '@sveltejs/kit';
|
||||
|
||||
Sentry.init({
|
||||
@@ -12,7 +13,9 @@ Sentry.init({
|
||||
});
|
||||
|
||||
export const handleError: HandleClientError = ({ error, message, status }) => {
|
||||
console.error(error);
|
||||
if (!isVerifyEmailRedirectError(error)) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
let type;
|
||||
if (error instanceof AppwriteException) {
|
||||
|
||||
@@ -46,8 +46,7 @@
|
||||
{
|
||||
callback: toggleCommandCenter,
|
||||
keys: ['k'],
|
||||
ctrl: true,
|
||||
forceEnable: true
|
||||
ctrl: true
|
||||
},
|
||||
{
|
||||
label: 'Toggle debug overlay',
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { browser } from '$app/environment';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { organization, currentPlan } from '$lib/stores/organization';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
const DISMISS_KEY = 'realtimePricingDismissed';
|
||||
|
||||
let dismissed = browser && localStorage.getItem(DISMISS_KEY) === 'true';
|
||||
|
||||
function handleDismiss() {
|
||||
dismissed = true;
|
||||
if (browser) {
|
||||
localStorage.setItem(DISMISS_KEY, 'true');
|
||||
}
|
||||
}
|
||||
|
||||
$: href = $currentPlan?.usagePerProject
|
||||
? `${base}/organization-${$organization.$id}/billing`
|
||||
: `${base}/organization-${$organization.$id}/usage`;
|
||||
</script>
|
||||
|
||||
{#if $organization?.$id && !dismissed}
|
||||
<HeaderAlert
|
||||
type="info"
|
||||
title="Realtime pricing enforcement starting April 30th"
|
||||
dismissible
|
||||
on:dismiss={handleDismiss}>
|
||||
<svelte:fragment>
|
||||
Starting April 30th, realtime usage (connections, messages, and bandwidth) will be
|
||||
charged based on your plan's rates. Review your usage to avoid unexpected charges.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button {href} text fullWidthMobile>
|
||||
<span class="text">View usage</span>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</HeaderAlert>
|
||||
{/if}
|
||||
@@ -67,8 +67,7 @@
|
||||
{/each}
|
||||
{#each Object.entries(org.billingPlanDetails.usage) as [key, usage]}
|
||||
{@const limit = getPlanLimit(key)}
|
||||
{@const show = limit !== false}
|
||||
{#if show}
|
||||
{#if limit !== false}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="resource" {root}>{usage.name}</Table.Cell>
|
||||
<Table.Cell column="limit" {root}>
|
||||
@@ -82,6 +81,18 @@
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
</Table.Row.Base>
|
||||
{:else if usage.price > 0}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="resource" {root}>{usage.name}</Table.Cell>
|
||||
<Table.Cell column="limit" {root}>Pay-as-you-go</Table.Cell>
|
||||
{#if !isFree}
|
||||
<Table.Cell column="rate" {root}>
|
||||
{formatCurrency(usage.price)}/{abbreviateNumber(
|
||||
usage.value
|
||||
)}{usage.unit}
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
</Table.Row.Base>
|
||||
{/if}
|
||||
{/each}
|
||||
</Table.Root>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<Card.Base>
|
||||
<Layout.Stack gap="xl" justifyContent="space-around">
|
||||
<Layout.GridFraction gap="xxxl" rowGap="xl" start={1} end={2}>
|
||||
<Layout.Stack gap="xxs">
|
||||
<div class="card-grid-content">
|
||||
<Layout.Stack gap="xxs" class="card-grid-main">
|
||||
<Typography.Title size="s" truncate><slot name="title" /></Typography.Title>
|
||||
{#if $$slots.default}
|
||||
<Typography.Text>
|
||||
@@ -17,12 +17,12 @@
|
||||
</Typography.Text>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<div style:overflow={overflow ? 'visible' : 'hidden'}>
|
||||
<div class="card-grid-aside" style:overflow={overflow ? 'visible' : 'hidden'}>
|
||||
<Layout.Stack {gap}>
|
||||
<slot name="aside" />
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.GridFraction>
|
||||
</div>
|
||||
{#if $$slots.actions && !hideFooter}
|
||||
<span
|
||||
style="margin-left: calc(-1* var(--space-9));margin-right: calc(-1* var(--space-9));width:auto;">
|
||||
@@ -34,3 +34,25 @@
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
|
||||
<style>
|
||||
.card-grid-content {
|
||||
display: grid;
|
||||
gap: var(--space-10);
|
||||
align-items: start;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.card-grid-main,
|
||||
.card-grid-aside {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.card-grid-content {
|
||||
column-gap: var(--space-13);
|
||||
row-gap: var(--space-10);
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -141,7 +141,8 @@
|
||||
bind:this={tooltip}
|
||||
class:u-width-full-line={fullWidth}
|
||||
style:--arrow-size={`${arrowSize}px`}
|
||||
style:z-index="21">
|
||||
style:z-index="21"
|
||||
style:pointer-events={show ? 'auto' : 'none'}>
|
||||
<div
|
||||
class="drop-arrow"
|
||||
class:is-popover={isPopover}
|
||||
|
||||
@@ -87,11 +87,26 @@
|
||||
value ||
|
||||
arrayValues.length)
|
||||
) {
|
||||
const columnsWithVirtual =
|
||||
!schema && selectedColumn && !$columns.find((c) => c.id === selectedColumn)
|
||||
? [
|
||||
...$columns,
|
||||
{ id: selectedColumn, title: selectedColumn, type: 'string' as const }
|
||||
]
|
||||
: $columns;
|
||||
|
||||
// For distance operators, pass the distance as a separate parameter
|
||||
if (isDistanceOperator && distanceValue !== null && value !== null) {
|
||||
addFilter($columns, selectedColumn, operatorKey, value, arrayValues, distanceValue);
|
||||
addFilter(
|
||||
columnsWithVirtual,
|
||||
selectedColumn,
|
||||
operatorKey,
|
||||
value,
|
||||
arrayValues,
|
||||
distanceValue
|
||||
);
|
||||
} else {
|
||||
addFilter($columns, selectedColumn, operatorKey, value, arrayValues);
|
||||
addFilter(columnsWithVirtual, selectedColumn, operatorKey, value, arrayValues);
|
||||
}
|
||||
selectedColumn = null;
|
||||
value = null;
|
||||
|
||||
@@ -13,34 +13,50 @@
|
||||
</script>
|
||||
|
||||
<Card.Base padding="xs" radius="s" variant="secondary">
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignItems="flex-start"
|
||||
justifyContent="space-between"
|
||||
wrap="wrap"
|
||||
gap="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignItems="flex-start"
|
||||
gap="s"
|
||||
style="min-width: 0; flex: 1 1 16rem;">
|
||||
<Icon icon={IconGithub} color="--fgcolor-neutral-primary" />
|
||||
<Layout.Stack gap="xxxs">
|
||||
<Layout.Stack gap="xxxs" style="min-width: 0; flex: 1 1 auto;">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
|
||||
{repository.name}
|
||||
<span class="repository-copy">{repository.name}</span>
|
||||
</Typography.Text>
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="xs" wrap="wrap">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Last updated: {toLocaleDateTime(repository.pushedAt)}
|
||||
<span class="repository-copy">
|
||||
Last updated: {toLocaleDateTime(repository.pushedAt)}
|
||||
</span>
|
||||
</Typography.Caption>
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
•
|
||||
</Typography.Caption>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="2"
|
||||
height="3"
|
||||
viewBox="0 0 2 3"
|
||||
fill="none">
|
||||
<circle cx="1" cy="1.5" r="1" fill="currentColor" />
|
||||
</svg>
|
||||
<Link
|
||||
size="s"
|
||||
variant="muted"
|
||||
external
|
||||
href={`https://github.com/${repository.organization}`}
|
||||
>{repository.organization}</Link>
|
||||
href={`https://github.com/${repository.organization}`}>
|
||||
<span class="repository-copy">{repository.organization}</span>
|
||||
</Link>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={() => dispatch('disconnect')}>Disconnect</Button>
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
|
||||
<style>
|
||||
.repository-copy {
|
||||
display: inline-block;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
const iconName =
|
||||
product === 'sites'
|
||||
? detection.framework
|
||||
? (detection as unknown as Models.DetectionFramework).framework
|
||||
: (detection as unknown as Models.DetectionRuntime).runtime;
|
||||
const resolved = resolveIconUrl(iconName);
|
||||
iconCache.set(path, resolved);
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { isProjectBlocked as getIsProjectBlocked } from '$lib/helpers/project';
|
||||
|
||||
let showSupport = false;
|
||||
|
||||
@@ -108,6 +109,7 @@
|
||||
}
|
||||
|
||||
$: currentOrg = organizations.find((org) => org.isSelected);
|
||||
$: isProjectBlocked = getIsProjectBlocked(currentProject);
|
||||
|
||||
beforeNavigate(() => (showAccountMenu = false));
|
||||
</script>
|
||||
@@ -137,7 +139,7 @@
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
href={`${base}/project-${currentProject.region}-${currentProject.$id}/get-started`}
|
||||
>Connect</Button.Anchor>
|
||||
disabled={isProjectBlocked}>Connect</Button.Anchor>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -201,6 +203,7 @@
|
||||
<Button.Button
|
||||
variant="text"
|
||||
aria-label="Toggle Command Center"
|
||||
disabled={isProjectBlocked}
|
||||
on:click={toggleCommandCenter}
|
||||
icon>
|
||||
<Icon icon={IconSearch} />
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { NoToneMapping } from 'three';
|
||||
import { Canvas } from '@threlte/core';
|
||||
import ImagineSvg from './imagine.svg';
|
||||
import ImaginationShader from './shader.svelte';
|
||||
</script>
|
||||
|
||||
<div class="imagine-animation">
|
||||
<div class="imagine-canvas">
|
||||
<Canvas toneMapping={NoToneMapping} dpr={1}>
|
||||
<ImaginationShader />
|
||||
</Canvas>
|
||||
</div>
|
||||
|
||||
<img src={ImagineSvg} alt="Imagine" class="imagine-logo" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.imagine-animation {
|
||||
height: 132px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
background-color: #000000;
|
||||
border: 0.795px solid var(--border-neutral-strong);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.imagine-canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.imagine-logo {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: auto;
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
@@ -1,61 +0,0 @@
|
||||
<svg width="1170" height="292" viewBox="0 0 1170 292" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M122.727 20.2045C125.269 20.2045 127.33 18.144 127.33 15.6023C127.33 13.0605 125.269 11 122.727 11C120.186 11 118.125 13.0605 118.125 15.6023C118.125 18.144 120.186 20.2045 122.727 20.2045Z" fill="white"/>
|
||||
<path d="M170.284 18.6705C171.979 18.6705 173.352 17.2968 173.352 15.6023C173.352 13.9078 171.979 12.5341 170.284 12.5341C168.59 12.5341 167.216 13.9078 167.216 15.6023C167.216 17.2968 168.59 18.6705 170.284 18.6705Z" fill="white"/>
|
||||
<path d="M56.7614 40.1477C56.7614 42.6895 54.7009 44.75 52.1591 44.75C49.6173 44.75 47.5568 42.6895 47.5568 40.1477C47.5568 37.606 49.6173 35.5455 52.1591 35.5455C54.7009 35.5455 56.7614 37.606 56.7614 40.1477Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.8977 87.7045C62.8977 80.0793 69.0793 73.8977 76.7045 73.8977C70.7738 73.8977 65.9659 69.0899 65.9659 63.1591C65.9659 57.2283 70.7738 52.4205 76.7045 52.4205C82.6353 52.4205 87.4432 57.2283 87.4432 63.1591C87.4432 56.3811 92.9379 50.8864 99.7159 50.8864C104.262 50.8864 108.231 53.3581 110.352 57.031C111.733 54.2462 114.027 51.9942 116.842 50.6657C113.919 48.7469 111.989 45.4398 111.989 41.6818C111.989 35.751 116.796 30.9432 122.727 30.9432C128.658 30.9432 133.466 35.751 133.466 41.6818C133.466 45.4398 131.536 48.7469 128.612 50.6657C131.359 51.9617 133.609 54.1366 135 56.8276C136.391 54.1366 138.641 51.9617 141.388 50.6657C138.464 48.7469 136.534 45.4398 136.534 41.6818C136.534 35.751 141.342 30.9432 147.273 30.9432C153.204 30.9432 158.011 35.751 158.011 41.6818C158.011 45.4398 156.081 48.7469 153.158 50.6657C155.973 51.9942 158.267 54.2462 159.648 57.031C161.769 53.3581 165.738 50.8864 170.284 50.8864C177.062 50.8864 182.557 56.3811 182.557 63.1591C182.557 66.7513 181.013 69.983 178.554 72.2274C180.964 73.2776 183.081 74.8742 184.748 76.8606C187.099 75.0051 190.068 73.8977 193.295 73.8977C200.921 73.8977 207.102 80.0793 207.102 87.7045C207.102 91.3011 205.727 94.5764 203.474 97.0335C205.387 98.8783 206.865 101.171 207.741 103.742C209.955 100.541 213.653 98.4432 217.841 98.4432C224.619 98.4432 230.114 103.938 230.114 110.716C230.114 115.262 227.642 119.231 223.969 121.352C227.278 122.993 229.835 125.923 230.984 129.486C232.629 125.664 236.428 122.989 240.852 122.989C246.783 122.989 251.591 127.796 251.591 133.727C251.591 139.658 246.783 144.466 240.852 144.466C236.428 144.466 232.629 141.79 230.984 137.969C229.859 141.458 227.383 144.34 224.172 146C227.383 147.66 229.859 150.542 230.984 154.031C232.629 150.21 236.428 147.534 240.852 147.534C246.783 147.534 251.591 152.342 251.591 158.273C251.591 164.204 246.783 169.011 240.852 169.011C236.428 169.011 232.629 166.336 230.984 162.514C229.835 166.077 227.278 169.007 223.969 170.648C227.642 172.769 230.114 176.738 230.114 181.284C230.114 188.062 224.619 193.557 217.841 193.557C213.653 193.557 209.955 191.459 207.741 188.258C206.865 190.829 205.387 193.122 203.474 194.967C205.727 197.424 207.102 200.699 207.102 204.295C207.102 211.921 200.921 218.102 193.295 218.102C190.068 218.102 187.099 216.995 184.748 215.139C183.081 217.126 180.964 218.722 178.554 219.773C181.013 222.017 182.557 225.249 182.557 228.841C182.557 235.619 177.062 241.114 170.284 241.114C165.738 241.114 161.769 238.642 159.648 234.969C158.267 237.754 155.973 240.006 153.158 241.334C156.081 243.253 158.011 246.56 158.011 250.318C158.011 256.249 153.204 261.057 147.273 261.057C141.342 261.057 136.534 256.249 136.534 250.318C136.534 246.56 138.464 243.253 141.388 241.334C138.641 240.038 136.391 237.863 135 235.172C133.609 237.863 131.359 240.038 128.612 241.334C131.536 243.253 133.466 246.56 133.466 250.318C133.466 256.249 128.658 261.057 122.727 261.057C116.796 261.057 111.989 256.249 111.989 250.318C111.989 246.56 113.919 243.253 116.842 241.334C114.027 240.006 111.733 237.754 110.352 234.969C108.231 238.642 104.262 241.114 99.7159 241.114C92.9379 241.114 87.4432 235.619 87.4432 228.841C87.4432 225.249 88.9865 222.017 91.4463 219.773C89.0364 218.722 86.9194 217.126 85.2517 215.139C82.9008 216.995 79.932 218.102 76.7045 218.102C69.0793 218.102 62.8977 211.921 62.8977 204.295C62.8977 201.068 64.0051 198.099 65.8606 195.748C63.8742 194.081 62.2776 191.964 61.2274 189.554C58.983 192.013 55.7513 193.557 52.1591 193.557C45.381 193.557 39.8864 188.062 39.8864 181.284C39.8864 176.738 42.3581 172.769 46.031 170.648C42.7218 169.007 40.1649 166.077 39.0161 162.514C37.3714 166.336 33.572 169.011 29.1477 169.011C23.2169 169.011 18.4091 164.204 18.4091 158.273C18.4091 152.342 23.2169 147.534 29.1477 147.534C33.572 147.534 37.3714 150.21 39.0161 154.031C40.1413 150.542 42.6172 147.66 45.8276 146C42.6172 144.34 40.1413 141.458 39.0161 137.969C37.3714 141.79 33.572 144.466 29.1477 144.466C23.2169 144.466 18.4091 139.658 18.4091 133.727C18.4091 127.796 23.2169 122.989 29.1477 122.989C33.572 122.989 37.3714 125.664 39.0161 129.486C40.1649 125.923 42.7218 122.993 46.031 121.352C42.3581 119.231 39.8864 115.262 39.8864 110.716C39.8864 103.938 45.3811 98.4432 52.1591 98.4432C55.7513 98.4432 58.983 99.9865 61.2274 102.446C62.2776 100.036 63.8742 97.9194 65.8606 96.2517C64.0051 93.9008 62.8977 90.932 62.8977 87.7045ZM76.7045 73.8977C82.6353 73.8977 87.4432 69.0899 87.4432 63.1591C87.4432 66.7513 88.9865 69.983 91.4462 72.2274C89.0364 73.2776 86.9194 74.8742 85.2517 76.8606C82.9008 75.0051 79.932 73.8977 76.7045 73.8977ZM135 202.761C166.348 202.761 191.761 177.348 191.761 146C191.761 114.652 166.348 89.2386 135 89.2386C103.652 89.2386 78.2386 114.652 78.2386 146C78.2386 177.348 103.652 202.761 135 202.761Z" fill="white"/>
|
||||
<path d="M62.8977 87.7045C62.8977 81.7738 58.0899 76.9659 52.1591 76.9659C46.2283 76.9659 41.4205 81.7738 41.4205 87.7045C41.4205 93.6353 46.2283 98.4432 52.1591 98.4432C58.0899 98.4432 62.8977 93.6353 62.8977 87.7045Z" fill="white"/>
|
||||
<path d="M52.1591 193.557C46.2283 193.557 41.4205 198.365 41.4205 204.295C41.4205 210.226 46.2283 215.034 52.1591 215.034C58.0899 215.034 62.8977 210.226 62.8977 204.295C62.8977 198.365 58.0899 193.557 52.1591 193.557Z" fill="white"/>
|
||||
<path d="M76.7045 218.102C82.6353 218.102 87.4432 222.91 87.4432 228.841C87.4432 234.772 82.6353 239.58 76.7045 239.58C70.7738 239.58 65.9659 234.772 65.9659 228.841C65.9659 222.91 70.7738 218.102 76.7045 218.102Z" fill="white"/>
|
||||
<path d="M182.557 228.841C182.557 222.91 187.365 218.102 193.295 218.102C199.226 218.102 204.034 222.91 204.034 228.841C204.034 234.772 199.226 239.58 193.295 239.58C187.365 239.58 182.557 234.772 182.557 228.841Z" fill="white"/>
|
||||
<path d="M207.102 204.295C207.102 210.226 211.91 215.034 217.841 215.034C223.772 215.034 228.58 210.226 228.58 204.295C228.58 198.365 223.772 193.557 217.841 193.557C211.91 193.557 207.102 198.365 207.102 204.295Z" fill="white"/>
|
||||
<path d="M217.841 98.4432C211.91 98.4432 207.102 93.6353 207.102 87.7045C207.102 81.7738 211.91 76.9659 217.841 76.9659C223.772 76.9659 228.58 81.7738 228.58 87.7045C228.58 93.6353 223.772 98.4432 217.841 98.4432Z" fill="white"/>
|
||||
<path d="M193.295 73.8977C199.226 73.8977 204.034 69.0899 204.034 63.1591C204.034 57.2283 199.226 52.4205 193.295 52.4205C187.365 52.4205 182.557 57.2283 182.557 63.1591C182.557 69.0899 187.365 73.8977 193.295 73.8977Z" fill="white"/>
|
||||
<path d="M52.1591 70.8295C56.3954 70.8295 59.8295 67.3954 59.8295 63.1591C59.8295 58.9228 56.3954 55.4886 52.1591 55.4886C47.9228 55.4886 44.4886 58.9228 44.4886 63.1591C44.4886 67.3954 47.9228 70.8295 52.1591 70.8295Z" fill="white"/>
|
||||
<path d="M52.1591 221.17C56.3954 221.17 59.8295 224.605 59.8295 228.841C59.8295 233.077 56.3954 236.511 52.1591 236.511C47.9228 236.511 44.4886 233.077 44.4886 228.841C44.4886 224.605 47.9228 221.17 52.1591 221.17Z" fill="white"/>
|
||||
<path d="M56.7614 251.852C56.7614 254.394 54.7009 256.455 52.1591 256.455C49.6173 256.455 47.5568 254.394 47.5568 251.852C47.5568 249.31 49.6173 247.25 52.1591 247.25C54.7009 247.25 56.7614 249.31 56.7614 251.852Z" fill="white"/>
|
||||
<path d="M222.443 40.1477C222.443 42.6895 220.383 44.75 217.841 44.75C215.299 44.75 213.239 42.6895 213.239 40.1477C213.239 37.606 215.299 35.5455 217.841 35.5455C220.383 35.5455 222.443 37.606 222.443 40.1477Z" fill="white"/>
|
||||
<path d="M222.443 251.852C222.443 254.394 220.383 256.455 217.841 256.455C215.299 256.455 213.239 254.394 213.239 251.852C213.239 249.31 215.299 247.25 217.841 247.25C220.383 247.25 222.443 249.31 222.443 251.852Z" fill="white"/>
|
||||
<path d="M170.284 273.33C171.979 273.33 173.352 274.703 173.352 276.398C173.352 278.092 171.979 279.466 170.284 279.466C168.59 279.466 167.216 278.092 167.216 276.398C167.216 274.703 168.59 273.33 170.284 273.33Z" fill="white"/>
|
||||
<path d="M99.7159 18.6705C101.41 18.6705 102.784 17.2968 102.784 15.6023C102.784 13.9078 101.41 12.5341 99.7159 12.5341C98.0214 12.5341 96.6477 13.9078 96.6477 15.6023C96.6477 17.2968 98.0214 18.6705 99.7159 18.6705Z" fill="white"/>
|
||||
<path d="M99.7159 273.33C101.41 273.33 102.784 274.703 102.784 276.398C102.784 278.092 101.41 279.466 99.7159 279.466C98.0214 279.466 96.6477 278.092 96.6477 276.398C96.6477 274.703 98.0214 273.33 99.7159 273.33Z" fill="white"/>
|
||||
<path d="M127.33 276.398C127.33 273.856 125.269 271.795 122.727 271.795C120.186 271.795 118.125 273.856 118.125 276.398C118.125 278.939 120.186 281 122.727 281C125.269 281 127.33 278.939 127.33 276.398Z" fill="white"/>
|
||||
<path d="M151.875 15.6023C151.875 18.144 149.814 20.2045 147.273 20.2045C144.731 20.2045 142.67 18.144 142.67 15.6023C142.67 13.0605 144.731 11 147.273 11C149.814 11 151.875 13.0605 151.875 15.6023Z" fill="white"/>
|
||||
<path d="M4.60227 107.648C6.29678 107.648 7.67045 109.021 7.67045 110.716C7.67045 112.41 6.29678 113.784 4.60227 113.784C2.90776 113.784 1.53409 112.41 1.53409 110.716C1.53409 109.021 2.90776 107.648 4.60227 107.648Z" fill="white"/>
|
||||
<path d="M4.60227 178.216C6.29678 178.216 7.67045 179.59 7.67045 181.284C7.67045 182.979 6.29678 184.352 4.60227 184.352C2.90776 184.352 1.53409 182.979 1.53409 181.284C1.53409 179.59 2.90776 178.216 4.60227 178.216Z" fill="white"/>
|
||||
<path d="M4.60227 153.67C7.14404 153.67 9.20455 155.731 9.20455 158.273C9.20455 160.814 7.14404 162.875 4.60227 162.875C2.06051 162.875 0 160.814 0 158.273C0 155.731 2.06051 153.67 4.60227 153.67Z" fill="white"/>
|
||||
<path d="M4.60227 129.125C7.14404 129.125 9.20455 131.186 9.20455 133.727C9.20455 136.269 7.14404 138.33 4.60227 138.33C2.06051 138.33 0 136.269 0 133.727C0 131.186 2.06051 129.125 4.60227 129.125Z" fill="white"/>
|
||||
<path d="M265.398 107.648C267.092 107.648 268.466 109.021 268.466 110.716C268.466 112.41 267.092 113.784 265.398 113.784C263.703 113.784 262.33 112.41 262.33 110.716C262.33 109.021 263.703 107.648 265.398 107.648Z" fill="white"/>
|
||||
<path d="M265.398 178.216C267.092 178.216 268.466 179.59 268.466 181.284C268.466 182.979 267.092 184.352 265.398 184.352C263.703 184.352 262.33 182.979 262.33 181.284C262.33 179.59 263.703 178.216 265.398 178.216Z" fill="white"/>
|
||||
<path d="M265.398 153.67C267.939 153.67 270 155.731 270 158.273C270 160.814 267.939 162.875 265.398 162.875C262.856 162.875 260.795 160.814 260.795 158.273C260.795 155.731 262.856 153.67 265.398 153.67Z" fill="white"/>
|
||||
<path d="M265.398 129.125C267.939 129.125 270 131.186 270 133.727C270 136.269 267.939 138.33 265.398 138.33C262.856 138.33 260.795 136.269 260.795 133.727C260.795 131.186 262.856 129.125 265.398 129.125Z" fill="white"/>
|
||||
<path d="M151.875 276.398C151.875 273.856 149.814 271.795 147.273 271.795C144.731 271.795 142.67 273.856 142.67 276.398C142.67 278.939 144.731 281 147.273 281C149.814 281 151.875 278.939 151.875 276.398Z" fill="white"/>
|
||||
<path d="M35.2841 87.7045C35.2841 91.0936 32.5367 93.8409 29.1477 93.8409C25.7587 93.8409 23.0114 91.0936 23.0114 87.7045C23.0114 84.3155 25.7587 81.5682 29.1477 81.5682C32.5367 81.5682 35.2841 84.3155 35.2841 87.7045Z" fill="white"/>
|
||||
<path d="M35.2841 204.295C35.2841 200.906 32.5367 198.159 29.1477 198.159C25.7587 198.159 23.0114 200.906 23.0114 204.295C23.0114 207.684 25.7587 210.432 29.1477 210.432C32.5367 210.432 35.2841 207.684 35.2841 204.295Z" fill="white"/>
|
||||
<path d="M246.989 87.7045C246.989 91.0936 244.241 93.8409 240.852 93.8409C237.463 93.8409 234.716 91.0936 234.716 87.7045C234.716 84.3155 237.463 81.5682 240.852 81.5682C244.241 81.5682 246.989 84.3155 246.989 87.7045Z" fill="white"/>
|
||||
<path d="M246.989 204.295C246.989 200.906 244.241 198.159 240.852 198.159C237.463 198.159 234.716 200.906 234.716 204.295C234.716 207.684 237.463 210.432 240.852 210.432C244.241 210.432 246.989 207.684 246.989 204.295Z" fill="white"/>
|
||||
<path d="M245.455 63.1591C245.455 65.7009 243.394 67.7614 240.852 67.7614C238.31 67.7614 236.25 65.7009 236.25 63.1591C236.25 60.6173 238.31 58.5568 240.852 58.5568C243.394 58.5568 245.455 60.6173 245.455 63.1591Z" fill="white"/>
|
||||
<path d="M245.455 228.841C245.455 226.299 243.394 224.239 240.852 224.239C238.31 224.239 236.25 226.299 236.25 228.841C236.25 231.383 238.31 233.443 240.852 233.443C243.394 233.443 245.455 231.383 245.455 228.841Z" fill="white"/>
|
||||
<path d="M33.75 63.1591C33.75 65.7009 31.6895 67.7614 29.1477 67.7614C26.606 67.7614 24.5455 65.7009 24.5455 63.1591C24.5455 60.6173 26.606 58.5568 29.1477 58.5568C31.6895 58.5568 33.75 60.6173 33.75 63.1591Z" fill="white"/>
|
||||
<path d="M33.75 228.841C33.75 226.299 31.6895 224.239 29.1477 224.239C26.606 224.239 24.5455 226.299 24.5455 228.841C24.5455 231.383 26.606 233.443 29.1477 233.443C31.6895 233.443 33.75 231.383 33.75 228.841Z" fill="white"/>
|
||||
<path d="M225.511 63.1591C225.511 67.3954 222.077 70.8295 217.841 70.8295C213.605 70.8295 210.17 67.3954 210.17 63.1591C210.17 58.9228 213.605 55.4886 217.841 55.4886C222.077 55.4886 225.511 58.9228 225.511 63.1591Z" fill="white"/>
|
||||
<path d="M225.511 228.841C225.511 224.605 222.077 221.17 217.841 221.17C213.605 221.17 210.17 224.605 210.17 228.841C210.17 233.077 213.605 236.511 217.841 236.511C222.077 236.511 225.511 233.077 225.511 228.841Z" fill="white"/>
|
||||
<path d="M108.92 40.1477C108.92 45.2313 104.799 49.3523 99.7159 49.3523C94.6324 49.3523 90.5114 45.2313 90.5114 40.1477C90.5114 35.0642 94.6324 30.9432 99.7159 30.9432C104.799 30.9432 108.92 35.0642 108.92 40.1477Z" fill="white"/>
|
||||
<path d="M108.92 251.852C108.92 246.769 104.799 242.648 99.7159 242.648C94.6324 242.648 90.5114 246.769 90.5114 251.852C90.5114 256.936 94.6324 261.057 99.7159 261.057C104.799 261.057 108.92 256.936 108.92 251.852Z" fill="white"/>
|
||||
<path d="M29.1477 119.92C34.2313 119.92 38.3523 115.799 38.3523 110.716C38.3523 105.632 34.2313 101.511 29.1477 101.511C24.0642 101.511 19.9432 105.632 19.9432 110.716C19.9432 115.799 24.0642 119.92 29.1477 119.92Z" fill="white"/>
|
||||
<path d="M240.852 119.92C245.936 119.92 250.057 115.799 250.057 110.716C250.057 105.632 245.936 101.511 240.852 101.511C235.769 101.511 231.648 105.632 231.648 110.716C231.648 115.799 235.769 119.92 240.852 119.92Z" fill="white"/>
|
||||
<path d="M82.8409 40.1477C82.8409 43.5367 80.0936 46.2841 76.7045 46.2841C73.3155 46.2841 70.5682 43.5367 70.5682 40.1477C70.5682 36.7587 73.3155 34.0114 76.7045 34.0114C80.0936 34.0114 82.8409 36.7587 82.8409 40.1477Z" fill="white"/>
|
||||
<path d="M82.8409 251.852C82.8409 248.463 80.0936 245.716 76.7045 245.716C73.3155 245.716 70.5682 248.463 70.5682 251.852C70.5682 255.241 73.3155 257.989 76.7045 257.989C80.0936 257.989 82.8409 255.241 82.8409 251.852Z" fill="white"/>
|
||||
<path d="M199.432 40.1477C199.432 43.5367 196.684 46.2841 193.295 46.2841C189.906 46.2841 187.159 43.5367 187.159 40.1477C187.159 36.7587 189.906 34.0114 193.295 34.0114C196.684 34.0114 199.432 36.7587 199.432 40.1477Z" fill="white"/>
|
||||
<path d="M199.432 251.852C199.432 248.463 196.684 245.716 193.295 245.716C189.906 245.716 187.159 248.463 187.159 251.852C187.159 255.241 189.906 257.989 193.295 257.989C196.684 257.989 199.432 255.241 199.432 251.852Z" fill="white"/>
|
||||
<path d="M179.489 40.1477C179.489 45.2313 175.368 49.3523 170.284 49.3523C165.201 49.3523 161.08 45.2313 161.08 40.1477C161.08 35.0642 165.201 30.9432 170.284 30.9432C175.368 30.9432 179.489 35.0642 179.489 40.1477Z" fill="white"/>
|
||||
<path d="M179.489 251.852C179.489 246.769 175.368 242.648 170.284 242.648C165.201 242.648 161.08 246.769 161.08 251.852C161.08 256.936 165.201 261.057 170.284 261.057C175.368 261.057 179.489 256.936 179.489 251.852Z" fill="white"/>
|
||||
<path d="M29.1477 190.489C34.2313 190.489 38.3523 186.368 38.3523 181.284C38.3523 176.201 34.2313 172.08 29.1477 172.08C24.0642 172.08 19.9432 176.201 19.9432 181.284C19.9432 186.368 24.0642 190.489 29.1477 190.489Z" fill="white"/>
|
||||
<path d="M240.852 190.489C245.936 190.489 250.057 186.368 250.057 181.284C250.057 176.201 245.936 172.08 240.852 172.08C235.769 172.08 231.648 176.201 231.648 181.284C231.648 186.368 235.769 190.489 240.852 190.489Z" fill="white"/>
|
||||
<path d="M1106.03 231.675C1064.38 231.675 1040.86 202.52 1040.86 165.035C1040.86 127.795 1066.09 98.885 1102.84 98.885C1143.27 98.885 1164.83 129.755 1164.83 174.59H1073.69C1076.14 193.945 1086.92 205.95 1105.78 205.95C1118.77 205.95 1126.36 200.07 1130.04 190.515H1162.87C1158.21 212.81 1138.37 231.675 1106.03 231.675ZM1103.33 124.61C1085.69 124.61 1077.12 135.145 1074.18 151.56H1130.04C1129.06 135.635 1118.52 124.61 1103.33 124.61Z" fill="white"/>
|
||||
<path d="M950.004 102.315V119.465H950.739C959.314 105.99 970.829 98.885 987.979 98.885C1013.95 98.885 1031.34 118.485 1031.34 145.925V228H998.024V150.825C998.024 137.35 990.184 127.795 976.219 127.795C961.519 127.795 950.739 139.555 950.739 156.705V228H917.419V102.315H950.004Z" fill="white"/>
|
||||
<path d="M867.529 228V102.315H900.849V228H867.529ZM867.529 82.715V52.825H900.849V82.715H867.529Z" fill="white"/>
|
||||
<path d="M790.704 270.875C756.404 270.875 735.334 256.175 731.904 231.675H764.979C767.674 239.515 774.779 245.64 790.214 245.64C809.079 245.64 818.144 236.575 818.144 219.425V205.705H817.409C810.059 214.035 800.504 220.405 784.579 220.405C756.649 220.405 729.209 198.355 729.209 160.38C729.209 122.895 751.749 98.885 783.599 98.885C799.279 98.885 810.794 105.01 818.389 115.545H818.879V102.315H850.974V218.445C850.974 236.085 845.339 248.09 836.029 256.665C825.494 266.465 809.324 270.875 790.704 270.875ZM790.214 193.7C811.284 193.7 819.859 178.265 819.859 159.645C819.859 141.27 810.059 125.59 789.969 125.59C773.064 125.59 761.794 138.82 761.794 159.89C761.794 181.205 773.064 193.7 790.214 193.7Z" fill="white"/>
|
||||
<path d="M691.366 228C689.406 225.55 688.181 219.67 687.446 214.035H686.956C680.586 223.59 671.521 230.94 649.961 230.94C624.236 230.94 606.106 217.465 606.106 192.475C606.106 164.79 628.646 155.97 656.576 152.05C677.401 149.11 686.956 147.395 686.956 137.84C686.956 128.775 679.851 122.895 665.886 122.895C650.206 122.895 642.611 128.53 641.631 140.535H611.986C612.966 118.485 629.381 99.13 666.131 99.13C703.861 99.13 719.051 116.035 719.051 145.435V209.38C719.051 218.935 720.521 224.57 723.461 226.775V228H691.366ZM658.046 207.665C677.156 207.665 687.691 195.905 687.691 183.655V164.79C681.811 168.22 672.746 170.18 664.416 172.14C647.021 176.06 638.446 179.98 638.446 191.74C638.446 203.5 646.286 207.665 658.046 207.665Z" fill="white"/>
|
||||
<path d="M412.805 228V102.315H444.9V119.22H445.635C452.495 107.705 464.745 98.885 482.14 98.885C498.065 98.885 510.805 107.705 516.93 120.935H517.42C525.995 107.215 539.225 98.885 555.15 98.885C581.61 98.885 596.555 116.035 596.555 143.475V228H563.235V149.11C563.235 134.9 556.13 127.55 543.88 127.55C529.915 127.55 521.34 138.33 521.34 154.99V228H488.02V149.11C488.02 134.9 480.915 127.55 468.665 127.55C455.19 127.55 446.125 138.33 446.125 154.99V228H412.805Z" fill="white"/>
|
||||
<path d="M357.72 228V52.825H393.245V228H357.72Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,109 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { T, useTask, useThrelte } from '@threlte/core';
|
||||
import { Uniform, MeshBasicMaterial, Vector2 } from 'three';
|
||||
import noise from './threlte/shaders/noise.glsl?raw';
|
||||
import { onMount } from 'svelte';
|
||||
const { invalidate, size } = useThrelte();
|
||||
|
||||
let uAspect: Uniform;
|
||||
let uOpacity: Uniform;
|
||||
let uTime: Uniform;
|
||||
let uViewportSize: Uniform;
|
||||
|
||||
$effect(() => {
|
||||
const width = $size.width || 1;
|
||||
const height = $size.height || 1;
|
||||
if (uAspect) {
|
||||
uAspect.value = width / height;
|
||||
invalidate();
|
||||
}
|
||||
if (uViewportSize) {
|
||||
uViewportSize.value.x = width;
|
||||
uViewportSize.value.y = height;
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
let material = $state<MeshBasicMaterial>(new MeshBasicMaterial());
|
||||
|
||||
onMount(() => {
|
||||
const width = $size.width || 1;
|
||||
const height = $size.height || 1;
|
||||
|
||||
uAspect = new Uniform(width / height);
|
||||
uOpacity = new Uniform(0);
|
||||
uTime = new Uniform(0);
|
||||
uViewportSize = new Uniform(new Vector2(width, height));
|
||||
|
||||
material.onBeforeCompile = (shader) => {
|
||||
shader.uniforms.uTime = uTime;
|
||||
shader.uniforms.uAspect = uAspect;
|
||||
shader.uniforms.uOpacity = uOpacity;
|
||||
shader.uniforms.uViewportSize = uViewportSize;
|
||||
shader.fragmentShader = `
|
||||
uniform float uTime;
|
||||
uniform float uAspect;
|
||||
uniform float uOpacity;
|
||||
uniform vec2 uViewportSize;
|
||||
${noise}
|
||||
|
||||
float getValue(vec2 uv, vec2 screenPos){
|
||||
vec2 cID = floor(uv);
|
||||
vec2 cUV = fract(uv);
|
||||
|
||||
vec3 gradient, dg, dg2;
|
||||
float n = psrddnoise(vec3(cID * 0.03, uTime * 0.08), vec3(0.0), uTime * 0.25, gradient, dg, dg2);
|
||||
n = abs(n);
|
||||
|
||||
// Radial gradient masking - reduce noise in center
|
||||
vec2 centerDist = (screenPos - 0.5) * vec2(1.0, 1.0 / 0.7);
|
||||
float distFromCenter = length(centerDist) * 2.0;
|
||||
float centerMask = 1.0 - smoothstep(0.65, 1.3, distFromCenter);
|
||||
n = n * (1.0 - centerMask * 0.95);
|
||||
|
||||
float r = sqrt(2.) * (1. - n * 0.4);
|
||||
float fw = length(fwidth(uv));
|
||||
float fCircle = smoothstep(r, r + fw, length(cUV - 0.5) * 2.);
|
||||
return fCircle;
|
||||
}
|
||||
${shader.fragmentShader}`.replace(
|
||||
`vec4 diffuseColor = vec4( diffuse, opacity );`,
|
||||
`
|
||||
vec3 col = diffuse;
|
||||
|
||||
// Optimized for small size - larger dots
|
||||
float scaleFactor = 45.0;
|
||||
|
||||
vec2 uv = (vUv - 0.5) * vec2(scaleFactor, scaleFactor / uAspect);
|
||||
vec2 shift = vec2(-0.12, 1.25);
|
||||
|
||||
col.r = getValue(uv + shift, vUv);
|
||||
col.g = getValue(uv, vUv);
|
||||
col.b = getValue(uv - shift, vUv);
|
||||
|
||||
float overlap = min(min(col.r, col.g), col.b);
|
||||
col = mix(col, vec3(1.0), overlap * 0.5);
|
||||
|
||||
float alpha = max(max(col.r, col.g), col.b) * uOpacity;
|
||||
vec4 diffuseColor = vec4(col, alpha);
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
material.defines = { USE_UV: '' };
|
||||
material.transparent = true;
|
||||
|
||||
uOpacity.value = 1;
|
||||
uTime.value = 0;
|
||||
invalidate();
|
||||
});
|
||||
|
||||
useTask((delta) => {
|
||||
uTime.value += delta;
|
||||
});
|
||||
</script>
|
||||
|
||||
<T.OrthographicCamera position={[0, 0, 10]} fov={10} near={0.1} far={1000} makeDefault />
|
||||
<T.Mesh scale={[$size.width || 1, $size.height || 1, 1]} {material}>
|
||||
<T.PlaneGeometry args={[1, 1]} />
|
||||
</T.Mesh>
|
||||
@@ -1,166 +0,0 @@
|
||||
//
|
||||
// psrddnoise3.glsl
|
||||
//
|
||||
// Authors: Stefan Gustavson (stefan.gustavson@gmail.com)
|
||||
// and Ian McEwan (ijm567@gmail.com)
|
||||
// Version 2021-12-02, published under the MIT license (see below)
|
||||
//
|
||||
// Copyright (c) 2021 Stefan Gustavson and Ian McEwan.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
vec4 permute(vec4 i) {
|
||||
vec4 im = mod(i, 289.0);
|
||||
return mod(((im*34.0)+10.0)*im, 289.0);
|
||||
}
|
||||
|
||||
float psrddnoise(vec3 x, vec3 period, float alpha, out vec3 gradient,
|
||||
out vec3 dg, out vec3 dg2)
|
||||
{
|
||||
const mat3 M = mat3(0.0, 1.0, 1.0,
|
||||
1.0, 0.0, 1.0,
|
||||
1.0, 1.0, 0.0);
|
||||
|
||||
const mat3 Mi = mat3(-0.5, 0.5, 0.5,
|
||||
0.5,-0.5, 0.5,
|
||||
0.5, 0.5,-0.5);
|
||||
|
||||
vec3 uvw;
|
||||
uvw = M * x;
|
||||
|
||||
vec3 i0 = floor(uvw);
|
||||
vec3 f0 = fract(uvw);
|
||||
|
||||
vec3 g_ = step(f0.xyx, f0.yzz);
|
||||
vec3 l_ = 1.0 - g_;
|
||||
vec3 g = vec3(l_.z, g_.xy);
|
||||
vec3 l = vec3(l_.xy, g_.z);
|
||||
vec3 o1 = min( g, l );
|
||||
vec3 o2 = max( g, l );
|
||||
|
||||
vec3 i1 = i0 + o1;
|
||||
vec3 i2 = i0 + o2;
|
||||
vec3 i3 = i0 + vec3(1.0);
|
||||
|
||||
vec3 v0, v1, v2, v3;
|
||||
|
||||
v0 = Mi * i0;
|
||||
v1 = Mi * i1;
|
||||
v2 = Mi * i2;
|
||||
v3 = Mi * i3;
|
||||
|
||||
vec3 x0 = x - v0;
|
||||
vec3 x1 = x - v1;
|
||||
vec3 x2 = x - v2;
|
||||
vec3 x3 = x - v3;
|
||||
|
||||
if(any(greaterThan(period, vec3(0.0)))) {
|
||||
vec4 vx = vec4(v0.x, v1.x, v2.x, v3.x);
|
||||
vec4 vy = vec4(v0.y, v1.y, v2.y, v3.y);
|
||||
vec4 vz = vec4(v0.z, v1.z, v2.z, v3.z);
|
||||
if(period.x > 0.0) vx = mod(vx, period.x);
|
||||
if(period.y > 0.0) vy = mod(vy, period.y);
|
||||
if(period.z > 0.0) vz = mod(vz, period.z);
|
||||
i0 = M * vec3(vx.x, vy.x, vz.x);
|
||||
i1 = M * vec3(vx.y, vy.y, vz.y);
|
||||
i2 = M * vec3(vx.z, vy.z, vz.z);
|
||||
i3 = M * vec3(vx.w, vy.w, vz.w);
|
||||
i0 = floor(i0 + 0.5);
|
||||
i1 = floor(i1 + 0.5);
|
||||
i2 = floor(i2 + 0.5);
|
||||
i3 = floor(i3 + 0.5);
|
||||
}
|
||||
|
||||
vec4 hash = permute( permute( permute(
|
||||
vec4(i0.z, i1.z, i2.z, i3.z ))
|
||||
+ vec4(i0.y, i1.y, i2.y, i3.y ))
|
||||
+ vec4(i0.x, i1.x, i2.x, i3.x ));
|
||||
|
||||
vec4 theta = hash * 3.883222077;
|
||||
vec4 sz = hash * -0.006920415 + 0.996539792;
|
||||
vec4 psi = hash * 0.108705628 ;
|
||||
|
||||
vec4 Ct = cos(theta);
|
||||
vec4 St = sin(theta);
|
||||
vec4 sz_prime = sqrt( 1.0 - sz*sz );
|
||||
|
||||
vec4 gx, gy, gz;
|
||||
|
||||
if(alpha != 0.0) {
|
||||
vec4 Sp = sin(psi);
|
||||
vec4 Cp = cos(psi);
|
||||
|
||||
vec4 px = Ct * sz_prime;
|
||||
vec4 py = St * sz_prime;
|
||||
vec4 pz = sz;
|
||||
|
||||
vec4 Ctp = St*Sp - Ct*Cp;
|
||||
vec4 qx = mix( Ctp*St, Sp, sz);
|
||||
vec4 qy = mix(-Ctp*Ct, Cp, sz);
|
||||
vec4 qz = -(py*Cp + px*Sp);
|
||||
|
||||
vec4 Sa = vec4(sin(alpha));
|
||||
vec4 Ca = vec4(cos(alpha));
|
||||
|
||||
gx = Ca * px + Sa * qx;
|
||||
gy = Ca * py + Sa * qy;
|
||||
gz = Ca * pz + Sa * qz;
|
||||
}
|
||||
else {
|
||||
gx = Ct * sz_prime;
|
||||
gy = St * sz_prime;
|
||||
gz = sz;
|
||||
}
|
||||
|
||||
vec3 g0 = vec3(gx.x, gy.x, gz.x);
|
||||
vec3 g1 = vec3(gx.y, gy.y, gz.y);
|
||||
vec3 g2 = vec3(gx.z, gy.z, gz.z);
|
||||
vec3 g3 = vec3(gx.w, gy.w, gz.w);
|
||||
|
||||
vec4 w = 0.5 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3));
|
||||
w = max(w, 0.0);
|
||||
vec4 w2 = w * w;
|
||||
vec4 w3 = w2 * w;
|
||||
|
||||
vec4 gdotx = vec4(dot(g0,x0), dot(g1,x1), dot(g2,x2), dot(g3,x3));
|
||||
|
||||
float n = dot(w3, gdotx);
|
||||
|
||||
vec4 dw = -6.0 * w2 * gdotx;
|
||||
vec3 dn0 = w3.x * g0 + dw.x * x0;
|
||||
vec3 dn1 = w3.y * g1 + dw.y * x1;
|
||||
vec3 dn2 = w3.z * g2 + dw.z * x2;
|
||||
vec3 dn3 = w3.w * g3 + dw.w * x3;
|
||||
gradient = 39.5 * (dn0 + dn1 + dn2 + dn3);
|
||||
|
||||
vec4 dw2 = 24.0 * w * gdotx;
|
||||
vec3 dga0 = dw2.x * x0 * x0 - 6.0 * w2.x * (gdotx.x + 2.0 * g0 * x0);
|
||||
vec3 dga1 = dw2.y * x1 * x1 - 6.0 * w2.y * (gdotx.y + 2.0 * g1 * x1);
|
||||
vec3 dga2 = dw2.z * x2 * x2 - 6.0 * w2.z * (gdotx.z + 2.0 * g2 * x2);
|
||||
vec3 dga3 = dw2.w * x3 * x3 - 6.0 * w2.w * (gdotx.w + 2.0 * g3 * x3);
|
||||
dg = 35.0 * (dga0 + dga1 + dga2 + dga3);
|
||||
vec3 dgb0 = dw2.x * x0 * x0.yzx - 6.0 * w2.x * (g0 * x0.yzx + g0.yzx * x0);
|
||||
vec3 dgb1 = dw2.y * x1 * x1.yzx - 6.0 * w2.y * (g1 * x1.yzx + g1.yzx * x1);
|
||||
vec3 dgb2 = dw2.z * x2 * x2.yzx - 6.0 * w2.z * (g2 * x2.yzx + g2.yzx * x2);
|
||||
vec3 dgb3 = dw2.w * x3 * x3.yzx - 6.0 * w2.w * (g3 * x3.yzx + g3.yzx * x3);
|
||||
dg2 = 39.5 * (dgb0 + dgb1 + dgb2 + dgb3);
|
||||
|
||||
return 39.5 * n;
|
||||
}
|
||||
@@ -124,7 +124,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div slot="middle" class="middle-container" class:icons={state === 'icons'}>
|
||||
{#if progressCard}
|
||||
{#if progressCard && project}
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
class="progress-card"
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
</div>
|
||||
|
||||
{#if option.showSupport}
|
||||
<div class="u-flex u-gap-12 u-cross-center">
|
||||
<div class="support-premium-row">
|
||||
{#if !hasPremiumSupport}
|
||||
<Button
|
||||
href={upgradeURL}
|
||||
@@ -113,14 +113,13 @@
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<div class="u-flex u-gap-6 u-cross-center">
|
||||
<div class="support-hours">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="{isSupportOnline()
|
||||
? 'icon-check-circle u-color-text-success'
|
||||
: 'icon-x-circle'} u-padding-block-end-1"></span>
|
||||
|
||||
{supportTimings}
|
||||
<span class="support-hours-text">{supportTimings}</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -194,4 +193,35 @@
|
||||
/* override required due to the card's background color */
|
||||
background: var(--bgcolor-neutral-default, #fafafb) !important;
|
||||
}
|
||||
|
||||
.support-premium-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.support-hours {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.support-hours-text {
|
||||
font-size: var(--font-size-xs, 0.75rem);
|
||||
line-height: 1.35;
|
||||
color: var(--fgcolor-neutral-secondary, #56565c);
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.support-premium-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.support-hours {
|
||||
padding-inline-start: 0.125rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
import DeleteVariableModal from './deleteVariableModal.svelte';
|
||||
import UpdateVariableModal from './updateVariableModal.svelte';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
|
||||
const DOCS_LINKS: Record<ProductLabel, string> = {
|
||||
site: 'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables',
|
||||
@@ -62,18 +63,26 @@
|
||||
const createSource = $derived(analyticsCreateSource || analyticsSource);
|
||||
const docsLink = $derived(DOCS_LINKS[productLabel]);
|
||||
|
||||
const tableColumns = [
|
||||
{ id: 'key', width: { min: 300 } },
|
||||
{ id: 'value', width: { min: 280 } },
|
||||
{ id: 'actions', width: 40 }
|
||||
];
|
||||
const tableColumns = $derived(
|
||||
$isSmallViewport
|
||||
? [
|
||||
{ id: 'key', width: { min: 420 } },
|
||||
{ id: 'value', width: { min: 240 } },
|
||||
{ id: 'actions', width: 40 }
|
||||
]
|
||||
: [
|
||||
{ id: 'key', width: { min: 300 } },
|
||||
{ id: 'value', width: { min: 280 } },
|
||||
{ id: 'actions', width: 40 }
|
||||
]
|
||||
);
|
||||
</script>
|
||||
|
||||
<Accordion title="Environment variables" badge="Optional" hideDivider>
|
||||
<Layout.Stack gap="xl">
|
||||
Set up environment variables to securely manage keys and settings for your project.
|
||||
<Layout.Stack gap="l">
|
||||
<Layout.Stack direction="row">
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Button
|
||||
secondary
|
||||
@@ -137,7 +146,7 @@
|
||||
{:else if variables?.length}
|
||||
<Paginator items={variables} limit={6} hideFooter={variables.length <= 6}>
|
||||
{#snippet children(paginatedItems)}
|
||||
<Table.Root let:root columns={tableColumns}>
|
||||
<Table.Root class="responsive-table" let:root columns={tableColumns}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Selector, Tooltip, Typography, Upload } from '@appwrite.io/pink-svelte';
|
||||
import { parse } from '$lib/helpers/envfile';
|
||||
import { removeFile } from '$lib/helpers/files';
|
||||
|
||||
export let show = false;
|
||||
export let variables: Partial<Models.Variable>[];
|
||||
@@ -13,6 +14,15 @@
|
||||
let files: FileList;
|
||||
let error: string;
|
||||
let secret = false;
|
||||
$: filesList = files?.length
|
||||
? Array.from(files).map((file) => ({
|
||||
...file,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
extension: file.type,
|
||||
removable: true
|
||||
}))
|
||||
: [];
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
@@ -52,28 +62,32 @@
|
||||
<Layout.Stack>
|
||||
<Upload.Dropzone bind:files>
|
||||
<Layout.Stack alignItems="center" gap="s">
|
||||
<Layout.Stack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
direction="row"
|
||||
gap="s">
|
||||
<Typography.Text variant="l-500">
|
||||
<Layout.Stack alignItems="center" justifyContent="center" inline>
|
||||
<Typography.Text variant="l-500" align="center" inline>
|
||||
Drag and drop files here or click to upload
|
||||
</Typography.Text>
|
||||
<Tooltip>
|
||||
<Layout.Stack alignItems="center" justifyContent="center" inline>
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Layout.Stack
|
||||
style="display: inline-flex; vertical-align: middle;"
|
||||
inline
|
||||
alignItems="center"
|
||||
justifyContent="center">
|
||||
<Tooltip>
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<svelte:fragment slot="tooltip"
|
||||
>Only .env files allowed</svelte:fragment>
|
||||
</Tooltip>
|
||||
</Layout.Stack>
|
||||
<svelte:fragment slot="tooltip">
|
||||
Only .env files allowed
|
||||
</svelte:fragment>
|
||||
</Tooltip>
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Typography.Caption variant="400">
|
||||
<Typography.Caption variant="400" align="center">
|
||||
Up to 100 variables allowed
|
||||
</Typography.Caption>
|
||||
</Layout.Stack>
|
||||
</Upload.Dropzone>
|
||||
{#if files?.length}
|
||||
<Upload.List
|
||||
bind:files={filesList}
|
||||
on:remove={(e) => (files = removeFile(e.detail, files))} />
|
||||
{/if}
|
||||
{#if variables?.length > 0}
|
||||
<Alert.Inline status="info" dismissible>
|
||||
This action can create and update variables but can not delete them.
|
||||
|
||||
+18
-9
@@ -158,12 +158,15 @@ export const defaultRoles: string[] = ['owner'];
|
||||
|
||||
// these are kept for backwards compatibility with keys and events that already exists.
|
||||
// for the new ones, we use the new terminology.
|
||||
export const scopes: {
|
||||
export type ScopeDefinition = {
|
||||
scope: string;
|
||||
description: string;
|
||||
category: string;
|
||||
icon: string;
|
||||
}[] = [
|
||||
deprecated?: boolean;
|
||||
};
|
||||
|
||||
export const scopes: ScopeDefinition[] = [
|
||||
{
|
||||
scope: 'sessions.write',
|
||||
description: "Access to create, update and delete your project's sessions",
|
||||
@@ -210,13 +213,15 @@ export const scopes: {
|
||||
scope: 'collections.read',
|
||||
description: "Access to read your project's database collections",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
icon: 'database',
|
||||
deprecated: true
|
||||
},
|
||||
{
|
||||
scope: 'collections.write',
|
||||
description: "Access to create, update, and delete your project's database collections",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
icon: 'database',
|
||||
deprecated: true
|
||||
},
|
||||
{
|
||||
scope: 'tables.read',
|
||||
@@ -234,14 +239,16 @@ export const scopes: {
|
||||
scope: 'attributes.read',
|
||||
description: "Access to read your project's database collection's attributes",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
icon: 'database',
|
||||
deprecated: true
|
||||
},
|
||||
{
|
||||
scope: 'attributes.write',
|
||||
description:
|
||||
"Access to create, update, and delete your project's database collection's attributes",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
icon: 'database',
|
||||
deprecated: true
|
||||
},
|
||||
{
|
||||
scope: 'columns.read',
|
||||
@@ -271,13 +278,15 @@ export const scopes: {
|
||||
scope: 'documents.read',
|
||||
description: "Access to read your project's database documents",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
icon: 'database',
|
||||
deprecated: true
|
||||
},
|
||||
{
|
||||
scope: 'documents.write',
|
||||
description: "Access to create, update, and delete your project's database documents",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
icon: 'database',
|
||||
deprecated: true
|
||||
},
|
||||
{
|
||||
scope: 'rows.read',
|
||||
@@ -469,7 +478,7 @@ export const scopes: {
|
||||
}
|
||||
];
|
||||
|
||||
export const cloudOnlyBackupScopes = [
|
||||
export const cloudOnlyBackupScopes: ScopeDefinition[] = [
|
||||
{
|
||||
scope: 'policies.read',
|
||||
description: 'Access to read your database backup policies',
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
export let autocomplete = false;
|
||||
export let maxlength: number = null;
|
||||
export let leadingIcon: ComponentType | undefined = undefined;
|
||||
export let helper: string = undefined;
|
||||
|
||||
let error: string;
|
||||
|
||||
@@ -52,7 +53,7 @@
|
||||
type="url"
|
||||
autofocus={autofocus || undefined}
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
helper={error}
|
||||
helper={error || helper}
|
||||
state={error ? 'error' : 'default'}
|
||||
on:invalid={handleInvalid}
|
||||
on:input
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { AppwriteException } from '@appwrite.io/console';
|
||||
|
||||
/** True when access is blocked until the console account email is verified. */
|
||||
export function isVerifyEmailRedirectError(error: unknown): boolean {
|
||||
if (error instanceof AppwriteException) {
|
||||
return (
|
||||
error.type === 'user_email_not_verified' ||
|
||||
error.type === 'console_account_verification_required' ||
|
||||
(error.message?.includes('Console account verification is required') ?? false)
|
||||
);
|
||||
}
|
||||
|
||||
if (error && typeof error === 'object' && 'message' in error) {
|
||||
const msg = (error as { message: unknown }).message;
|
||||
if (typeof msg !== 'string') return false;
|
||||
const typ =
|
||||
'type' in error && typeof (error as { type: unknown }).type === 'string'
|
||||
? (error as { type: string }).type
|
||||
: undefined;
|
||||
return (
|
||||
typ === 'user_email_not_verified' ||
|
||||
typ === 'console_account_verification_required' ||
|
||||
msg.includes('Console account verification is required')
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -3,6 +3,29 @@ import { env } from '$env/dynamic/public';
|
||||
const SECRET = env.PUBLIC_CONSOLE_FINGERPRINT_KEY ?? '';
|
||||
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
/** Cached server timestamp and the local time it was fetched at, for interpolation. */
|
||||
let serverTimeCache: { serverSecs: number; fetchedAtMs: number } | null = null;
|
||||
|
||||
/**
|
||||
* Cache the server's clock so fingerprint timestamps always align with the
|
||||
* backend's clock, regardless of local clock drift.
|
||||
*
|
||||
* @param serverTimeSecs - the server's unix timestamp in seconds
|
||||
* (e.g. parsed from a response Date header)
|
||||
*/
|
||||
export function syncServerTime(serverTimeSecs: number): void {
|
||||
if (serverTimeCache) return;
|
||||
serverTimeCache = { serverSecs: serverTimeSecs, fetchedAtMs: Date.now() };
|
||||
}
|
||||
|
||||
function getServerTimestamp(): number {
|
||||
if (!serverTimeCache) {
|
||||
return Math.floor(Date.now() / 1000);
|
||||
}
|
||||
const elapsedSecs = Math.floor((Date.now() - serverTimeCache.fetchedAtMs) / 1000);
|
||||
return serverTimeCache.serverSecs + elapsedSecs;
|
||||
}
|
||||
|
||||
async function sha256(message: string): Promise<string> {
|
||||
if (!crypto?.subtle) {
|
||||
console.warn('crypto.subtle unavailable, fingerprinting disabled');
|
||||
@@ -204,7 +227,7 @@ export async function generateFingerprintToken(): Promise<string> {
|
||||
|
||||
const signals: BrowserSignals = {
|
||||
...staticSignals,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
timestamp: getServerTimestamp()
|
||||
};
|
||||
|
||||
const payload = JSON.stringify(signals);
|
||||
|
||||
@@ -2,6 +2,8 @@ import { page } from '$app/state';
|
||||
import { get } from 'svelte/store';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { projectRegion } from '$routes/(console)/project-[region]-[project]/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
/**
|
||||
* Returns the current project ID.
|
||||
@@ -42,3 +44,52 @@ export function getProjectEndpoint(): string {
|
||||
|
||||
return currentProjectRegion ? `${protocol}//${currentProjectRegion.$id}.${hostname}/v1` : href;
|
||||
}
|
||||
|
||||
export function isProjectBlocked(project: Models.Project | null | undefined): boolean {
|
||||
const hasGlobalProjectBlock = (project?.blocks ?? []).some((block) => {
|
||||
const type = block.resourceType?.trim().toLowerCase();
|
||||
const id = block.resourceId?.trim();
|
||||
|
||||
// Global project block:
|
||||
// - legacy: both type and id empty
|
||||
// - new: resourceType === 'projects' with no specific resourceId
|
||||
const isLegacyGlobal = !type && !id;
|
||||
const isProjectsGlobal = type === 'projects' && !id;
|
||||
|
||||
return isLegacyGlobal || isProjectsGlobal;
|
||||
});
|
||||
|
||||
return project?.status !== 'paused' && hasGlobalProjectBlock;
|
||||
}
|
||||
|
||||
export function isResourceBlocked(
|
||||
project: Models.Project | null | undefined,
|
||||
resourceType: string,
|
||||
resourceId: string
|
||||
): boolean {
|
||||
const normalizedType = resourceType.trim().toLowerCase();
|
||||
const normalizedId = resourceId.trim();
|
||||
|
||||
return (project?.blocks ?? []).some((block) => {
|
||||
const type = block.resourceType?.trim().toLowerCase();
|
||||
const id = block.resourceId?.trim();
|
||||
|
||||
return type === normalizedType && id === normalizedId;
|
||||
});
|
||||
}
|
||||
|
||||
export function guardResourceBlock(
|
||||
project: Models.Project | null | undefined,
|
||||
resourceType: string | string[],
|
||||
resourceId: string
|
||||
) {
|
||||
const resourceTypes = Array.isArray(resourceType) ? resourceType : [resourceType];
|
||||
const isBlocked = resourceTypes.some((type) => isResourceBlocked(project, type, resourceId));
|
||||
|
||||
if (isBlocked) {
|
||||
error(403, {
|
||||
type: 'general_resource_blocked',
|
||||
message: 'This resource page cannot be accessed.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const BODY_TOOLTIP_MAX_WIDTH = 'min(22rem, calc(100vw - 2rem))';
|
||||
|
||||
export const BODY_TOOLTIP_WRAPPER_STYLE =
|
||||
'min-width: 0; max-width: 100%; overflow-wrap: break-word; white-space: normal';
|
||||
|
||||
export const BODY_TOOLTIP_WRAPPER_STYLE_PRELINE =
|
||||
'min-width: 0; max-width: 100%; overflow-wrap: break-word; white-space: pre-line';
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { BillingPlanGroup } from '@appwrite.io/console';
|
||||
@@ -23,7 +27,7 @@
|
||||
export let buttonType: 'primary' | 'secondary' | 'text' = 'primary';
|
||||
</script>
|
||||
|
||||
<Tooltip disabled={!disabled}>
|
||||
<Tooltip disabled={!disabled} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<Button
|
||||
size="s"
|
||||
text={buttonType === 'text'}
|
||||
@@ -38,5 +42,5 @@
|
||||
{/if}
|
||||
<span class="text">{buttonText}</span>
|
||||
</Button>
|
||||
<div slot="tooltip">{tooltipContent}</div>
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE}>{tooltipContent}</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { getContext } from 'svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let subNavigation;
|
||||
|
||||
$: subNavigation = page.data.subNavigation;
|
||||
// We need to have this second variable, because we only want narrow
|
||||
// to change automatically if we change from having a second side nav to
|
||||
// not having one, not when the second side nav changes to a different value.
|
||||
|
||||
+70
-38
@@ -14,16 +14,20 @@
|
||||
import SideNavigation from '$lib/layout/navigation.svelte';
|
||||
import { hasOnboardingDismissed } from '$lib/helpers/onboarding';
|
||||
import { isSidebarOpen, noWidthTransition } from '$lib/stores/sidebar';
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import { page as pageStore } from '$app/stores';
|
||||
import { BillingPlanGroup, type Models } from '@appwrite.io/console';
|
||||
import { getSidebarState, isInDatabasesRoute, updateSidebarState } from '$lib/helpers/sidebar';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { isProjectBlocked as getIsProjectBlocked } from '$lib/helpers/project';
|
||||
|
||||
export let showHeader = true;
|
||||
export let showFooter = true;
|
||||
export let showSideNavigation = false;
|
||||
export let selectedProject: Models.Project = null;
|
||||
|
||||
$: activeProject = selectedProject && page.params.project ? selectedProject : null;
|
||||
|
||||
// variables
|
||||
let yOnMenuOpen: number;
|
||||
let showAccountMenu = false;
|
||||
@@ -51,23 +55,21 @@
|
||||
}
|
||||
|
||||
function getProgressCard() {
|
||||
if (selectedProject && !hasOnboardingDismissed(selectedProject.$id, $user)) {
|
||||
const { platforms, pingCount } = selectedProject;
|
||||
let percentage = 33;
|
||||
if (!activeProject || hasOnboardingDismissed(activeProject.$id, $user)) return undefined;
|
||||
|
||||
if (platforms.length > 0 && pingCount === 0) {
|
||||
percentage = 66;
|
||||
} else if (pingCount > 0) {
|
||||
percentage = 100;
|
||||
}
|
||||
const { platforms, pingCount } = activeProject;
|
||||
let percentage = 33;
|
||||
|
||||
return {
|
||||
title: 'Get started',
|
||||
percentage
|
||||
};
|
||||
if (platforms.length > 0 && pingCount === 0) {
|
||||
percentage = 66;
|
||||
} else if (pingCount > 0) {
|
||||
percentage = 100;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return {
|
||||
title: 'Get started',
|
||||
percentage
|
||||
};
|
||||
}
|
||||
|
||||
function handleResize() {
|
||||
@@ -75,6 +77,20 @@
|
||||
showAccountMenu = false;
|
||||
}
|
||||
|
||||
function closeOpenDialogs() {
|
||||
if (typeof document === 'undefined') return;
|
||||
|
||||
const openDialogs = Array.from(
|
||||
document.querySelectorAll('dialog[open]')
|
||||
) as HTMLDialogElement[];
|
||||
|
||||
for (const dialog of openDialogs) {
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
document.documentElement.classList.remove('u-overflow-hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel navigation when wizard is open and triggered by popstate
|
||||
*/
|
||||
@@ -124,8 +140,9 @@
|
||||
// subscriptions
|
||||
isNewWizardStatusOpen.subscribe((value) => (showHeader = !value));
|
||||
|
||||
page.subscribe(({ url }) => {
|
||||
$showSubNavigation = url.searchParams.get('openNavbar') === 'true';
|
||||
$: {
|
||||
const url = page.url;
|
||||
showSubNavigation.set(url.searchParams.get('openNavbar') === 'true');
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (url.pathname.includes('project-')) {
|
||||
@@ -135,7 +152,7 @@
|
||||
} else {
|
||||
showContentTransition = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// reactive blocks
|
||||
$: sideSize = $hasSubNavigation ? ($isNarrow ? '17rem' : '25rem') : '12.5rem';
|
||||
@@ -170,19 +187,20 @@
|
||||
};
|
||||
}),
|
||||
|
||||
currentProject: selectedProject
|
||||
currentProject: activeProject
|
||||
};
|
||||
|
||||
$: state = $isSidebarOpen ? 'open' : 'closed';
|
||||
|
||||
$: subNavigation = $page.data.subNavigation;
|
||||
$: subNavigation = $pageStore.data.subNavigation;
|
||||
|
||||
$: shouldRenderSidebar =
|
||||
!$isNewWizardStatusOpen && showSideNavigation && !$showOnboardingAnimation;
|
||||
$: hasSidebarSpace = shouldRenderSidebar && !$isTabletViewport && !!selectedProject;
|
||||
|
||||
$: hasSidebarSpace = shouldRenderSidebar && !$isTabletViewport && !!activeProject;
|
||||
$: isProjectBlocked = getIsProjectBlocked(activeProject);
|
||||
$: {
|
||||
if ($isSidebarOpen) {
|
||||
closeOpenDialogs();
|
||||
yOnMenuOpen = window.scrollY;
|
||||
bodyStyle.set({ position: 'fixed', top: `-${window.scrollY}px` });
|
||||
} else if (!$isSidebarOpen) {
|
||||
@@ -203,6 +221,7 @@
|
||||
<main
|
||||
class:has-alert={$activeHeaderAlert?.show}
|
||||
class:is-open={$showSubNavigation}
|
||||
class:is-sidebar-open={$isSidebarOpen}
|
||||
class:u-hide={$wizard.show || $wizard.cover}
|
||||
class:is-fixed-layout={$activeHeaderAlert?.show}
|
||||
class:no-header={!showHeader || $showOnboardingAnimation}
|
||||
@@ -211,30 +230,32 @@
|
||||
<Navbar {...navbarProps} bind:sideBarIsOpen={$isSidebarOpen} bind:showAccountMenu />
|
||||
{/if}
|
||||
|
||||
{#if shouldRenderSidebar}
|
||||
<Sidebar
|
||||
project={selectedProject}
|
||||
progressCard={getProgressCard()}
|
||||
avatar={navbarProps.avatar}
|
||||
bind:subNavigation
|
||||
bind:sideBarIsOpen={$isSidebarOpen}
|
||||
bind:showAccountMenu
|
||||
bind:state />
|
||||
{/if}
|
||||
<div class="shell-sidebar-area" inert={isProjectBlocked || undefined}>
|
||||
{#if shouldRenderSidebar}
|
||||
<Sidebar
|
||||
project={activeProject}
|
||||
progressCard={getProgressCard()}
|
||||
avatar={navbarProps.avatar}
|
||||
{subNavigation}
|
||||
bind:sideBarIsOpen={$isSidebarOpen}
|
||||
bind:showAccountMenu
|
||||
bind:state />
|
||||
{/if}
|
||||
|
||||
{#if !$showOnboardingAnimation}
|
||||
<SideNavigation bind:subNavigation />
|
||||
{/if}
|
||||
{#if !$showOnboardingAnimation}
|
||||
<SideNavigation {subNavigation} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="content"
|
||||
class:has-transition={showContentTransition}
|
||||
class:icons-content={state === 'icons' && selectedProject}
|
||||
class:icons-content={state === 'icons' && activeProject}
|
||||
class:no-sidebar={!hasSidebarSpace}>
|
||||
<section class="main-content" data-test={showSideNavigation}>
|
||||
{#if $page.data?.header}
|
||||
{#if page.data?.header}
|
||||
<div class="layout-header">
|
||||
<svelte:component this={$page.data.header} />
|
||||
<svelte:component this={page.data.header} />
|
||||
</div>
|
||||
{/if}
|
||||
<slot />
|
||||
@@ -255,6 +276,17 @@
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
.shell-sidebar-area {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
main.is-sidebar-open .content {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
|
||||
@@ -327,7 +359,7 @@
|
||||
height: 100vh;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
z-index: 19;
|
||||
background-color: #56565c1a;
|
||||
backdrop-filter: blur(5px);
|
||||
transition:
|
||||
|
||||
@@ -78,10 +78,16 @@ export const showBudgetAlert = derived(
|
||||
);
|
||||
|
||||
function getPlansInfoStore(): BillingPlansMap | null {
|
||||
return get(plansInfo) ?? get(page).data?.plansInfo ?? null;
|
||||
return get(plansInfo) ?? get(page).data?.plansInfo ?? new Map();
|
||||
}
|
||||
|
||||
function makeBillingPlan(billingPlanOrId: string | Models.BillingPlan): Models.BillingPlan {
|
||||
function makeBillingPlan(
|
||||
billingPlanOrId: string | Models.BillingPlan | null | undefined
|
||||
): Models.BillingPlan | null {
|
||||
if (!billingPlanOrId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return typeof billingPlanOrId === 'string' ? billingIdToPlan(billingPlanOrId) : billingPlanOrId;
|
||||
}
|
||||
|
||||
@@ -89,21 +95,35 @@ export function getRoleLabel(role: string) {
|
||||
return roles.find((r) => r.value === role)?.label ?? role;
|
||||
}
|
||||
|
||||
export function isStarterPlan(billingPlanOrId: string | Models.BillingPlan): boolean {
|
||||
export function isStarterPlan(
|
||||
billingPlanOrId: string | Models.BillingPlan | null | undefined
|
||||
): boolean {
|
||||
const billingPlan = makeBillingPlan(billingPlanOrId);
|
||||
return planHasGroup(billingPlan, BillingPlanGroup.Starter);
|
||||
}
|
||||
|
||||
export function canUpgrade(billingPlanOrId: string | Models.BillingPlan): boolean {
|
||||
export function canUpgrade(
|
||||
billingPlanOrId: string | Models.BillingPlan | null | undefined
|
||||
): boolean {
|
||||
const billingPlan = makeBillingPlan(billingPlanOrId);
|
||||
if (!billingPlan?.$id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextTier = getNextTierBillingPlan(billingPlan.$id);
|
||||
|
||||
// defaults back to PRO, so adjust the check!
|
||||
return billingPlan.$id !== nextTier.$id;
|
||||
}
|
||||
|
||||
export function canDowngrade(billingPlanOrId: string | Models.BillingPlan): boolean {
|
||||
export function canDowngrade(
|
||||
billingPlanOrId: string | Models.BillingPlan | null | undefined
|
||||
): boolean {
|
||||
const billingPlan = makeBillingPlan(billingPlanOrId);
|
||||
if (!billingPlan?.$id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextTier = getPreviousTierBillingPlan(billingPlan.$id);
|
||||
|
||||
// defaults back to Starter, so adjust the check!
|
||||
@@ -111,7 +131,7 @@ export function canDowngrade(billingPlanOrId: string | Models.BillingPlan): bool
|
||||
}
|
||||
|
||||
export function planHasGroup(
|
||||
billingPlanOrId: string | Models.BillingPlan,
|
||||
billingPlanOrId: string | Models.BillingPlan | null | undefined,
|
||||
group: BillingPlanGroup
|
||||
): boolean {
|
||||
const billingPlan = makeBillingPlan(billingPlanOrId);
|
||||
@@ -191,6 +211,7 @@ export type PlanServices =
|
||||
| 'platforms'
|
||||
| 'realtime'
|
||||
| 'realtimeAddon'
|
||||
| 'realtimeMessages'
|
||||
| 'storage'
|
||||
| 'storageAddon'
|
||||
| 'teams'
|
||||
@@ -274,6 +295,7 @@ export function checkForUsageFees(plan: string, id: PlanServices) {
|
||||
case 'users':
|
||||
case 'executions':
|
||||
case 'realtime':
|
||||
case 'realtimeMessages':
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -565,6 +587,9 @@ export function checkForMarkedForDeletion(org: Models.Organization) {
|
||||
|
||||
export async function checkForMissingPaymentMethod() {
|
||||
const starterPlan = getBasePlanFromGroup(BillingPlanGroup.Starter);
|
||||
if (!starterPlan?.$id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orgs = await sdk.forConsole.organizations.list({
|
||||
queries: [
|
||||
|
||||
@@ -55,7 +55,10 @@ export function getApiEndpoint(region?: string): string {
|
||||
const hostname = url.host; // "hostname:port" (or just "hostname" if no port)
|
||||
|
||||
// If instance supports multi-region, add the region subdomain.
|
||||
const subdomain = isMultiRegionSupported(url) ? getSubdomain(region) : '';
|
||||
let subdomain = isMultiRegionSupported(url) ? getSubdomain(region) : '';
|
||||
if (subdomain && hostname.startsWith(subdomain)) {
|
||||
subdomain = '';
|
||||
}
|
||||
|
||||
return `${protocol}//${subdomain}${hostname}/v1`;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ export const VARS = {
|
||||
APPWRITE_ENDPOINT: env.PUBLIC_APPWRITE_ENDPOINT ?? undefined,
|
||||
GROWTH_ENDPOINT: env.PUBLIC_GROWTH_ENDPOINT ?? undefined,
|
||||
PUBLIC_STRIPE_KEY: env.PUBLIC_STRIPE_KEY ?? undefined,
|
||||
EMAIL_VERIFICATION: env.PUBLIC_CONSOLE_EMAIL_VERIFICATION === 'true',
|
||||
MOCK_AI_SUGGESTIONS: (env.PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS ?? 'true') === 'true'
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
<script>
|
||||
import { base } from '$app/paths';
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base, resolve } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
$effect(() => {
|
||||
const verifyEmailPath = resolve('/verify-email');
|
||||
if (isVerifyEmailRedirectError(page.error) && page.url.pathname !== verifyEmailPath) {
|
||||
goto(verifyEmailPath, { replaceState: true });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
import { feedback } from '$lib/stores/feedback';
|
||||
import { hasStripePublicKey, isCloud, VARS } from '$lib/system';
|
||||
import { stripe } from '$lib/stores/stripe';
|
||||
import MobileFeedbackModal from './wizard/feedback/mobileFeedbackModal.svelte';
|
||||
import MobileSupportModal from './wizard/support/mobileSupportModal.svelte';
|
||||
import { showSupportModal } from './wizard/support/store';
|
||||
import { activeHeaderAlert, consoleVariables } from './store';
|
||||
@@ -43,6 +44,7 @@
|
||||
import { UsageRates } from '$lib/components/billing';
|
||||
import { canSeeProjects } from '$lib/stores/roles';
|
||||
import { BottomModalAlert } from '$lib/components';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import {
|
||||
IconAnnotation,
|
||||
IconBookOpen,
|
||||
@@ -341,6 +343,10 @@
|
||||
|
||||
<Create bind:show={$newOrgModal} />
|
||||
|
||||
{#if $feedback.show && $isSmallViewport}
|
||||
<MobileFeedbackModal />
|
||||
{/if}
|
||||
|
||||
{#if $showSupportModal}
|
||||
<MobileSupportModal bind:show={$showSupportModal}></MobileSupportModal>
|
||||
{/if}
|
||||
|
||||
@@ -1,20 +1,71 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { isCloud } from '$lib/system';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import type { Account } from '$lib/stores/user';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Platform, Query } from '@appwrite.io/console';
|
||||
import { Platform, Query, type Models } from '@appwrite.io/console';
|
||||
import { makePlansMap } from '$lib/helpers/billing';
|
||||
import { plansInfo as plansInfoStore } from '$lib/stores/billing';
|
||||
import { normalizeConsoleVariables } from '$lib/helpers/domains';
|
||||
import { syncServerTime } from '$lib/helpers/fingerprint';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { resolve } from '$app/paths';
|
||||
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
|
||||
|
||||
export const load: LayoutLoad = async ({ depends, parent }) => {
|
||||
const { organizations, plansInfo } = await parent();
|
||||
export const load: LayoutLoad = async ({ depends, parent, url }) => {
|
||||
const parentData = await parent();
|
||||
const { organizations, plansInfo } = parentData;
|
||||
const account = parentData.account as Account | undefined;
|
||||
|
||||
const { endpoint, project } = sdk.forConsole.client.config;
|
||||
const verifyEmailUrl = resolve('/verify-email');
|
||||
|
||||
// While unverified, several console APIs (not only teams) may return 401; avoid failing the layout.
|
||||
if (url.pathname === verifyEmailUrl && account && !account.emailVerification) {
|
||||
depends(Dependencies.RUNTIMES);
|
||||
depends(Dependencies.CONSOLE_VARIABLES);
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
|
||||
const [preferences, rawConsoleVariables, versionData] = await Promise.all([
|
||||
sdk.forConsole.account.getPrefs().catch(() => ({}) as Models.DefaultPreferences),
|
||||
sdk.forConsole.console.variables().catch(() => ({}) as Models.ConsoleVariables),
|
||||
fetch(`${endpoint}/health/version`, {
|
||||
headers: { 'X-Appwrite-Project': project as string }
|
||||
})
|
||||
.then(async (response) => {
|
||||
const dateHeader = response.headers.get('Date');
|
||||
const parsed = dateHeader ? new Date(dateHeader).getTime() : NaN;
|
||||
if (Number.isFinite(parsed)) {
|
||||
syncServerTime(Math.floor(parsed / 1000));
|
||||
}
|
||||
return response.json() as { version?: string };
|
||||
})
|
||||
.catch(() => null)
|
||||
]);
|
||||
|
||||
const consoleVariables = normalizeConsoleVariables(rawConsoleVariables);
|
||||
|
||||
plansInfoStore.set(plansInfo ?? null);
|
||||
|
||||
return {
|
||||
roles: [],
|
||||
scopes: [],
|
||||
preferences,
|
||||
currentOrgId: undefined,
|
||||
organizations,
|
||||
consoleVariables,
|
||||
allProjectsCount: 0,
|
||||
plansInfo: plansInfo ?? null,
|
||||
version: versionData?.version ?? null
|
||||
};
|
||||
}
|
||||
|
||||
depends(Dependencies.RUNTIMES);
|
||||
depends(Dependencies.CONSOLE_VARIABLES);
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
|
||||
const { endpoint, project } = sdk.forConsole.client.config;
|
||||
const shouldRedirectToVerifyEmail = (error: unknown) =>
|
||||
isVerifyEmailRedirectError(error) && url.pathname !== verifyEmailUrl;
|
||||
|
||||
const plansArrayPromise =
|
||||
plansInfo || !isCloud
|
||||
@@ -28,9 +79,22 @@ export const load: LayoutLoad = async ({ depends, parent }) => {
|
||||
plansArrayPromise,
|
||||
fetch(`${endpoint}/health/version`, {
|
||||
headers: { 'X-Appwrite-Project': project as string }
|
||||
}).then((response) => response.json() as { version?: string }),
|
||||
}).then((response) => {
|
||||
const dateHeader = response.headers.get('Date');
|
||||
const parsed = dateHeader ? new Date(dateHeader).getTime() : NaN;
|
||||
if (Number.isFinite(parsed)) {
|
||||
syncServerTime(Math.floor(parsed / 1000));
|
||||
}
|
||||
return response.json() as { version?: string };
|
||||
}),
|
||||
sdk.forConsole.console.variables()
|
||||
]);
|
||||
]).catch((error) => {
|
||||
if (shouldRedirectToVerifyEmail(error)) {
|
||||
redirect(303, verifyEmailUrl);
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
const consoleVariables = normalizeConsoleVariables(rawConsoleVariables);
|
||||
|
||||
@@ -57,6 +121,10 @@ export const load: LayoutLoad = async ({ depends, parent }) => {
|
||||
})
|
||||
).total;
|
||||
} catch (e) {
|
||||
if (shouldRedirectToVerifyEmail(e)) {
|
||||
redirect(303, verifyEmailUrl);
|
||||
}
|
||||
|
||||
projectsCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { daysLeftInTrial, billingIdToPlan } from '$lib/stores/billing';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Icon, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
@@ -132,29 +136,30 @@
|
||||
{#await planName}
|
||||
<Skeleton width={30} height={20} variant="line" />
|
||||
{:then name}
|
||||
<Tooltip>
|
||||
<Tooltip maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<Badge size="xs" variant="secondary" content={name} />
|
||||
|
||||
<span slot="tooltip">
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE}>
|
||||
You are limited to 1 free organization per account
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/await}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if isOrganizationOnTrial(organization)}
|
||||
<Tooltip>
|
||||
<Tooltip maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div class="u-flex u-cross-center">
|
||||
<Badge
|
||||
class="eyebrow-heading-3"
|
||||
variant="secondary"
|
||||
content="TRIAL" />
|
||||
</div>
|
||||
<span slot="tooltip"
|
||||
>{`Your trial ends on ${toLocaleDate(
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE}>
|
||||
{`Your trial ends on ${toLocaleDate(
|
||||
organization.billingStartDate
|
||||
)}. ${$daysLeftInTrial} days remaining.`}</span>
|
||||
)}. ${$daysLeftInTrial} days remaining.`}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -1,59 +1,6 @@
|
||||
import { isCloud } from '$lib/system';
|
||||
import { isSameDay } from '$lib/helpers/date';
|
||||
import Imagine from '$lib/components/promos/imagine.svelte';
|
||||
import {
|
||||
type BottomModalAlertItem,
|
||||
setMobileSingleAlertLayout,
|
||||
showBottomModalAlert
|
||||
} from '$lib/stores/bottom-alerts';
|
||||
|
||||
const SHOW_IMAGINE_PROMO = true;
|
||||
|
||||
const listOfPromotions: BottomModalAlertItem[] = [];
|
||||
|
||||
if (isCloud && SHOW_IMAGINE_PROMO) {
|
||||
const imaginePromo: BottomModalAlertItem = {
|
||||
id: 'modal:imagine.dev',
|
||||
backgroundComponent: Imagine,
|
||||
title: 'Introducing Imagine',
|
||||
message: 'The most complete AI builder to date',
|
||||
importance: 8,
|
||||
scope: 'everywhere',
|
||||
plan: 'free',
|
||||
cta: {
|
||||
text: 'Try it now',
|
||||
color: {
|
||||
light: '#FFFFFF',
|
||||
dark: '#000000'
|
||||
},
|
||||
background: {
|
||||
light: '#000000',
|
||||
dark: '#FFFFFF'
|
||||
},
|
||||
backgroundHover: {
|
||||
light: '#333333',
|
||||
dark: '#CCCCCC'
|
||||
},
|
||||
link: () => 'https://imagine.dev',
|
||||
external: true,
|
||||
hideOnClick: true
|
||||
},
|
||||
show: true
|
||||
};
|
||||
|
||||
listOfPromotions.push(imaginePromo);
|
||||
}
|
||||
|
||||
export function addBottomModalAlerts() {
|
||||
listOfPromotions.forEach((promotion) => showBottomModalAlert(promotion));
|
||||
|
||||
// only for imagine!
|
||||
if (listOfPromotions.length > 0) {
|
||||
const imaginePromo = listOfPromotions[0];
|
||||
const { cta, title, message } = imaginePromo;
|
||||
setMobileSingleAlertLayout({ enabled: true, cta, title, message });
|
||||
}
|
||||
}
|
||||
export function addBottomModalAlerts() {}
|
||||
|
||||
// use this for time based promo handling
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
@@ -8,6 +8,7 @@ import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import Header from './header.svelte';
|
||||
import { headerAlert } from '$lib/stores/headerAlert';
|
||||
import ProjectsAtRisk from '$lib/components/billing/alerts/projectsAtRisk.svelte';
|
||||
import RealtimePricing from '$lib/components/billing/alerts/realtimePricing.svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { defaultRoles, defaultScopes } from '$lib/constants';
|
||||
@@ -64,6 +65,15 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
loadAvailableRegions(params.organization)
|
||||
]);
|
||||
|
||||
if (isCloud && new Date() < new Date('2026-04-22')) {
|
||||
headerAlert.add({
|
||||
show: true,
|
||||
component: RealtimePricing,
|
||||
id: 'realtimePricing',
|
||||
importance: 1
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
header: Header,
|
||||
breadcrumbs: Breadcrumbs,
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import type { PageProps } from './$types';
|
||||
import { getPlatformInfo } from '$lib/helpers/platform';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
import CreateProjectCloud from './createProjectCloud.svelte';
|
||||
import { regions as regionsStore } from '$lib/stores/organization';
|
||||
|
||||
@@ -147,16 +151,16 @@
|
||||
|
||||
{#if $canWriteProjects}
|
||||
{#if projectCreationDisabled && reachedProjectLimit}
|
||||
<Tooltip placement="bottom">
|
||||
<Tooltip placement="bottom" maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button event="create_project" disabled>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create project
|
||||
</Button>
|
||||
</div>
|
||||
<span slot="tooltip">
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE}>
|
||||
You have reached your limit of {projectsLimit} projects.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<Button
|
||||
|
||||
@@ -361,6 +361,27 @@
|
||||
getResource(resources, 'GBHours'),
|
||||
currentPlan?.GBHours
|
||||
),
|
||||
createResourceRow(
|
||||
'realtime',
|
||||
'Realtime connections',
|
||||
getResource(resources, 'realtime'),
|
||||
currentPlan?.realtime
|
||||
),
|
||||
createResourceRow(
|
||||
'realtime-messages',
|
||||
'Realtime messages',
|
||||
getResource(resources, 'realtimeMessages'),
|
||||
currentPlan?.realtimeMessages
|
||||
),
|
||||
createRow({
|
||||
id: 'realtime-bandwidth',
|
||||
label: 'Realtime bandwidth',
|
||||
resource: getResource(resources, 'realtimeBandwidth'),
|
||||
usageFormatter: ({ value }) =>
|
||||
humanFileSize(value).value + humanFileSize(value).unit,
|
||||
priceFormatter: ({ amount }) => formatCurrency(amount),
|
||||
includeProgress: false
|
||||
}),
|
||||
createRow({
|
||||
id: 'sms',
|
||||
label: 'Phone OTP',
|
||||
|
||||
+4
-3
@@ -11,6 +11,7 @@
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { BODY_TOOLTIP_WRAPPER_STYLE_PRELINE } from '$lib/helpers/tooltipContent';
|
||||
import { page } from '$app/state';
|
||||
import { recordTypes } from './store';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
@@ -99,7 +100,7 @@
|
||||
</Layout.Stack>
|
||||
|
||||
<InputText id="value" label="Value" {placeholder} bind:value required>
|
||||
<Tooltip slot="info">
|
||||
<Tooltip slot="info" maxWidth="fit-content">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
Enter the target or destination for this DNS record (e.g., IP address, hostname,
|
||||
@@ -111,10 +112,10 @@
|
||||
<InputNumber id="ttl" label="TTL" placeholder="Enter number" bind:value={ttl}>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE} slot="tooltip">
|
||||
TTL defines how long DNS information is cached. Lower values update faster;
|
||||
higher values reduce server load.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</InputNumber>
|
||||
{#if showPriority(type)}
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
import { AvatarGroup, Tab, Tabs } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
import { isTabSelected } from '$lib/helpers/load';
|
||||
import { Cover } from '$lib/layout';
|
||||
import { daysLeftInTrial, getServiceLimit, readOnly } from '$lib/stores/billing';
|
||||
@@ -110,12 +114,14 @@
|
||||
{/if}
|
||||
|
||||
{#if organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlanDetails.trial && organization?.billingTrialDays}
|
||||
<Tooltip>
|
||||
<Tooltip maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<Badge variant="secondary" content="Trial" />
|
||||
<svelte:fragment slot="tooltip">
|
||||
{`Your trial ends on ${toLocaleDate(
|
||||
organization.billingStartDate
|
||||
)}. ${$daysLeftInTrial} days remaining.`}
|
||||
<div style={BODY_TOOLTIP_WRAPPER_STYLE}>
|
||||
{`Your trial ends on ${toLocaleDate(
|
||||
organization.billingStartDate
|
||||
)}. ${$daysLeftInTrial} days remaining.`}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
@@ -139,7 +145,10 @@
|
||||
{/if}
|
||||
|
||||
{#if $isOwner}
|
||||
<Tooltip disabled={!areMembersLimited} placement="bottom-end">
|
||||
<Tooltip
|
||||
disabled={!areMembersLimited}
|
||||
placement="bottom-end"
|
||||
maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button
|
||||
secondary
|
||||
@@ -150,7 +159,7 @@
|
||||
Invite
|
||||
</Button>
|
||||
</div>
|
||||
<div slot="tooltip">
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE}>
|
||||
{!(
|
||||
organization?.billingPlanDetails?.addons?.seats?.supported ??
|
||||
true
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import Upgrade from '$lib/components/roles/upgrade.svelte';
|
||||
import { getServiceLimit, readOnly, getRoleLabel } from '$lib/stores/billing';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { currentPlan, newMemberModal, organization } from '$lib/stores/organization';
|
||||
import { isOwner } from '$lib/stores/roles';
|
||||
@@ -78,7 +82,10 @@
|
||||
<Container>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Typography.Title>Members</Typography.Title>
|
||||
<Tooltip disabled={!isButtonDisabled} placement="bottom-end">
|
||||
<Tooltip
|
||||
disabled={!isButtonDisabled}
|
||||
placement="bottom-end"
|
||||
maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<ConsoleButton
|
||||
size="s"
|
||||
@@ -89,7 +96,7 @@
|
||||
<span class="text">Invite</span>
|
||||
</ConsoleButton>
|
||||
</div>
|
||||
<div slot="tooltip">
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE}>
|
||||
{!supportsMembers
|
||||
? 'Upgrade to add more members'
|
||||
: `You've reached the members limit for the ${$organization?.billingPlanDetails.name} plan`}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
const plan = data?.plan ?? undefined;
|
||||
|
||||
$: projects = data.organizationUsage.projects;
|
||||
$: orgUsage = data.organizationUsage;
|
||||
|
||||
let usageProjects: Record<string, UsageProjectInfo> = {};
|
||||
|
||||
@@ -423,6 +424,143 @@
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid gap="none">
|
||||
<svelte:fragment slot="title">Realtime connections</svelte:fragment>
|
||||
Peak concurrent realtime connections across all projects in your organization.
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
{#if orgUsage.realtimeConnectionsTotal}
|
||||
{@const current = orgUsage.realtimeConnectionsTotal}
|
||||
{@const max = getServiceLimit('realtime', tier, plan)}
|
||||
<ProgressBarBig
|
||||
currentUnit="Connections"
|
||||
currentValue={formatNum(current)}
|
||||
maxValue={max ? `/ ${formatNum(max)} connections` : undefined}
|
||||
progressValue={current}
|
||||
progressMax={max}
|
||||
showBar={false} />
|
||||
<BarChart
|
||||
options={{
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: formatNum
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'Realtime connections',
|
||||
data: [
|
||||
...(orgUsage.realtimeConnections ?? []).map((e) => [
|
||||
e.date,
|
||||
e.value
|
||||
])
|
||||
]
|
||||
}
|
||||
]} />
|
||||
{#if projects?.length > 0}
|
||||
<ProjectBreakdown {projects} metric="realtime" {usageProjects} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<Layout.Stack gap="xs" alignItems="center" justifyContent="center">
|
||||
<Icon icon={IconChartSquareBar} size="l" />
|
||||
<Typography.Text variant="m-600">No data to show</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid gap="none">
|
||||
<svelte:fragment slot="title">Realtime messages</svelte:fragment>
|
||||
Total realtime messages sent to clients across all projects in your organization.
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
{#if orgUsage.realtimeMessagesTotal}
|
||||
{@const current = orgUsage.realtimeMessagesTotal}
|
||||
{@const max = getServiceLimit('realtimeMessages', tier, plan)}
|
||||
<ProgressBarBig
|
||||
currentUnit="Messages"
|
||||
currentValue={formatNum(current)}
|
||||
maxValue={max ? `/ ${formatNum(max)} messages used` : undefined}
|
||||
progressValue={current}
|
||||
progressMax={max}
|
||||
showBar={false} />
|
||||
<BarChart
|
||||
options={{
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: formatNum
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'Realtime messages',
|
||||
data: [
|
||||
...(orgUsage.realtimeMessages ?? []).map((e) => [e.date, e.value])
|
||||
]
|
||||
}
|
||||
]} />
|
||||
{#if projects?.length > 0}
|
||||
<ProjectBreakdown {projects} metric="realtimeMessages" {usageProjects} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<Layout.Stack gap="xs" alignItems="center" justifyContent="center">
|
||||
<Icon icon={IconChartSquareBar} size="l" />
|
||||
<Typography.Text variant="m-600">No data to show</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Realtime bandwidth</svelte:fragment>
|
||||
Total realtime bandwidth consumed across all projects in your organization.
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
{#if orgUsage.realtimeBandwidthTotal}
|
||||
{@const current = orgUsage.realtimeBandwidthTotal}
|
||||
{@const currentHumanized = humanFileSize(current)}
|
||||
<ProgressBarBig
|
||||
currentUnit={currentHumanized.unit}
|
||||
currentValue={currentHumanized.value}
|
||||
progressValue={current}
|
||||
showBar={false} />
|
||||
<BarChart
|
||||
options={{
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: (value) =>
|
||||
humanFileSize(value).value + humanFileSize(value).unit
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'Realtime bandwidth',
|
||||
data: [
|
||||
...(orgUsage.realtimeBandwidth ?? []).map((e) => [e.date, e.value])
|
||||
]
|
||||
}
|
||||
]} />
|
||||
{#if projects?.length > 0}
|
||||
<ProjectBreakdown {projects} metric="realtimeBandwidth" {usageProjects} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<Layout.Stack gap="xs" alignItems="center" justifyContent="center">
|
||||
<Icon icon={IconChartSquareBar} size="l" />
|
||||
<Typography.Text variant="m-600">No data to show</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid gap="none">
|
||||
<svelte:fragment slot="title">Storage</svelte:fragment>
|
||||
Calculated for all your files, deployments, builds, databases and backups.
|
||||
|
||||
@@ -35,7 +35,13 @@ export const load: PageLoad = async ({ params, parent }) => {
|
||||
imageTransformations: null,
|
||||
imageTransformationsTotal: null,
|
||||
screenshotsGenerated: null,
|
||||
screenshotsGeneratedTotal: null
|
||||
screenshotsGeneratedTotal: null,
|
||||
realtimeConnections: null,
|
||||
realtimeConnectionsTotal: null,
|
||||
realtimeMessages: null,
|
||||
realtimeMessagesTotal: null,
|
||||
realtimeBandwidth: null,
|
||||
realtimeBandwidthTotal: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+7
-1
@@ -17,7 +17,10 @@
|
||||
| 'databasesReads'
|
||||
| 'databasesWrites'
|
||||
| 'imageTransformations'
|
||||
| 'screenshotsGenerated';
|
||||
| 'screenshotsGenerated'
|
||||
| 'realtime'
|
||||
| 'realtimeMessages'
|
||||
| 'realtimeBandwidth';
|
||||
|
||||
type Estimate = 'authPhoneEstimate';
|
||||
|
||||
@@ -97,9 +100,12 @@
|
||||
return formatNumberWithCommas(value);
|
||||
case 'executions':
|
||||
case 'users':
|
||||
case 'realtime':
|
||||
case 'realtimeMessages':
|
||||
return abbreviateNumber(value);
|
||||
case 'storage':
|
||||
case 'bandwidth':
|
||||
case 'realtimeBandwidth':
|
||||
return humanFileSize(value).value + humanFileSize(value).unit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,77 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Card, Link, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { currentPlan, organizationList } from '$lib/stores/organization';
|
||||
import SupportWizard from '$routes/(console)/supportWizard.svelte';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { currentPlan } from '$lib/stores/organization';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
function getResource(id: string) {
|
||||
id = id.replace('/(console)/project-[region]-[project]/', '');
|
||||
let parts = id.split('/');
|
||||
const resource = parts[0];
|
||||
|
||||
return resource === 'settings' ? 'project' : resource;
|
||||
function contactSupport() {
|
||||
wizard.start(SupportWizard);
|
||||
}
|
||||
|
||||
$: allOrgsHavePremiumSupport = $organizationList.teams.every(
|
||||
(team) => (team as Models.Organization).billingPlanDetails.premiumSupport
|
||||
);
|
||||
|
||||
$: hasPremiumSupport = $currentPlan?.premiumSupport ?? allOrgsHavePremiumSupport ?? false;
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if page.error.type === 'general_resource_blocked'}
|
||||
{@const resource = getResource(page.route.id)}
|
||||
<Card.Base>
|
||||
<Typography.Title size="s">Your {capitalize(resource)} is paused</Typography.Title>
|
||||
<p class="text-red-500">
|
||||
We've detected unusual activity and temporarily paused your {resource}. If you
|
||||
believe this is a mistake or need urgent access, please contact
|
||||
{#if $currentPlan?.premiumSupport}
|
||||
<Link.Button
|
||||
on:click={() => {
|
||||
wizard.start(SupportWizard);
|
||||
}}>support</Link.Button
|
||||
>.
|
||||
{:else}
|
||||
support@appwrite.io.
|
||||
{/if}
|
||||
</p>
|
||||
</Card.Base>
|
||||
{:else}
|
||||
<Typography.Title size="xl"
|
||||
>{'status' in page.error
|
||||
? page.error.status || 'Invalid Argument'
|
||||
: 'Invalid Argument'}</Typography.Title>
|
||||
{#if page.error.type === 'general_resource_blocked'}
|
||||
<section class="resource-blocked">
|
||||
<div class="resource-blocked__content">
|
||||
<Layout.Stack gap="s" alignItems="center">
|
||||
<Badge type="error" variant="secondary" content="Access blocked" />
|
||||
<Typography.Title size="l" align="center">
|
||||
This resource page can't be accessed. Check your permissions or contact
|
||||
support for help.
|
||||
</Typography.Title>
|
||||
<div class="u-margin-block-start-16">
|
||||
{#if hasPremiumSupport}
|
||||
<Button secondary on:click={contactSupport}>Contact support</Button>
|
||||
{:else}
|
||||
<Button secondary href="mailto:support@appwrite.io">Contact support</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</section>
|
||||
{:else}
|
||||
<section class="resource-blocked resource-blocked--default">
|
||||
<div class="resource-blocked__content">
|
||||
<Layout.Stack gap="s" alignItems="center">
|
||||
<Typography.Title size="s" align="center">
|
||||
{'status' in page.error
|
||||
? page.error.status || 'Invalid Argument'
|
||||
: 'Invalid Argument'}
|
||||
</Typography.Title>
|
||||
<p class="u-text-center">{page.error.message}</p>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<Typography.Title>{page.error.message}</Typography.Title>
|
||||
{/if}
|
||||
</Container>
|
||||
<style>
|
||||
.resource-blocked {
|
||||
min-height: calc(100vh - 48px - 2rem);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rem 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.resource-blocked__content {
|
||||
width: min(100%, 33rem);
|
||||
max-width: 33rem;
|
||||
}
|
||||
|
||||
.resource-blocked p {
|
||||
margin: 0;
|
||||
color: var(--fgcolor-neutral-secondary, #56565c);
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
text-wrap: balance;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { BackupRestoreBox, MigrationBox, UploadBox, CsvExportBox } from '$lib/components';
|
||||
import VariablesImportBox from './variablesImportBox.svelte';
|
||||
import { realtime } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { project, stats } from './store';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { registerCommands, registerSearchers } from '$lib/commandCenter';
|
||||
import { disableCommands, registerCommands, registerSearchers } from '$lib/commandCenter';
|
||||
|
||||
import {
|
||||
bucketSearcher,
|
||||
@@ -28,8 +29,28 @@
|
||||
import { isCloud } from '$lib/system';
|
||||
import PausedProjectModal from './pausedProjectModal.svelte';
|
||||
import type { LayoutData } from './$types';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { currentPlan, organizationList } from '$lib/stores/organization';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import SupportWizard from '$routes/(console)/supportWizard.svelte';
|
||||
import BlockedLock from './blocked-lock.svg';
|
||||
import { isProjectBlocked as getIsProjectBlocked } from '$lib/helpers/project';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let data: LayoutData;
|
||||
$: isProjectBlocked = getIsProjectBlocked(data.project);
|
||||
$: $disableCommands(isProjectBlocked);
|
||||
$: allOrgsHavePremiumSupport =
|
||||
isCloud &&
|
||||
($organizationList?.teams ?? []).every(
|
||||
(team) => (team as Models.Organization).billingPlanDetails?.premiumSupport === true
|
||||
);
|
||||
$: hasPremiumSupport = $currentPlan?.premiumSupport ?? allOrgsHavePremiumSupport ?? false;
|
||||
|
||||
function contactSupport() {
|
||||
wizard.start(SupportWizard);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
return realtime.forProject(page.params.region, ['project', 'console'], (response) => {
|
||||
@@ -117,7 +138,45 @@
|
||||
$registerSearchers(userSearcher, teamSearcher, dbSearcher, functionsSearcher, bucketSearcher);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
<div class="project-layout" class:is-blocked={isProjectBlocked}>
|
||||
<div
|
||||
class="project-layout__content"
|
||||
aria-hidden={isProjectBlocked}
|
||||
inert={isProjectBlocked || undefined}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
{#if isProjectBlocked}
|
||||
<div class="project-layout__overlay">
|
||||
<div class="project-layout__lock">
|
||||
<img
|
||||
src={BlockedLock}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="project-layout__lock-icon" />
|
||||
</div>
|
||||
|
||||
<Layout.Stack gap="s" alignItems="center">
|
||||
<Layout.Stack gap="xs" alignItems="center">
|
||||
<Typography.Title size="l" align="center">
|
||||
Project is currently blocked
|
||||
</Typography.Title>
|
||||
<Typography.Text align="center">
|
||||
Access to this project is restricted. Contact support if the issue persists.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<div class="u-margin-block-start-12">
|
||||
{#if hasPremiumSupport}
|
||||
<Button secondary on:click={contactSupport}>Contact support</Button>
|
||||
{:else}
|
||||
<Button secondary href="mailto:support@appwrite.io">Contact support</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if isCloud && data.project?.status === 'paused'}
|
||||
<PausedProjectModal show={true} projectId={data.project.$id} teamId={data.project.teamId} />
|
||||
@@ -129,9 +188,66 @@
|
||||
<BackupRestoreBox />
|
||||
<CsvImportBox />
|
||||
<CsvExportBox />
|
||||
<VariablesImportBox />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.project-layout {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.project-layout__content {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.project-layout.is-blocked .project-layout__content {
|
||||
filter: blur(3px);
|
||||
opacity: 0.18;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.project-layout__overlay {
|
||||
position: fixed;
|
||||
inset: 48px 0 0 0;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.project-layout__overlay {
|
||||
padding-left: calc(190px + 2rem);
|
||||
}
|
||||
}
|
||||
|
||||
.project-layout__lock {
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: color-mix(in srgb, var(--bgcolor-neutral-primary, #ffffff) 96%, transparent);
|
||||
border: 1px solid color-mix(in srgb, #fb4f7c 22%, var(--border-neutral, #d7d7db));
|
||||
border-radius: 0.875rem;
|
||||
box-shadow: 0 8px 24px rgba(17, 24, 39, 0.06);
|
||||
}
|
||||
|
||||
.project-layout__lock-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.layout-level-progress-bars {
|
||||
gap: 1rem;
|
||||
z-index: 100;
|
||||
@@ -145,6 +261,22 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.project-layout__overlay {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.project-layout__lock {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.project-layout__lock-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.layout-level-progress-bars {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
|
||||
@@ -3,9 +3,13 @@ import Header from './header.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.TEAM);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'teams', params.team);
|
||||
|
||||
return {
|
||||
header: Header,
|
||||
breadcrumbs: Breadcrumbs,
|
||||
|
||||
@@ -3,9 +3,12 @@ import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import Header from './header.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.USER);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'users', params.user);
|
||||
|
||||
const [user, userFactors] = await Promise.all([
|
||||
sdk.forProject(params.region, params.project).users.get({ userId: params.user }),
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@
|
||||
<Link.Anchor
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://appwrite.io/docs/products/auth">
|
||||
href="https://appwrite.io/docs/products/auth/impersonation">
|
||||
Learn more.
|
||||
</Link.Anchor>
|
||||
</Typography.Text>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.75 10V7.75a3.25 3.25 0 1 1 6.5 0V10"
|
||||
stroke="#FB4F7C"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<rect
|
||||
x="6.25"
|
||||
y="10"
|
||||
width="11.5"
|
||||
height="8.75"
|
||||
rx="2.25"
|
||||
stroke="#FB4F7C"
|
||||
stroke-width="1.5" />
|
||||
<circle cx="12" cy="14.35" r="1.1" fill="#FB4F7C" />
|
||||
<path d="M12 15.45V17" stroke="#FB4F7C" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 586 B |
@@ -49,7 +49,7 @@
|
||||
<Modal
|
||||
bind:show={showCreate}
|
||||
onSubmit={handleVariable}
|
||||
title={`Create' ${isGlobal ? 'global' : 'environment'} variable`}>
|
||||
title={`Create ${isGlobal ? 'global' : 'environment'} variables`}>
|
||||
<svelte:fragment slot="description">
|
||||
<span>
|
||||
Set the environment variables or secret keys that will be passed to {!isGlobal
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
import EmptySearch from '$lib/components/emptySearch.svelte';
|
||||
import { isServiceLimited } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
|
||||
import { resolveRoute, withPath } from '$lib/stores/navigation';
|
||||
import EmptyDatabaseCloud from './empty.svelte';
|
||||
@@ -48,9 +52,9 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if data.databases.total}
|
||||
{@render containerHeader()}
|
||||
{@render containerHeader()}
|
||||
|
||||
{#if data.databases.total}
|
||||
{#if data.view === 'grid'}
|
||||
<Grid {data} onCreateDatabaseClick={goToCreateDatabaseWizard} />
|
||||
{:else}
|
||||
@@ -97,7 +101,7 @@
|
||||
view={data.view}
|
||||
searchPlaceholder="Search by name or ID">
|
||||
{#if $canWriteDatabases}
|
||||
<Tooltip disabled={!isLimited}>
|
||||
<Tooltip disabled={!isLimited} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button
|
||||
disabled={isLimited}
|
||||
@@ -108,7 +112,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<div style="white-space: pre-line;">
|
||||
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
|
||||
You have reached the maximum number of databases for your plan.
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import type { Page } from '@sveltejs/kit';
|
||||
|
||||
import { capitalize, plural } from '$lib/helpers/string';
|
||||
import { AppwriteException, type DatabasesIndexType, type Models } from '@appwrite.io/console';
|
||||
import { AppwriteException, type TablesDBIndexType, type Models } from '@appwrite.io/console';
|
||||
import type { Attributes, Collection, Columns, Table } from '$database/store';
|
||||
import type { Term, TerminologyResult, TerminologyShape } from '$database/(entity)/helpers/types';
|
||||
|
||||
@@ -103,7 +103,7 @@ const terminologyData = Object.fromEntries(
|
||||
export function toSupportiveIndex(index: Models.Index | Models.ColumnIndex): Index {
|
||||
return {
|
||||
...index,
|
||||
type: index.type as DatabasesIndexType,
|
||||
type: index.type as TablesDBIndexType,
|
||||
fields: (index as Models.Index).attributes ?? (index as Models.ColumnIndex).columns ?? []
|
||||
};
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,5 +1,5 @@
|
||||
<script module lang="ts">
|
||||
import { DatabasesIndexType, OrderBy } from '@appwrite.io/console';
|
||||
import { TablesDBIndexType, OrderBy } from '@appwrite.io/console';
|
||||
export type CreateIndexesCallbackType = {
|
||||
key: string;
|
||||
type: string;
|
||||
@@ -40,14 +40,14 @@
|
||||
|
||||
let key = $state('');
|
||||
let initializedForOpen = $state(false);
|
||||
let selectedType = $state<DatabasesIndexType>(DatabasesIndexType.Key);
|
||||
let selectedType = $state<TablesDBIndexType>(TablesDBIndexType.Key);
|
||||
|
||||
const { dependencies, terminology } = getTerminologies();
|
||||
|
||||
const fieldOptions = $derived(
|
||||
entity.fields
|
||||
.filter((field) => {
|
||||
if (selectedType === DatabasesIndexType.Spatial) {
|
||||
if (selectedType === TablesDBIndexType.Spatial) {
|
||||
// keep only spatial
|
||||
return isSpatialType(field);
|
||||
}
|
||||
@@ -69,13 +69,13 @@
|
||||
|
||||
const types = $derived(
|
||||
[
|
||||
{ value: DatabasesIndexType.Key, label: 'Key' },
|
||||
{ value: DatabasesIndexType.Unique, label: 'Unique' },
|
||||
{ value: DatabasesIndexType.Fulltext, label: 'Fulltext' },
|
||||
{ value: DatabasesIndexType.Spatial, label: 'Spatial' }
|
||||
{ value: TablesDBIndexType.Key, label: 'Key' },
|
||||
{ value: TablesDBIndexType.Unique, label: 'Unique' },
|
||||
{ value: TablesDBIndexType.Fulltext, label: 'Fulltext' },
|
||||
{ value: TablesDBIndexType.Spatial, label: 'Spatial' }
|
||||
].filter((type) => {
|
||||
if (
|
||||
type.value === DatabasesIndexType.Spatial &&
|
||||
type.value === TablesDBIndexType.Spatial &&
|
||||
!$regionalConsoleVariables?.supportForSpatials
|
||||
)
|
||||
return false;
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
// order options derived from selected type
|
||||
let orderOptions = $derived.by(() =>
|
||||
selectedType === DatabasesIndexType.Spatial
|
||||
selectedType === TablesDBIndexType.Spatial
|
||||
? [
|
||||
{ value: OrderBy.Asc, label: 'ASC' },
|
||||
{ value: OrderBy.Desc, label: 'DESC' },
|
||||
@@ -102,7 +102,7 @@
|
||||
$effect(() => {
|
||||
const firstField = entity.fields.find((field) => field.key === fieldList.at(0)?.value);
|
||||
if (
|
||||
selectedType === DatabasesIndexType.Spatial &&
|
||||
selectedType === TablesDBIndexType.Spatial &&
|
||||
firstField &&
|
||||
!isSpatialType(firstField)
|
||||
) {
|
||||
@@ -126,7 +126,7 @@
|
||||
const isSpatial = field.length && isSpatialType(field[0]);
|
||||
const order = isSpatial ? null : OrderBy.Asc;
|
||||
|
||||
selectedType = isSpatial ? DatabasesIndexType.Spatial : DatabasesIndexType.Key;
|
||||
selectedType = isSpatial ? TablesDBIndexType.Spatial : TablesDBIndexType.Key;
|
||||
|
||||
fieldList = externalFieldKey
|
||||
? [{ value: externalFieldKey, order, length: null }]
|
||||
@@ -136,7 +136,7 @@
|
||||
}
|
||||
|
||||
const addFieldDisabled = $derived(
|
||||
selectedType === DatabasesIndexType.Spatial ||
|
||||
selectedType === TablesDBIndexType.Spatial ||
|
||||
!fieldList.at(-1)?.value ||
|
||||
(!fieldList.at(-1)?.order && fieldList.at(-1)?.order !== null)
|
||||
);
|
||||
@@ -170,7 +170,7 @@
|
||||
if (
|
||||
!key ||
|
||||
!selectedType ||
|
||||
(selectedType !== DatabasesIndexType.Spatial && addFieldDisabled)
|
||||
(selectedType !== TablesDBIndexType.Spatial && addFieldDisabled)
|
||||
) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
@@ -250,7 +250,7 @@
|
||||
required
|
||||
options={[
|
||||
// allow system fields only for non-spatial index types
|
||||
...(selectedType === DatabasesIndexType.Spatial
|
||||
...(selectedType === TablesDBIndexType.Spatial
|
||||
? []
|
||||
: [
|
||||
{ value: '$id', label: '$id', leadingIcon: IconFingerPrint },
|
||||
@@ -280,7 +280,7 @@
|
||||
bind:value={field.order}
|
||||
placeholder="Select order" />
|
||||
|
||||
{#if selectedType === DatabasesIndexType.Key}
|
||||
{#if selectedType === TablesDBIndexType.Key}
|
||||
<InputNumber
|
||||
id={`length-${index}`}
|
||||
label={index === 0 ? 'Length' : undefined}
|
||||
|
||||
+13
-4
@@ -47,13 +47,17 @@
|
||||
onCreateIndex,
|
||||
onDeleteIndexes,
|
||||
emptyIndexesSheetView,
|
||||
emptyEntitiesSheetView
|
||||
emptyEntitiesSheetView,
|
||||
createIndexForm,
|
||||
createIndexRef = $bindable()
|
||||
}: {
|
||||
entity: Entity;
|
||||
onCreateIndex: (index: CreateIndexesCallbackType) => Promise<void>;
|
||||
onDeleteIndexes: (indexKeys: string[]) => Promise<void>;
|
||||
emptyIndexesSheetView: Snippet<[() => void]>;
|
||||
emptyEntitiesSheetView?: Snippet<[() => void]>;
|
||||
createIndexForm?: Snippet;
|
||||
createIndexRef?: { create: () => Promise<void> };
|
||||
} = $props();
|
||||
|
||||
let showCreateIndex = $state(false);
|
||||
@@ -194,7 +198,7 @@
|
||||
<Button
|
||||
secondary
|
||||
event="create_index"
|
||||
disabled={!entity.fields?.length}
|
||||
disabled={!createIndexForm && !entity.fields?.length}
|
||||
on:click={() => (showCreateIndex = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create index
|
||||
@@ -345,9 +349,14 @@
|
||||
bind:show={showCreateIndex}
|
||||
submit={{
|
||||
text: 'Create',
|
||||
onClick: async () => await createIndex.create()
|
||||
onClick: async () =>
|
||||
createIndexForm ? await createIndexRef?.create() : await createIndex.create()
|
||||
}}>
|
||||
<CreateIndex {entity} {onCreateIndex} {showCreateIndex} bind:this={createIndex} />
|
||||
{#if createIndexForm}
|
||||
{@render createIndexForm()}
|
||||
{:else}
|
||||
<CreateIndex {entity} {onCreateIndex} {showCreateIndex} bind:this={createIndex} />
|
||||
{/if}
|
||||
</SideSheet>
|
||||
|
||||
{#if selectedIndex}
|
||||
|
||||
+9
-9
@@ -6,7 +6,7 @@
|
||||
import { Modal } from '$lib/components';
|
||||
import { type Entity, SideSheet } from '$database/(entity)';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { DatabasesIndexType, OrderBy, TablesDBIndexType } from '@appwrite.io/console';
|
||||
import { OrderBy, TablesDBIndexType } from '@appwrite.io/console';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import type { Columns } from '$database/store';
|
||||
import { isRelationship } from '../table-[table]/rows/store';
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
indexes = mockSuggestions.columns.slice(0, 3).map((column, index) => ({
|
||||
key: column.name,
|
||||
type: DatabasesIndexType.Key,
|
||||
type: TablesDBIndexType.Key,
|
||||
fields: [column.name],
|
||||
orders: index === 2 ? OrderBy.Desc : OrderBy.Asc,
|
||||
lengths: []
|
||||
@@ -85,7 +85,7 @@
|
||||
indexes = suggestions.indexes.map((index) => {
|
||||
return {
|
||||
key: index.columns[0],
|
||||
type: index.type as DatabasesIndexType,
|
||||
type: index.type as TablesDBIndexType,
|
||||
orders: (index.orders?.[0] as OrderBy) || OrderBy.Asc,
|
||||
fields: index.columns,
|
||||
lengths: index.lengths ?? []
|
||||
@@ -113,7 +113,7 @@
|
||||
if (indexes.length < MAX_INDEXES) {
|
||||
indexes.push({
|
||||
key: '',
|
||||
type: DatabasesIndexType.Key,
|
||||
type: TablesDBIndexType.Key,
|
||||
orders: OrderBy.Asc,
|
||||
fields: [],
|
||||
lengths: null
|
||||
@@ -134,9 +134,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getOrderOptions(selectedType: DatabasesIndexType) {
|
||||
function getOrderOptions(selectedType: TablesDBIndexType) {
|
||||
const base = [OrderBy.Asc, OrderBy.Desc];
|
||||
const values = selectedType === DatabasesIndexType.Spatial ? [...base, null] : base;
|
||||
const values = selectedType === TablesDBIndexType.Spatial ? [...base, null] : base;
|
||||
|
||||
return values.map((order) => ({
|
||||
label: order ? capitalize(String(order)) : 'None',
|
||||
@@ -166,7 +166,7 @@
|
||||
|
||||
// prepare lengths array
|
||||
let lengths: (number | null)[];
|
||||
if (index.type === DatabasesIndexType.Key) {
|
||||
if (index.type === TablesDBIndexType.Key) {
|
||||
// only validate if it's a key index
|
||||
lengths = index.fields.map((columnKey, i) => {
|
||||
const maxSize = columnMap.get(columnKey);
|
||||
@@ -293,10 +293,10 @@
|
||||
}
|
||||
|
||||
const typeOptions = $derived(
|
||||
Object.values(DatabasesIndexType)
|
||||
Object.values(TablesDBIndexType)
|
||||
.filter((type) => {
|
||||
if (
|
||||
type === DatabasesIndexType.Spatial &&
|
||||
type === TablesDBIndexType.Spatial &&
|
||||
!$regionalConsoleVariables?.supportForSpatials
|
||||
)
|
||||
return false;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { OrderBy } from '@appwrite.io/console';
|
||||
import { OrderBy, type TablesDBIndexType } from '@appwrite.io/console';
|
||||
import { columnOptions } from '../table-[table]/columns/store';
|
||||
|
||||
export type EntityColumnSuggestions = {
|
||||
@@ -34,7 +34,7 @@ export type IndexOrder = OrderBy | null;
|
||||
|
||||
export type SuggestedIndexSchema = {
|
||||
key: string;
|
||||
type: string;
|
||||
type: TablesDBIndexType;
|
||||
orders: IndexOrder;
|
||||
fields: string[];
|
||||
lengths?: number[] | undefined;
|
||||
|
||||
+4
-1
@@ -4,9 +4,12 @@ import type { LayoutLoad } from './$types';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import SubNavigation from './subNavigation.svelte';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.DATABASE);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'databases', params.database);
|
||||
|
||||
const database = await sdk.forProject(params.region, params.project).tablesDB.get({
|
||||
databaseId: params.database
|
||||
|
||||
+1
-2
@@ -249,8 +249,7 @@
|
||||
databaseId: page.params.database,
|
||||
tableId: page.params.collection,
|
||||
context: $entityColumnSuggestions.context ?? undefined,
|
||||
min: 6,
|
||||
databaseType: data.database.type
|
||||
min: 6
|
||||
})) as unknown as {
|
||||
total: number;
|
||||
columns: ColumnInput[];
|
||||
|
||||
+69
-25
@@ -17,9 +17,15 @@
|
||||
IconChevronUp,
|
||||
IconPlus,
|
||||
IconViewBoards,
|
||||
IconRefresh
|
||||
IconRefresh,
|
||||
IconUpload,
|
||||
IconDownload
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import { Click } from '$lib/actions/analytics';
|
||||
import { expandTabs, randomDataModalState, spreadsheetRenderKey } from '$database/store';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { hash } from '$lib/helpers/string';
|
||||
@@ -50,36 +56,36 @@
|
||||
let columnDisplayNameInput: ColumnDisplayNameInput | null = $state(null);
|
||||
|
||||
const disableCreateDocument = $derived(
|
||||
$noSqlDocument.isNew && ($noSqlDocument.hasDataChanged || $noSqlDocument.isDirty)
|
||||
$isCollectionsJsonImportInProgress ||
|
||||
($noSqlDocument.isNew && ($noSqlDocument.hasDataChanged || $noSqlDocument.isDirty))
|
||||
);
|
||||
|
||||
function createFilterableColumns(): Column[] {
|
||||
return [
|
||||
{ id: '$id', title: '$id', type: 'string' as ColumnType },
|
||||
{ id: '$createdAt', title: '$createdAt', type: 'datetime' as ColumnType },
|
||||
{ id: '$updatedAt', title: '$updatedAt', type: 'datetime' as ColumnType }
|
||||
];
|
||||
}
|
||||
|
||||
function handleColumnToggle() {
|
||||
// Force spreadsheet re-render when columns are toggled
|
||||
spreadsheetRenderKey.set(hash(Date.now().toString()));
|
||||
function getExportUrl() {
|
||||
const queryParam = page.url.searchParams.get('query');
|
||||
const url = resolve(
|
||||
'/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/export',
|
||||
{
|
||||
region: page.params.region,
|
||||
project: page.params.project,
|
||||
database: page.params.database,
|
||||
collection: page.params.collection
|
||||
}
|
||||
);
|
||||
return queryParam ? `${url}?query=${encodeURIComponent(queryParam)}` : url;
|
||||
}
|
||||
|
||||
async function onSelect(file: Models.File, localFile = false) {
|
||||
$isCollectionsJsonImportInProgress = true;
|
||||
|
||||
console.log(file, localFile);
|
||||
|
||||
try {
|
||||
/*await sdk
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.migrations.createJSONImport({
|
||||
bucketId: file.bucketId,
|
||||
fileId: file.$id,
|
||||
resourceId: `${page.params.database}:${page.params.collection}`,
|
||||
internalFile: localFile
|
||||
});*/
|
||||
});
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -98,6 +104,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
function createFilterableColumns(): Column[] {
|
||||
return [
|
||||
{ id: '$id', title: '$id', type: 'string' as ColumnType },
|
||||
{ id: '$createdAt', title: '$createdAt', type: 'datetime' as ColumnType },
|
||||
{ id: '$updatedAt', title: '$updatedAt', type: 'datetime' as ColumnType }
|
||||
];
|
||||
}
|
||||
|
||||
function handleColumnToggle() {
|
||||
// Force spreadsheet re-render when columns are toggled
|
||||
spreadsheetRenderKey.set(hash(Date.now().toString()));
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
filterColumns.set(createFilterableColumns());
|
||||
});
|
||||
@@ -147,13 +166,6 @@
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="flex-end">
|
||||
<!--<Button
|
||||
secondary
|
||||
disabled
|
||||
event={Click.DatabaseImportJson}
|
||||
on:click={() => (showImportJson = true)}>
|
||||
Import JSON
|
||||
</Button>-->
|
||||
{#if !$isSmallViewport}
|
||||
<Tooltip
|
||||
maxWidth="210px"
|
||||
@@ -176,10 +188,42 @@
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="tooltip">
|
||||
Save your current document before creating a new one
|
||||
{$isCollectionsJsonImportInProgress
|
||||
? 'This action is disabled during import'
|
||||
: 'Save your current document before creating a new one'}
|
||||
</svelte:fragment>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip placement="top">
|
||||
<Button
|
||||
icon
|
||||
size="s"
|
||||
secondary
|
||||
class="small-button-dimensions"
|
||||
disabled={$isCollectionsJsonImportInProgress}
|
||||
on:click={() => (showImportJson = true)}>
|
||||
<Icon icon={IconUpload} size="s" />
|
||||
</Button>
|
||||
<svelte:fragment slot="tooltip">Import JSON</svelte:fragment>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip placement="top">
|
||||
<Button
|
||||
icon
|
||||
size="s"
|
||||
secondary
|
||||
class="small-button-dimensions"
|
||||
disabled={!data.documents.total ||
|
||||
$isCollectionsJsonImportInProgress}
|
||||
on:click={() => {
|
||||
trackEvent(Click.DatabaseExportCsv);
|
||||
goto(getExportUrl());
|
||||
}}>
|
||||
<Icon icon={IconDownload} size="s" />
|
||||
</Button>
|
||||
<svelte:fragment slot="tooltip">Export JSON</svelte:fragment>
|
||||
</Tooltip>
|
||||
|
||||
<Button
|
||||
icon
|
||||
size="s"
|
||||
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Wizard } from '$lib/layout';
|
||||
import { Fieldset, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Button, InputCheckbox, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { toLocalDateTimeISO } from '$lib/helpers/date';
|
||||
import { writable } from 'svelte/store';
|
||||
import { queries, type TagValue } from '$lib/components/filters/store';
|
||||
import { TagList } from '$lib/components/filters';
|
||||
|
||||
let showExitModal = $state(false);
|
||||
let formComponent: Form;
|
||||
let isSubmitting = $state(writable(false));
|
||||
|
||||
let localQueries = $state<Map<TagValue, string>>(new Map());
|
||||
const localTags = $derived(Array.from(localQueries.keys()));
|
||||
|
||||
const timestamp = toLocalDateTimeISO(Date.now())
|
||||
.replace(/[:.]/g, '-')
|
||||
.split('T')
|
||||
.join('_')
|
||||
.slice(0, -4);
|
||||
|
||||
const collectionName = page.params.collection;
|
||||
const filename = `${collectionName}_${timestamp}.json`;
|
||||
|
||||
let exportWithFilters = $state(false);
|
||||
|
||||
const collectionUrl = $derived.by(() => {
|
||||
const queryParam = page.url.searchParams.get('query');
|
||||
const url = resolve(
|
||||
'/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]',
|
||||
{
|
||||
region: page.params.region,
|
||||
project: page.params.project,
|
||||
database: page.params.database,
|
||||
collection: page.params.collection
|
||||
}
|
||||
);
|
||||
return queryParam ? `${url}?query=${encodeURIComponent(queryParam)}` : url;
|
||||
});
|
||||
|
||||
function removeLocalFilter(tag: TagValue) {
|
||||
localQueries.delete(tag);
|
||||
localQueries = new Map(localQueries);
|
||||
}
|
||||
|
||||
async function handleExport() {
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.migrations.createJSONExport({
|
||||
resourceId: `${page.params.database}:${page.params.collection}`,
|
||||
filename: filename,
|
||||
columns: [],
|
||||
queries: exportWithFilters ? Array.from(localQueries.values()) : [],
|
||||
notify: true
|
||||
});
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'JSON export has started. You will receive an email when it is ready.'
|
||||
});
|
||||
|
||||
trackEvent(Submit.DatabaseExportCsv);
|
||||
|
||||
await goto(collectionUrl);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
|
||||
trackError(error, Submit.DatabaseExportCsv);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
localQueries = new Map($queries);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard
|
||||
title="Export JSON"
|
||||
columnSize="s"
|
||||
href={collectionUrl}
|
||||
bind:showExitModal
|
||||
confirmExit
|
||||
column>
|
||||
<Form bind:this={formComponent} bind:isSubmitting onSubmit={handleExport}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Fieldset legend="Export options">
|
||||
<Layout.Stack gap="l">
|
||||
<Layout.Stack gap="m">
|
||||
<div class:disabled-checkbox={localTags.length === 0}>
|
||||
<InputCheckbox
|
||||
id="exportWithFilters"
|
||||
label="Export with filters"
|
||||
description="Export documents that match the current collection filters"
|
||||
disabled={localTags.length === 0}
|
||||
bind:checked={exportWithFilters} />
|
||||
</div>
|
||||
|
||||
{#if localTags.length > 0}
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
gap="xs"
|
||||
alignItems="center"
|
||||
style="padding-left: 1.75rem;"
|
||||
wrap="wrap">
|
||||
<TagList
|
||||
tags={localTags}
|
||||
on:remove={(e) => {
|
||||
removeLocalFilter(e.detail);
|
||||
}} />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="footer">
|
||||
<Layout.Stack justifyContent="flex-end" direction="row">
|
||||
<Button fullWidthMobile secondary on:click={() => (showExitModal = true)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
fullWidthMobile
|
||||
on:click={() => formComponent.triggerSubmit()}
|
||||
disabled={$isSubmitting}>
|
||||
Export
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</Wizard>
|
||||
|
||||
<style>
|
||||
.disabled-checkbox :global(*) {
|
||||
cursor: unset;
|
||||
}
|
||||
</style>
|
||||
+12
-2
@@ -9,9 +9,12 @@
|
||||
useDatabaseSdk
|
||||
} from '$database/(entity)';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import CreateIndexForm from './createIndex.svelte';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
let createIndexRef: CreateIndexForm;
|
||||
|
||||
const databaseSdk = useDatabaseSdk(page.params.region, page.params.project, data.database.type);
|
||||
|
||||
async function onCreateIndex(index: CreateIndexesCallbackType) {
|
||||
@@ -39,14 +42,21 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Indexes {onCreateIndex} {onDeleteIndexes} entity={data.collection}>
|
||||
<Indexes {onCreateIndex} {onDeleteIndexes} entity={data.collection} bind:createIndexRef>
|
||||
{#snippet createIndexForm()}
|
||||
<CreateIndexForm
|
||||
entity={data.collection}
|
||||
{onCreateIndex}
|
||||
showCreateIndex={true}
|
||||
bind:this={createIndexRef} />
|
||||
{/snippet}
|
||||
|
||||
{#snippet emptyIndexesSheetView(toggle)}
|
||||
<EmptySheet mode="indexes" type={data.database.type}>
|
||||
{#snippet actions()}
|
||||
<EmptySheetCards
|
||||
icon={IconPlus}
|
||||
title="Create index"
|
||||
disabled={!data.collection.fields?.length}
|
||||
subtitle="Create indexes manually"
|
||||
onClick={toggle} />
|
||||
{/snippet}
|
||||
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
<script module lang="ts">
|
||||
import { DocumentsDBIndexType, OrderBy } from '@appwrite.io/console';
|
||||
import type { CreateIndexesCallbackType } from '$database/(entity)';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, InputNumber, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import { remove } from '$lib/helpers/array';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { IconCalendar, IconFingerPrint, IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { type Entity, getTerminologies } from '$database/(entity)';
|
||||
import { resolveRoute, withPath } from '$lib/stores/navigation';
|
||||
|
||||
let {
|
||||
entity,
|
||||
showCreateIndex = $bindable(false),
|
||||
onCreateIndex
|
||||
}: {
|
||||
entity: Entity;
|
||||
showCreateIndex: boolean;
|
||||
onCreateIndex: (index: CreateIndexesCallbackType) => Promise<void>;
|
||||
} = $props();
|
||||
|
||||
let key = $state('');
|
||||
let initializedForOpen = $state(false);
|
||||
let selectedType = $state<DocumentsDBIndexType>(DocumentsDBIndexType.Key);
|
||||
|
||||
const { dependencies, terminology } = getTerminologies();
|
||||
|
||||
const hasAttributes = $derived(entity.fields?.length > 0);
|
||||
|
||||
const fieldOptions = $derived(
|
||||
(entity.fields ?? []).map((field) => ({
|
||||
value: field.key,
|
||||
label: field.key
|
||||
}))
|
||||
);
|
||||
|
||||
let fieldList: Array<{
|
||||
value: string;
|
||||
order: OrderBy;
|
||||
length: number | null;
|
||||
}> = $state([{ value: '', order: OrderBy.Asc, length: null }]);
|
||||
|
||||
const types = [
|
||||
{ value: DocumentsDBIndexType.Key, label: 'Key' },
|
||||
{ value: DocumentsDBIndexType.Unique, label: 'Unique' },
|
||||
{ value: DocumentsDBIndexType.Fulltext, label: 'Fulltext' }
|
||||
];
|
||||
|
||||
const orderOptions = [
|
||||
{ value: OrderBy.Asc, label: 'ASC' },
|
||||
{ value: OrderBy.Desc, label: 'DESC' }
|
||||
];
|
||||
|
||||
function generateIndexKey() {
|
||||
let indexKeys = entity.indexes.map((index) => index.key);
|
||||
|
||||
let highestIndex = indexKeys.reduce((max, key) => {
|
||||
const match = key.match(/^index_(\d+)$/);
|
||||
return match ? Math.max(max, parseInt(match[1], 10)) : max;
|
||||
}, indexKeys.length);
|
||||
|
||||
return `index_${highestIndex + 1}`;
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
selectedType = DocumentsDBIndexType.Key;
|
||||
fieldList = [{ value: '', order: OrderBy.Asc, length: null }];
|
||||
key = `index_${entity.indexes.length + 1}`;
|
||||
}
|
||||
|
||||
const addFieldDisabled = $derived(!fieldList.at(-1)?.value || !fieldList.at(-1)?.order);
|
||||
|
||||
const isOnIndexesPage = $derived(page.route.id?.endsWith('/indexes'));
|
||||
const navigatorPathToIndexes = $derived.by(() => {
|
||||
const type = terminology.entity.lower.singular;
|
||||
const base = resolveRoute(
|
||||
'/(console)/project-[region]-[project]/databases/database-[database]',
|
||||
page.params
|
||||
);
|
||||
|
||||
return withPath(base, `${type}-${entity.$id}`, 'indexes');
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (showCreateIndex && !initializedForOpen) {
|
||||
initialize();
|
||||
key = generateIndexKey();
|
||||
initializedForOpen = true;
|
||||
}
|
||||
|
||||
if (!showCreateIndex && initializedForOpen) {
|
||||
initializedForOpen = false;
|
||||
}
|
||||
});
|
||||
|
||||
export async function create() {
|
||||
const fieldType = terminology.field.lower.singular;
|
||||
|
||||
if (!key || !selectedType || addFieldDisabled) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: `Selected ${fieldType} key or type invalid`
|
||||
});
|
||||
throw new Error(`Selected ${fieldType} key or type invalid`);
|
||||
}
|
||||
|
||||
try {
|
||||
await onCreateIndex({
|
||||
key,
|
||||
type: selectedType,
|
||||
fields: fieldList.map((a) => a.value),
|
||||
lengths: fieldList.map((a) => (a.length ? Number(a.length) : null)),
|
||||
orders: fieldList.map((a) => a.order)
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
invalidate(dependencies.entity.singular),
|
||||
invalidate(Dependencies.DATABASE)
|
||||
]);
|
||||
|
||||
addNotification({
|
||||
message: 'Index is being created',
|
||||
type: 'success',
|
||||
buttons: !isOnIndexesPage
|
||||
? [
|
||||
{
|
||||
name: 'View indexes',
|
||||
method: () => goto(navigatorPathToIndexes)
|
||||
}
|
||||
]
|
||||
: undefined
|
||||
});
|
||||
trackEvent(Submit.IndexCreate, { type: 'manual' });
|
||||
showCreateIndex = false;
|
||||
} catch (err) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: err.message
|
||||
});
|
||||
trackError(err, Submit.IndexCreate);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function addField() {
|
||||
if (addFieldDisabled) return;
|
||||
fieldList = [...fieldList, { value: '', order: OrderBy.Asc, length: null }];
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputText
|
||||
required
|
||||
id="key"
|
||||
label="Index Key"
|
||||
pattern="^[A-Za-z0-9][A-Za-z0-9._\-]*$"
|
||||
placeholder="Enter Key"
|
||||
bind:value={key}
|
||||
autofocus />
|
||||
|
||||
<InputSelect required id="type" options={types} label="Index type" bind:value={selectedType} />
|
||||
|
||||
<Layout.Stack gap="s">
|
||||
{@const fieldType = terminology.field.title.singular}
|
||||
{@const fieldTypeLower = terminology.field.lower.singular}
|
||||
{#each fieldList as field, index}
|
||||
{@const direction = $isSmallViewport ? 'column' : 'row'}
|
||||
<Layout.Stack {direction}>
|
||||
{#if hasAttributes}
|
||||
<InputSelect
|
||||
required
|
||||
options={[
|
||||
{ value: '$id', label: '$id', leadingIcon: IconFingerPrint },
|
||||
{
|
||||
value: '$createdAt',
|
||||
label: '$createdAt',
|
||||
leadingIcon: IconCalendar
|
||||
},
|
||||
{
|
||||
value: '$updatedAt',
|
||||
label: '$updatedAt',
|
||||
leadingIcon: IconCalendar
|
||||
},
|
||||
...fieldOptions
|
||||
]}
|
||||
id={`field-${index}`}
|
||||
label={index === 0 ? fieldType : undefined}
|
||||
placeholder="Select {fieldType}"
|
||||
bind:value={field.value} />
|
||||
{:else}
|
||||
<InputText
|
||||
required
|
||||
id={`field-${index}`}
|
||||
label={index === 0 ? fieldType : undefined}
|
||||
placeholder="Enter {fieldType} name"
|
||||
bind:value={field.value} />
|
||||
{/if}
|
||||
|
||||
<InputSelect
|
||||
options={orderOptions}
|
||||
required
|
||||
id={`order-${index}`}
|
||||
label={index === 0 ? 'Order' : undefined}
|
||||
bind:value={field.order}
|
||||
placeholder="Select order" />
|
||||
|
||||
{#if selectedType === DocumentsDBIndexType.Key}
|
||||
<InputNumber
|
||||
id={`length-${index}`}
|
||||
label={index === 0 ? 'Length' : undefined}
|
||||
placeholder="Enter length"
|
||||
bind:value={field.length} />
|
||||
{/if}
|
||||
|
||||
{#if $isSmallViewport}
|
||||
<div style:margin-top="0.25rem">
|
||||
<Button
|
||||
text
|
||||
secondary
|
||||
disabled={fieldList.length <= 1}
|
||||
on:click={() => {
|
||||
fieldList = remove(fieldList, index);
|
||||
}}>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div style:margin-top="27.6px" class="x-button-holder">
|
||||
<Button
|
||||
icon
|
||||
size="s"
|
||||
secondary
|
||||
disabled={fieldList.length <= 1}
|
||||
on:click={() => {
|
||||
fieldList = remove(fieldList, index);
|
||||
}}>
|
||||
<Icon icon={IconX} size="s" />
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
<div>
|
||||
<Button compact on:click={addField} disabled={addFieldDisabled}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add {fieldTypeLower}
|
||||
</Button>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
|
||||
<style lang="scss">
|
||||
.x-button-holder :global(button) {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
</style>
|
||||
+3
-1
@@ -2,10 +2,12 @@ import Header from './header.svelte';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Breadcrumbs, useDatabaseSdk } from '$database/(entity)';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
const { database } = await parent();
|
||||
const { database, project } = await parent();
|
||||
depends(Dependencies.TABLE);
|
||||
guardResourceBlock(project, ['tables', 'collections'], params.table);
|
||||
|
||||
const databaseSdk = useDatabaseSdk(params.region, params.project, database.type);
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
import { isServiceLimited } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
|
||||
import { parseExpression } from 'cron-parser';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -102,7 +106,7 @@
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<SearchQuery placeholder="Search by name or ID" />
|
||||
|
||||
<Tooltip disabled={!isLimited}>
|
||||
<Tooltip disabled={!isLimited} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button disabled={isLimited} href={createFunctionsUrl}>
|
||||
<Icon icon={IconPlus} slot="start" />
|
||||
@@ -110,7 +114,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<div style="white-space: pre-line;">
|
||||
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
|
||||
You have reached the maximum number of functions for your plan.
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@
|
||||
installationId: data.installation.$id,
|
||||
providerRepositoryId: page.params.repository,
|
||||
type: VCSDetectionType.Runtime
|
||||
})) as unknown as Models.DetectionRuntime; /* SDK return type is wrong atm */
|
||||
})) as unknown as Models.DetectionRuntime;
|
||||
|
||||
entrypoint = detections.entrypoint;
|
||||
buildCommand = detections.commands;
|
||||
|
||||
+4
-1
@@ -5,10 +5,13 @@ import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import Header from './header.svelte';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { RuleType } from '$lib/stores/sdk';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.FUNCTION);
|
||||
depends(Dependencies.DEPLOYMENTS);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'functions', params.function);
|
||||
|
||||
const func = await sdk
|
||||
.forProject(params.region, params.project)
|
||||
|
||||
+10
-6
@@ -2,6 +2,10 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Empty, EmptyFilter, PaginationWithLimit } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container, ResponsiveContainerHeader } from '$lib/layout';
|
||||
import { realtime } from '$lib/stores/sdk';
|
||||
@@ -27,7 +31,7 @@
|
||||
|
||||
<Container>
|
||||
<ResponsiveContainerHeader hasFilters {columns} hideView analyticsSource="function_executions">
|
||||
<Tooltip disabled={!!data.func?.deploymentId}>
|
||||
<Tooltip disabled={!!data.func?.deploymentId} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button
|
||||
event="execute_function"
|
||||
@@ -37,9 +41,9 @@
|
||||
Create execution
|
||||
</Button>
|
||||
</div>
|
||||
<span slot="tooltip">
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
|
||||
Execution cannot be created because there is no active deployment.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</ResponsiveContainerHeader>
|
||||
|
||||
@@ -63,7 +67,7 @@
|
||||
event="empty_documentation"
|
||||
size="s"
|
||||
ariaLabel="create execution">Documentation</Button>
|
||||
<Tooltip disabled={!!data.func?.deploymentId}>
|
||||
<Tooltip disabled={!!data.func?.deploymentId} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button
|
||||
secondary
|
||||
@@ -73,9 +77,9 @@
|
||||
Create execution
|
||||
</Button>
|
||||
</div>
|
||||
<span slot="tooltip">
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
|
||||
Execution cannot be created because there is no active deployment.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
|
||||
+4
-1
@@ -4,9 +4,12 @@ import Header from './header.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.MESSAGING_MESSAGE);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'messages', params.message);
|
||||
|
||||
const message = await sdk
|
||||
.forProject(params.region, params.project)
|
||||
|
||||
+4
-1
@@ -3,9 +3,12 @@ import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import Header from './header.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.MESSAGING_PROVIDER);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'providers', params.provider);
|
||||
|
||||
return {
|
||||
header: Header,
|
||||
|
||||
+4
-1
@@ -3,9 +3,12 @@ import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import Header from './header.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.MESSAGING_TOPIC);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'topics', params.topic);
|
||||
|
||||
return {
|
||||
header: Header,
|
||||
|
||||
+1
-5
@@ -16,7 +16,6 @@
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import Scopes from '../api-keys/scopes.svelte';
|
||||
import { InteractiveText, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { getEffectiveScopes } from '../api-keys/scopes.svelte';
|
||||
|
||||
export let key: Models.DevKey | Models.Key;
|
||||
export let keyType: 'api' | 'dev' = 'api';
|
||||
@@ -163,8 +162,6 @@
|
||||
{#if isApiKey}
|
||||
<Form onSubmit={updateScopes}>
|
||||
{@const apiKey = asApiKey(key)}
|
||||
{@const apiKeyCorrectScopes = getEffectiveScopes(apiKey.scopes)}
|
||||
{@const currentEffective = scopes ? getEffectiveScopes(scopes) : null}
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Scopes</svelte:fragment>
|
||||
You can choose which permission scope to grant your application. It is a best practice
|
||||
@@ -178,8 +175,7 @@
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
submit
|
||||
disabled={scopes &&
|
||||
!symmetricDifference(currentEffective, apiKeyCorrectScopes).length}
|
||||
disabled={scopes && !symmetricDifference(scopes, apiKey.scopes).length}
|
||||
>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import { Badge, Layout, Table } from '@appwrite.io/pink-svelte';
|
||||
import DeleteBatch from './deleteBatch.svelte';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { getEffectiveScopes } from '../api-keys/scopes.svelte';
|
||||
|
||||
let {
|
||||
keyType = 'api',
|
||||
@@ -31,7 +30,7 @@
|
||||
|
||||
function getApiKeyScopeCount(key: Models.Key | Models.DevKey) {
|
||||
const apiKey = key as Models.Key;
|
||||
return getEffectiveScopes(apiKey.scopes).length;
|
||||
return apiKey.scopes.length;
|
||||
}
|
||||
|
||||
function getExpiryDetails(key: Models.Key | Models.DevKey): {
|
||||
|
||||
@@ -31,29 +31,21 @@
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { scopes as allScopes, cloudOnlyBackupScopes } from '$lib/constants';
|
||||
import { Accordion, Divider, Layout, Selector } from '@appwrite.io/pink-svelte';
|
||||
import { Accordion, Badge, Divider, Layout, Selector } from '@appwrite.io/pink-svelte';
|
||||
import type { Scopes } from '@appwrite.io/console';
|
||||
|
||||
export let scopes: Scopes[];
|
||||
|
||||
const baseFilteredScopes = allScopes.filter((scope) => {
|
||||
const val = scope.scope;
|
||||
if (!val) return false;
|
||||
|
||||
const legacyPrefixes = ['collections.', 'attributes.', 'documents.'];
|
||||
return !legacyPrefixes.some((prefix) => val.startsWith(prefix));
|
||||
});
|
||||
|
||||
// insert cloud-only scopes right after databases.write
|
||||
const databasesWriteIndex = baseFilteredScopes.findIndex((s) => s.scope === 'databases.write');
|
||||
const databasesWriteIndex = allScopes.findIndex((s) => s.scope === 'databases.write');
|
||||
const filteredScopes =
|
||||
isCloud && databasesWriteIndex !== -1
|
||||
? [
|
||||
...baseFilteredScopes.slice(0, databasesWriteIndex + 1),
|
||||
...allScopes.slice(0, databasesWriteIndex + 1),
|
||||
...cloudOnlyBackupScopes,
|
||||
...baseFilteredScopes.slice(databasesWriteIndex + 1)
|
||||
...allScopes.slice(databasesWriteIndex + 1)
|
||||
]
|
||||
: baseFilteredScopes;
|
||||
: allScopes;
|
||||
|
||||
// include all scopes
|
||||
const scopeCatalog = new Set([
|
||||
@@ -90,9 +82,8 @@
|
||||
|
||||
onMount(() => {
|
||||
scopes.forEach((scope) => {
|
||||
const newerScope = toNewerScope(scope);
|
||||
if (newerScope in activeScopes) {
|
||||
activeScopes[newerScope] = true;
|
||||
if (scope in activeScopes) {
|
||||
activeScopes[scope] = true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -111,36 +102,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
function toNewerScope(scope: string): string {
|
||||
for (const pair of compatPairs) {
|
||||
if (scope.startsWith(pair.legacy)) {
|
||||
return scope.replace(pair.legacy, pair.newer);
|
||||
}
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
function getAllScopeVariants(scope: string): string[] {
|
||||
const variants = new Set([scope]);
|
||||
|
||||
for (const pair of compatPairs) {
|
||||
if (scope.startsWith(pair.newer)) {
|
||||
variants.add(scope.replace(pair.newer, pair.legacy));
|
||||
} else if (scope.startsWith(pair.legacy)) {
|
||||
variants.add(scope.replace(pair.legacy, pair.newer));
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(variants);
|
||||
}
|
||||
|
||||
function categoryState(category: string, s: string[]): boolean | 'indeterminate' {
|
||||
const scopesByCategory = filteredScopes.filter((n) => n.category === category);
|
||||
|
||||
const activeInCategory = scopesByCategory.filter((scopeItem) => {
|
||||
const newerScope = scopeItem.scope;
|
||||
return s.some((scope) => toNewerScope(scope) === newerScope);
|
||||
});
|
||||
const activeInCategory = scopesByCategory.filter((scopeItem) =>
|
||||
s.includes(scopeItem.scope as Scopes)
|
||||
);
|
||||
|
||||
if (activeInCategory.length === 0) {
|
||||
return false;
|
||||
@@ -154,27 +120,16 @@
|
||||
function onCategoryChange(event: CustomEvent<boolean | 'indeterminate'>, category: Category) {
|
||||
if (event.detail === 'indeterminate') return;
|
||||
filteredScopes.forEach((s) => {
|
||||
if (s.category === category) {
|
||||
if (s.category === category && !s.deprecated) {
|
||||
activeScopes[s.scope] = event.detail;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generateSyncedScopes(activeScopesObj: Record<string, boolean>): Scopes[] {
|
||||
const result = new Set<string>();
|
||||
|
||||
Object.entries(activeScopesObj).forEach(([scope, isActive]) => {
|
||||
if (isActive) {
|
||||
const variants = getAllScopeVariants(scope);
|
||||
variants.forEach((variant) => {
|
||||
if (scopeCatalog.has(variant)) {
|
||||
result.add(variant);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(result) as Scopes[];
|
||||
return Object.entries(activeScopesObj)
|
||||
.filter(([scope, isActive]) => isActive && scopeCatalog.has(scope))
|
||||
.map(([scope]) => scope as Scopes);
|
||||
}
|
||||
|
||||
$: {
|
||||
@@ -203,9 +158,7 @@
|
||||
{@const checked = categoryState(category, scopes)}
|
||||
{@const isLastItem = index === categories.length - 1}
|
||||
{@const scopesLength = filteredScopes.filter(
|
||||
(n) =>
|
||||
n.category === category &&
|
||||
scopes.some((scope) => toNewerScope(scope) === n.scope)
|
||||
(n) => n.category === category && scopes.includes(n.scope as Scopes)
|
||||
).length}
|
||||
<Accordion
|
||||
selectable
|
||||
@@ -216,12 +169,17 @@
|
||||
on:change={(event) => onCategoryChange(event, category)}>
|
||||
<Layout.Stack>
|
||||
{#each filteredScopes.filter((s) => s.category === category) as scope}
|
||||
<Selector.Checkbox
|
||||
size="s"
|
||||
id={scope.scope}
|
||||
label={scope.scope}
|
||||
description={scope.description}
|
||||
bind:checked={activeScopes[scope.scope]} />
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Selector.Checkbox
|
||||
size="s"
|
||||
id={scope.scope}
|
||||
label={`${scope.scope}${scope.deprecated ? ' (Deprecated)' : ''}`}
|
||||
description={scope.description}
|
||||
bind:checked={activeScopes[scope.scope]} />
|
||||
{#if scope.deprecated}
|
||||
<Badge size="xs" variant="secondary" content="Deprecated" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Accordion>
|
||||
|
||||
@@ -14,13 +14,17 @@
|
||||
import { isServiceLimited } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { page } from '$app/state';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
|
||||
$: isLimited = isServiceLimited('platforms', $organization, page.data.platforms.total);
|
||||
</script>
|
||||
|
||||
{#if $canWritePlatforms}
|
||||
{#if isLimited}
|
||||
<Tooltip>
|
||||
<Tooltip maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button disabled>
|
||||
<Icon icon={IconPlus} slot="start" />
|
||||
@@ -29,7 +33,7 @@
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="tooltip">
|
||||
<div style="white-space: pre-line;">
|
||||
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
|
||||
You have reached the maximum number of platforms for your plan in a project.
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Project paused" bind:open={show} size="l" dismissible={false}>
|
||||
<Modal title="Project paused" bind:open={show} size="m" dismissible={false}>
|
||||
<Layout.Stack gap="m">
|
||||
<Typography.Text>This project has been paused due to inactivity.</Typography.Text>
|
||||
<Typography.Text>
|
||||
|
||||
@@ -8,14 +8,25 @@
|
||||
import { parse } from '$lib/helpers/envfile';
|
||||
import { Icon, InlineCode, Layout, Tabs } from '@appwrite.io/pink-svelte';
|
||||
import { InputTextarea } from '$lib/elements/forms';
|
||||
import { IconDownload, IconDuplicate } from '@appwrite.io/pink-icons-svelte';
|
||||
import {
|
||||
IconChevronLeft,
|
||||
IconChevronRight,
|
||||
IconDownload,
|
||||
IconDuplicate
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let isGlobal: boolean;
|
||||
export let showEditor = false;
|
||||
export let variableList: Models.VariableList;
|
||||
|
||||
type DraftVariable = { key: string; value: string };
|
||||
|
||||
const EDITOR_PAGE_LIMIT = 10;
|
||||
const editableVariables = variableList.variables.filter((variable) => !variable.secret);
|
||||
const secretVariables = variableList.variables.filter((variable) => variable.secret);
|
||||
const initialDraftEntries = editableVariables.map((variable) => ({
|
||||
key: variable.key,
|
||||
value: variable.value
|
||||
}));
|
||||
|
||||
export let sdkCreateVariable: (
|
||||
key: string,
|
||||
@@ -31,39 +42,109 @@
|
||||
export let sdkDeleteVariable: (variableId: string) => Promise<unknown>;
|
||||
|
||||
let error = '';
|
||||
let envCode = editableVariables
|
||||
.map((variable) => `${variable.key}=${variable.value}`)
|
||||
.join('\n');
|
||||
let jsonCode = JSON.stringify(
|
||||
JSON.parse(
|
||||
`{${editableVariables
|
||||
.map((variable) => `"${variable.key}":"${variable.value.split('"').join('\\"')}"`)
|
||||
.join(',')}}`
|
||||
),
|
||||
null,
|
||||
2
|
||||
);
|
||||
let baseEnvCode = envCode;
|
||||
let baseJsonCode = jsonCode;
|
||||
let tab: 'env' | 'json' = 'env';
|
||||
let isSubmitting = false;
|
||||
let pageOffset = 0;
|
||||
let draftEntries = initialDraftEntries.map((variable) => ({ ...variable }));
|
||||
let editorCode = serializeEntries(tab, getPageEntries(draftEntries, pageOffset));
|
||||
|
||||
if (jsonCode === '{}') {
|
||||
jsonCode = '';
|
||||
baseJsonCode = '';
|
||||
function getPageEntries(entries: DraftVariable[], offset: number) {
|
||||
return entries.slice(offset, offset + EDITOR_PAGE_LIMIT);
|
||||
}
|
||||
|
||||
let tab: 'env' | 'json' = 'env';
|
||||
function serializeEntries(view: 'env' | 'json', entries: DraftVariable[]) {
|
||||
if (view === 'env') {
|
||||
return entries.map((variable) => `${variable.key}=${variable.value}`).join('\n');
|
||||
}
|
||||
|
||||
const jsonObject = Object.fromEntries(
|
||||
entries.map((variable) => [variable.key, variable.value])
|
||||
);
|
||||
|
||||
return Object.keys(jsonObject).length ? JSON.stringify(jsonObject, null, 2) : '';
|
||||
}
|
||||
|
||||
function parseEntries(code: string, view: 'env' | 'json') {
|
||||
const variables = view === 'env' ? parse(code) : JSON.parse(code || '{}');
|
||||
|
||||
return Object.entries(variables).map(([key, value]) => ({
|
||||
key,
|
||||
value: `${value}`
|
||||
}));
|
||||
}
|
||||
|
||||
function validateEntries(entries: DraftVariable[]) {
|
||||
for (const { key, value } of entries) {
|
||||
if (value.length > 8192) {
|
||||
throw new Error(`Variable ${key} is longer than 8192 allowed characters`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function replacePageEntries(
|
||||
entries: DraftVariable[],
|
||||
offset: number,
|
||||
pageEntries: DraftVariable[]
|
||||
) {
|
||||
return [
|
||||
...entries.slice(0, offset),
|
||||
...pageEntries,
|
||||
...entries.slice(offset + EDITOR_PAGE_LIMIT)
|
||||
];
|
||||
}
|
||||
|
||||
function buildDraftEntries() {
|
||||
const pageEntries = parseEntries(editorCode, tab);
|
||||
|
||||
validateEntries(pageEntries);
|
||||
|
||||
return replacePageEntries(draftEntries, pageOffset, pageEntries);
|
||||
}
|
||||
|
||||
function syncEditorCode() {
|
||||
editorCode = serializeEntries(tab, getPageEntries(draftEntries, pageOffset));
|
||||
}
|
||||
|
||||
function trySyncDraft() {
|
||||
try {
|
||||
draftEntries = buildDraftEntries();
|
||||
error = '';
|
||||
} catch {
|
||||
// Keep draft state intact while the user is mid-edit.
|
||||
}
|
||||
}
|
||||
|
||||
function changeTab(nextTab: 'env' | 'json') {
|
||||
try {
|
||||
draftEntries = buildDraftEntries();
|
||||
tab = nextTab;
|
||||
error = '';
|
||||
syncEditorCode();
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
function changePage(nextOffset: number) {
|
||||
try {
|
||||
draftEntries = buildDraftEntries();
|
||||
pageOffset = nextOffset;
|
||||
error = '';
|
||||
syncEditorCode();
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const vars = tab === 'env' ? parse(envCode) : JSON.parse(jsonCode ? jsonCode : '{}');
|
||||
isSubmitting = true;
|
||||
|
||||
const entries = Object.entries(vars);
|
||||
|
||||
for (const [key, value] of entries) {
|
||||
if (('' + value).length > 8192) {
|
||||
throw new Error(`Variable ${key} is longer than 8192 allowed characters`);
|
||||
}
|
||||
}
|
||||
const vars = Object.fromEntries(
|
||||
buildDraftEntries().map((variable) => [variable.key, variable.value])
|
||||
);
|
||||
const editableVariables = variableList.variables.filter((variable) => !variable.secret);
|
||||
const secretVariables = variableList.variables.filter((variable) => variable.secret);
|
||||
|
||||
await Promise.all(
|
||||
editableVariables.map(async (variable) => {
|
||||
@@ -81,9 +162,20 @@
|
||||
// Add new variables, skipping keys that exist in secret variables
|
||||
await Promise.all(
|
||||
Object.keys(vars).map(async (key) => {
|
||||
if (!secretVariables.some((v) => v.key === key)) {
|
||||
await sdkCreateVariable(key, vars[key], false);
|
||||
const existingVariable = variableList.variables.find(
|
||||
(variable) => variable.key === key
|
||||
);
|
||||
|
||||
if (existingVariable?.secret) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingVariable) {
|
||||
await sdkUpdateVariable(existingVariable.$id, key, vars[key], false);
|
||||
return;
|
||||
}
|
||||
|
||||
await sdkCreateVariable(key, vars[key], false);
|
||||
})
|
||||
);
|
||||
// Ensure secret variables are preserved
|
||||
@@ -103,11 +195,25 @@
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.VariableEditor);
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
function downloadVariables() {
|
||||
const content = tab === 'json' ? jsonCode : envCode;
|
||||
let content = '';
|
||||
|
||||
try {
|
||||
const nextDraftEntries = buildDraftEntries();
|
||||
|
||||
content = serializeEntries(tab, nextDraftEntries);
|
||||
draftEntries = nextDraftEntries;
|
||||
error = '';
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = tab === 'json' ? 'vars.json' : '.env';
|
||||
const type = tab === 'json' ? 'application/json' : 'application/x-envoy';
|
||||
|
||||
@@ -127,8 +233,19 @@
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
$: totalEntries = draftEntries.length;
|
||||
$: currentPageEntries = getPageEntries(draftEntries, pageOffset);
|
||||
$: renderedPageCode = serializeEntries(tab, currentPageEntries);
|
||||
$: hasUnsyncedChanges = editorCode !== renderedPageCode;
|
||||
$: copyValue = (() => {
|
||||
try {
|
||||
return serializeEntries(tab, buildDraftEntries());
|
||||
} catch {
|
||||
return editorCode;
|
||||
}
|
||||
})();
|
||||
$: isButtonDisabled =
|
||||
(tab === 'env' && baseEnvCode === envCode) || (tab === 'json' && baseJsonCode === jsonCode);
|
||||
!hasUnsyncedChanges && JSON.stringify(draftEntries) === JSON.stringify(initialDraftEntries);
|
||||
</script>
|
||||
|
||||
<Modal title="Editor" bind:show={showEditor} onSubmit={handleSubmit} bind:error>
|
||||
@@ -138,10 +255,10 @@
|
||||
</p>
|
||||
<Layout.Stack gap="s">
|
||||
<Tabs.Root stretch let:root>
|
||||
<Tabs.Item.Button {root} on:click={() => (tab = 'env')} active={tab === 'env'}>
|
||||
<Tabs.Item.Button {root} on:click={() => changeTab('env')} active={tab === 'env'}>
|
||||
ENV
|
||||
</Tabs.Item.Button>
|
||||
<Tabs.Item.Button {root} on:click={() => (tab = 'json')} active={tab === 'json'}>
|
||||
<Tabs.Item.Button {root} on:click={() => changeTab('json')} active={tab === 'json'}>
|
||||
JSON
|
||||
</Tabs.Item.Button>
|
||||
</Tabs.Root>
|
||||
@@ -151,23 +268,53 @@
|
||||
<InputTextarea
|
||||
spellcheck={false}
|
||||
id="variables"
|
||||
bind:value={envCode}
|
||||
bind:value={editorCode}
|
||||
on:input={trySyncDraft}
|
||||
rows={10}
|
||||
placeholder={`SECRET_KEY=dQw4w9WgXcQ...`} />
|
||||
{:else if tab === 'json'}
|
||||
<InputTextarea
|
||||
spellcheck={false}
|
||||
id="variables"
|
||||
bind:value={jsonCode}
|
||||
bind:value={editorCode}
|
||||
on:input={trySyncDraft}
|
||||
rows={10}
|
||||
placeholder={`{\n "SECRET_KEY": "dQw4w9WgXcQ..."\n}`} />
|
||||
{/if}
|
||||
{#if totalEntries > EDITOR_PAGE_LIMIT}
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<p class="text">
|
||||
Variables {pageOffset + 1}-{Math.min(
|
||||
pageOffset + EDITOR_PAGE_LIMIT,
|
||||
totalEntries
|
||||
)} of {totalEntries}
|
||||
</p>
|
||||
<Layout.Stack direction="row" gap="xs" inline>
|
||||
<Button
|
||||
size="xs"
|
||||
secondary
|
||||
disabled={pageOffset === 0}
|
||||
on:click={() => changePage(pageOffset - EDITOR_PAGE_LIMIT)}>
|
||||
<Icon size="s" slot="start" icon={IconChevronLeft} />
|
||||
Prev
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
secondary
|
||||
disabled={pageOffset + EDITOR_PAGE_LIMIT >= totalEntries}
|
||||
on:click={() => changePage(pageOffset + EDITOR_PAGE_LIMIT)}>
|
||||
Next
|
||||
<Icon size="s" slot="end" icon={IconChevronRight} />
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
<Layout.Stack direction="row" gap="xs">
|
||||
<Button size="xs" on:click={() => downloadVariables()} secondary>
|
||||
<Icon size="s" slot="start" icon={IconDownload} />
|
||||
Download
|
||||
</Button>
|
||||
<Copy value={tab == 'json' ? jsonCode : envCode}>
|
||||
<Copy value={copyValue}>
|
||||
<Button size="xs" secondary>
|
||||
<Icon size="s" slot="start" icon={IconDuplicate} />
|
||||
Copy
|
||||
@@ -178,7 +325,11 @@
|
||||
</Layout.Stack>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showEditor = false)}>Cancel</Button>
|
||||
<Button submit disabled={isButtonDisabled}>Save</Button>
|
||||
<Button secondary on:click={() => (showEditor = false)} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button submit disabled={isButtonDisabled || isSubmitting}>
|
||||
{isSubmitting ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -97,6 +97,9 @@
|
||||
{sdkDeleteVariable}
|
||||
isGlobal
|
||||
variableList={data.variables}
|
||||
backendPagination
|
||||
variablesOffset={data.variablesOffset}
|
||||
variablesLimit={data.limit}
|
||||
project={data.project}
|
||||
analyticsSource="project_settings" />
|
||||
<ChangeOrganization />
|
||||
|
||||
@@ -8,10 +8,13 @@ export const load: PageLoad = async ({ depends, url, params }) => {
|
||||
depends(Dependencies.PROJECT_INSTALLATIONS);
|
||||
const limit = PAGE_LIMIT;
|
||||
const offset = Number(url.searchParams.get('offset') ?? 0);
|
||||
|
||||
const variablesOffset = Number(url.searchParams.get('variablesOffset') ?? 0);
|
||||
const projectSdk = sdk.forProject(params.region, params.project);
|
||||
const [variables, installations] = await Promise.all([
|
||||
sdk.forProject(params.region, params.project).projectApi.listVariables(),
|
||||
sdk.forProject(params.region, params.project).vcs.listInstallations({
|
||||
projectSdk.projectApi.listVariables({
|
||||
queries: [Query.limit(limit), Query.offset(variablesOffset)]
|
||||
}),
|
||||
projectSdk.vcs.listInstallations({
|
||||
queries: [Query.limit(limit), Query.offset(offset)]
|
||||
})
|
||||
]);
|
||||
@@ -19,6 +22,7 @@ export const load: PageLoad = async ({ depends, url, params }) => {
|
||||
return {
|
||||
limit,
|
||||
offset,
|
||||
variablesOffset,
|
||||
variables,
|
||||
installations
|
||||
};
|
||||
|
||||
+15
-2
@@ -84,7 +84,7 @@
|
||||
<svelte:fragment slot="aside">
|
||||
{#if total > 0}
|
||||
<Layout.Stack gap="l">
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<div class="installations-action-row">
|
||||
<FormButton
|
||||
secondary
|
||||
href={configureGitHub()}
|
||||
@@ -94,7 +94,7 @@
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add installation
|
||||
</FormButton>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
|
||||
<Table.Root
|
||||
let:root
|
||||
@@ -233,3 +233,16 @@
|
||||
</Card.Base>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<style>
|
||||
.installations-action-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.installations-action-row {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+115
@@ -37,6 +37,12 @@
|
||||
$: imageTransformationsTotal = data.usage.imageTransformationsTotal;
|
||||
$: screenshotsGenerated = data.usage.screenshotsGenerated;
|
||||
$: screenshotsGeneratedTotal = data.usage.screenshotsGeneratedTotal;
|
||||
$: realtimeConnections = data.usage.realtimeConnections;
|
||||
$: realtimeConnectionsTotal = data.usage.realtimeConnectionsTotal;
|
||||
$: realtimeMessages = data.usage.realtimeMessages;
|
||||
$: realtimeMessagesTotal = data.usage.realtimeMessagesTotal;
|
||||
$: realtimeBandwidth = data.usage.realtimeBandwidth;
|
||||
$: realtimeBandwidthTotal = data.usage.realtimeBandwidthTotal;
|
||||
$: dbReads = data.usage.databasesReads;
|
||||
$: dbWrites = data.usage.databasesWrites;
|
||||
|
||||
@@ -444,6 +450,115 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Realtime connections</svelte:fragment>
|
||||
Peak concurrent realtime connections in your project.
|
||||
<svelte:fragment slot="aside">
|
||||
{#if realtimeConnections}
|
||||
{@const current = formatNum(realtimeConnectionsTotal)}
|
||||
<Layout.Stack gap="s" direction="row" alignItems="baseline">
|
||||
<Typography.Title>
|
||||
{current}
|
||||
</Typography.Title>
|
||||
<Typography.Text>Connections</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<BarChart
|
||||
options={{
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: formatNum
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'Realtime connections',
|
||||
data: [...realtimeConnections.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<Layout.Stack gap="xs" alignItems="center" justifyContent="center">
|
||||
<Icon icon={IconChartSquareBar} size="l" />
|
||||
<Typography.Text variant="m-600">No data to show</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Realtime messages</svelte:fragment>
|
||||
Total realtime messages sent to clients in your project.
|
||||
<svelte:fragment slot="aside">
|
||||
{#if realtimeMessages}
|
||||
{@const current = formatNum(realtimeMessagesTotal)}
|
||||
<Layout.Stack gap="s" direction="row" alignItems="baseline">
|
||||
<Typography.Title>
|
||||
{current}
|
||||
</Typography.Title>
|
||||
<Typography.Text>Messages</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<BarChart
|
||||
options={{
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: formatNum
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'Realtime messages',
|
||||
data: [...realtimeMessages.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<Layout.Stack gap="xs" alignItems="center" justifyContent="center">
|
||||
<Icon icon={IconChartSquareBar} size="l" />
|
||||
<Typography.Text variant="m-600">No data to show</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Realtime bandwidth</svelte:fragment>
|
||||
Total realtime bandwidth consumed in your project.
|
||||
<svelte:fragment slot="aside">
|
||||
{#if realtimeBandwidth}
|
||||
{@const currentHumanized = humanFileSize(realtimeBandwidthTotal)}
|
||||
<Layout.Stack gap="s" direction="row" alignItems="baseline">
|
||||
<Typography.Title>
|
||||
{currentHumanized.value}
|
||||
</Typography.Title>
|
||||
<Typography.Text>{currentHumanized.unit}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<BarChart
|
||||
options={{
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: (value) =>
|
||||
humanFileSize(value).value + humanFileSize(value).unit
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'Realtime bandwidth',
|
||||
data: [...realtimeBandwidth.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<Layout.Stack gap="xs" alignItems="center" justifyContent="center">
|
||||
<Icon icon={IconChartSquareBar} size="l" />
|
||||
<Typography.Text variant="m-600">No data to show</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Phone OTP</svelte:fragment>
|
||||
Calculated for all Phone OTP sent across your project. Resets at the start of each billing cycle.<br />
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { isServiceLimited } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -39,7 +43,7 @@
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<ViewSelector ui="new" {columns} view={View.Table} hideView />
|
||||
{#if $canWriteWebhooks}
|
||||
<Tooltip disabled={!isLimited}>
|
||||
<Tooltip disabled={!isLimited} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button disabled={isLimited} event="create_webhook" href={webhooksCreateUrl}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
@@ -47,7 +51,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<div style="white-space: pre-line;">
|
||||
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
|
||||
You have reached the maximum number of webhooks for your plan.
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
+5
-2
@@ -49,8 +49,11 @@
|
||||
`${base}/project-${page.params.region}-${page.params.project}/settings/webhooks/${webhook.$id}`
|
||||
);
|
||||
} catch (error) {
|
||||
trackError(error.message, Submit.DomainCreate);
|
||||
throw new Error(error.message);
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.WebhookCreate);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
label="POST URL"
|
||||
id="url"
|
||||
placeholder="https://example.com/callback"
|
||||
helper="Must be https (or http) and a public domain — localhost and private URLs are not accepted on Appwrite Cloud."
|
||||
bind:value={url}
|
||||
required />
|
||||
</Layout.Stack>
|
||||
|
||||
+6
-1
@@ -75,7 +75,12 @@
|
||||
type: VCSDetectionType.Framework,
|
||||
providerRootDirectory: rootDir
|
||||
});
|
||||
framework = data.frameworks.frameworks.find((f) => f.key === response.framework);
|
||||
framework = data.frameworks.frameworks.find(
|
||||
(f) => f.key === (response as unknown as Models.DetectionFramework).framework
|
||||
);
|
||||
if (!framework) {
|
||||
framework = data.frameworks.frameworks.find((f) => f.key === 'other');
|
||||
}
|
||||
adapter = framework?.adapters[0];
|
||||
installCommand = adapter?.installCommand;
|
||||
buildCommand = adapter?.buildCommand;
|
||||
|
||||
@@ -2,9 +2,13 @@ import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import Header from './header.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load = async ({ depends, params }) => {
|
||||
export const load = async ({ depends, params, parent }) => {
|
||||
depends(Dependencies.SITE);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'sites', params.site);
|
||||
|
||||
const [site] = await Promise.all([
|
||||
sdk.forProject(params.region, params.project).sites.get({ siteId: params.site })
|
||||
]);
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
import { realtime } from '$lib/stores/sdk';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
import { base } from '$app/paths';
|
||||
@@ -44,7 +48,9 @@
|
||||
Visit
|
||||
</Button>
|
||||
{/if}
|
||||
<Tooltip disabled={data.hasProdReadyDeployments}>
|
||||
<Tooltip
|
||||
disabled={data.hasProdReadyDeployments}
|
||||
maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button
|
||||
secondary
|
||||
@@ -53,10 +59,10 @@
|
||||
Instant rollback
|
||||
</Button>
|
||||
</div>
|
||||
<span slot="tooltip">
|
||||
<div slot="tooltip" style={BODY_TOOLTIP_WRAPPER_STYLE}>
|
||||
Rollback is possible only if there is a deployment that is ready and was
|
||||
active.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/snippet}
|
||||
</SiteCard>
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
import { isServiceLimited } from '$lib/stores/billing';
|
||||
import type { PageProps } from './$types';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import {
|
||||
BODY_TOOLTIP_MAX_WIDTH,
|
||||
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
|
||||
} from '$lib/helpers/tooltipContent';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
view={data.view}
|
||||
searchPlaceholder="Search by name or ID">
|
||||
{#if $canWriteBuckets}
|
||||
<Tooltip disabled={!isLimited}>
|
||||
<Tooltip disabled={!isLimited} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
|
||||
<div>
|
||||
<Button
|
||||
size="s"
|
||||
@@ -56,7 +60,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<div style="white-space: pre-line;">
|
||||
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
|
||||
You have reached the maximum number of buckets for your plan.
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -3,9 +3,12 @@ import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import Header from './header.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.BUCKET);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'buckets', params.bucket);
|
||||
|
||||
return {
|
||||
header: Header,
|
||||
|
||||
+4
-1
@@ -3,10 +3,13 @@ import Header from './header.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { guardResourceBlock } from '$lib/helpers/project';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.FILE);
|
||||
depends(Dependencies.FILE_TOKENS);
|
||||
const { project } = await parent();
|
||||
guardResourceBlock(project, 'files', params.file);
|
||||
|
||||
return {
|
||||
header: Header,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ID, type Models } from '@appwrite.io/console';
|
||||
import { ID, Query, type Models } from '@appwrite.io/console';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { CardGrid, Empty, Output, PaginationInline } from '$lib/components';
|
||||
import UploadVariables from './uploadVariablesModal.svelte';
|
||||
import { variablesOperation, type VariablesOperationItem } from './variablesOperation';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
@@ -39,9 +40,11 @@
|
||||
import SecretVariableModal from './secretVariableModal.svelte';
|
||||
import { Confirm } from '$lib/components';
|
||||
import { resolveRoute, withPath } from '$lib/stores/navigation';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
|
||||
export let project: Models.Project;
|
||||
export let variableList: Models.VariableList;
|
||||
export let allVariableList: Models.VariableList | undefined = undefined;
|
||||
export let globalVariableList: Models.VariableList | undefined = undefined;
|
||||
export let analyticsSource = '';
|
||||
export let isGlobal: boolean;
|
||||
@@ -58,6 +61,9 @@
|
||||
) => Promise<unknown>;
|
||||
export let sdkDeleteVariable: (variableId: string) => Promise<unknown>;
|
||||
export let product: 'function' | 'site' = 'function';
|
||||
export let backendPagination = false;
|
||||
export let variablesOffset = 0;
|
||||
export let variablesLimit = 10;
|
||||
|
||||
let selectedVar: Models.Variable = null;
|
||||
let showVariablesUpload = false;
|
||||
@@ -68,9 +74,45 @@
|
||||
let showSecretModal = false;
|
||||
let showDeleteModal = false;
|
||||
let deleteError: string;
|
||||
let fullVariableList: Models.VariableList | undefined = allVariableList;
|
||||
let previousVariableList = variableList;
|
||||
let offset = 0;
|
||||
const limit = 10;
|
||||
|
||||
async function loadAllVariables() {
|
||||
const projectSdk = sdk.forProject(page.params.region, page.params.project);
|
||||
const variables = [...variableList.variables];
|
||||
let nextOffset = variables.length;
|
||||
let total = variableList.total;
|
||||
|
||||
while (nextOffset < total) {
|
||||
const response = await projectSdk.projectApi.listVariables({
|
||||
queries: [Query.limit(variablesLimit), Query.offset(nextOffset)]
|
||||
});
|
||||
|
||||
total = response.total;
|
||||
|
||||
if (response.variables.length === 0) break;
|
||||
|
||||
variables.push(...response.variables);
|
||||
nextOffset += response.variables.length;
|
||||
}
|
||||
|
||||
return {
|
||||
total,
|
||||
variables
|
||||
};
|
||||
}
|
||||
|
||||
async function ensureAllVariablesLoaded() {
|
||||
if (fullVariableList && fullVariableList.total === variableList.total) return;
|
||||
|
||||
fullVariableList = await loadAllVariables();
|
||||
}
|
||||
function handleVariablesImportStatus(detail: VariablesOperationItem) {
|
||||
variablesOperation.set(detail);
|
||||
}
|
||||
|
||||
async function handleVariableCreated(event: CustomEvent<Models.Variable[]>) {
|
||||
const variables = event.detail;
|
||||
try {
|
||||
@@ -78,6 +120,7 @@
|
||||
sdkCreateVariable(variable.key, variable.value, variable?.secret || false)
|
||||
);
|
||||
await Promise.all(promises);
|
||||
fullVariableList = undefined;
|
||||
showVariablesModal = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -99,6 +142,7 @@
|
||||
const variable = event.detail;
|
||||
try {
|
||||
await sdkUpdateVariable(variable.$id, variable.key, variable.value, variable.secret);
|
||||
fullVariableList = undefined;
|
||||
selectedVar = null;
|
||||
showVariablesModal = false;
|
||||
addNotification({
|
||||
@@ -120,6 +164,7 @@
|
||||
const variable = event.detail;
|
||||
try {
|
||||
await sdkUpdateVariable(variable.$id, variable.key, variable.value, variable.secret);
|
||||
fullVariableList = undefined;
|
||||
selectedVar = null;
|
||||
showVariablesModal = false;
|
||||
addNotification({
|
||||
@@ -139,10 +184,26 @@
|
||||
}
|
||||
|
||||
async function handleVariableDeleted() {
|
||||
const deleteId = selectedVar.$id;
|
||||
|
||||
try {
|
||||
variablesOperation.set({
|
||||
id: deleteId,
|
||||
count: 1,
|
||||
mode: 'delete',
|
||||
status: 'deleting'
|
||||
});
|
||||
|
||||
await sdkDeleteVariable(selectedVar.$id);
|
||||
fullVariableList = undefined;
|
||||
showDeleteModal = false;
|
||||
selectedVar = null;
|
||||
variablesOperation.set({
|
||||
id: deleteId,
|
||||
count: 1,
|
||||
mode: 'delete',
|
||||
status: 'completed'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${project.name} ${
|
||||
@@ -152,6 +213,13 @@
|
||||
trackEvent(Submit.VariableDelete);
|
||||
} catch (error) {
|
||||
deleteError = error.message;
|
||||
variablesOperation.set({
|
||||
id: deleteId,
|
||||
count: 1,
|
||||
mode: 'delete',
|
||||
status: 'failed',
|
||||
error: error.message
|
||||
});
|
||||
trackError(error, Submit.VariableDelete);
|
||||
}
|
||||
}
|
||||
@@ -220,6 +288,7 @@
|
||||
|
||||
selectedVar = null;
|
||||
showPromoteModal = false;
|
||||
fullVariableList = undefined;
|
||||
|
||||
await Promise.all([
|
||||
invalidate(Dependencies.FUNCTION),
|
||||
@@ -259,13 +328,54 @@
|
||||
})
|
||||
: [];
|
||||
|
||||
$: if (allVariableList && fullVariableList !== allVariableList) {
|
||||
fullVariableList = allVariableList;
|
||||
}
|
||||
|
||||
$: if (variableList !== previousVariableList) {
|
||||
fullVariableList = undefined;
|
||||
previousVariableList = variableList;
|
||||
}
|
||||
|
||||
$: if (fullVariableList && fullVariableList.total !== variableList.total) {
|
||||
fullVariableList = undefined;
|
||||
}
|
||||
|
||||
$: editorVariableList = fullVariableList ?? allVariableList ?? variableList;
|
||||
$: displayedVariables = backendPagination
|
||||
? variableList.variables
|
||||
: variableList.variables.slice(offset, offset + limit);
|
||||
|
||||
$: hasConflictOnPage = globalVariableList
|
||||
? variableList.variables.slice(offset, offset + limit).filter((variable) => {
|
||||
? displayedVariables.filter((variable) => {
|
||||
return globalVariableList.variables.find((globalVariable) => {
|
||||
return variable.key === globalVariable.key;
|
||||
});
|
||||
})
|
||||
: false;
|
||||
|
||||
$: variableColumns = $isSmallViewport
|
||||
? [
|
||||
{ id: 'key', width: { min: 380, max: 520 } },
|
||||
{ id: 'value', width: { min: 200, max: 320 } },
|
||||
{ id: 'actions', width: 50 }
|
||||
]
|
||||
: [
|
||||
{ id: 'key', width: { min: 280, max: 420 } },
|
||||
{ id: 'value', width: { min: 200, max: 400 } },
|
||||
{ id: 'actions', width: 50 }
|
||||
];
|
||||
|
||||
async function handleVariablesPageChange() {
|
||||
const nextUrl = new URL(page.url);
|
||||
|
||||
nextUrl.searchParams.set('variablesOffset', String(variablesOffset));
|
||||
|
||||
await goto(nextUrl, {
|
||||
keepFocus: true,
|
||||
noScroll: true
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
@@ -286,11 +396,12 @@
|
||||
{/if}
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="l">
|
||||
<Layout.Stack direction="row">
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Layout.Stack direction="row" gap="s" wrap={$isSmallViewport ? 'wrap' : 'nowrap'}>
|
||||
<Layout.Stack direction="row" gap="s" wrap={$isSmallViewport ? 'wrap' : 'nowrap'}>
|
||||
<Button
|
||||
secondary
|
||||
on:mousedown={() => {
|
||||
on:mousedown={async () => {
|
||||
await ensureAllVariablesLoaded();
|
||||
showEditorModal = true;
|
||||
trackEvent(Click.VariablesUpdateClick, { source: analyticsSource });
|
||||
}}>
|
||||
@@ -298,7 +409,8 @@
|
||||
</Button>
|
||||
<Button
|
||||
secondary
|
||||
on:mousedown={() => {
|
||||
on:mousedown={async () => {
|
||||
await ensureAllVariablesLoaded();
|
||||
showVariablesUpload = true;
|
||||
trackEvent(Click.VariablesUpdateClick, { source: analyticsSource });
|
||||
}}>
|
||||
@@ -341,19 +453,13 @@
|
||||
</p>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<Table.Root
|
||||
columns={[
|
||||
{ id: 'key', width: { min: 200, max: 400 } },
|
||||
{ id: 'value', width: { min: 200, max: 400 } },
|
||||
{ id: 'actions', width: 50 }
|
||||
]}
|
||||
let:root>
|
||||
<Table.Root class="responsive-table" columns={variableColumns} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
|
||||
<Table.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
{#each variableList.variables.slice(offset, offset + limit) as variable}
|
||||
{#each displayedVariables as variable}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="key" {root}>
|
||||
{@const isConflicting = globalVariableList
|
||||
@@ -362,7 +468,11 @@
|
||||
) !== undefined
|
||||
: false}
|
||||
|
||||
<Layout.Stack gap="xxs" alignItems="center" direction="row">
|
||||
<Layout.Stack
|
||||
gap="xxs"
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
class="variable-key-cell">
|
||||
{#if isConflicting && hasConflictOnPage}
|
||||
<span
|
||||
class="icon-exclamation u-color-text-warning"
|
||||
@@ -446,10 +556,19 @@
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{#if sum > limit}
|
||||
{#if sum > (backendPagination ? variablesLimit : limit)}
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<p class="text">Total variables: {sum}</p>
|
||||
<PaginationInline total={sum} {limit} bind:offset hidePages />
|
||||
{#if backendPagination}
|
||||
<PaginationInline
|
||||
total={sum}
|
||||
limit={variablesLimit}
|
||||
bind:offset={variablesOffset}
|
||||
hidePages
|
||||
on:change={handleVariablesPageChange} />
|
||||
{:else}
|
||||
<PaginationInline total={sum} {limit} bind:offset hidePages />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
@@ -489,7 +608,7 @@
|
||||
{sdkCreateVariable}
|
||||
{sdkUpdateVariable}
|
||||
{sdkDeleteVariable}
|
||||
{variableList}
|
||||
variableList={editorVariableList}
|
||||
bind:showEditor={showEditorModal} />
|
||||
{/if}
|
||||
|
||||
@@ -509,8 +628,9 @@
|
||||
<UploadVariables
|
||||
{sdkCreateVariable}
|
||||
{sdkUpdateVariable}
|
||||
{variableList}
|
||||
bind:show={showVariablesUpload} />
|
||||
variableList={editorVariableList}
|
||||
bind:show={showVariablesUpload}
|
||||
onStatusChange={handleVariablesImportStatus} />
|
||||
{/if}
|
||||
|
||||
{#if showDeleteModal}
|
||||
@@ -522,3 +642,16 @@
|
||||
<p>Are you sure you want to delete this variable? This action is irreversible.</p>
|
||||
</Confirm>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
:global(.variable-key-cell) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:global(.variable-key-cell > :last-child) {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user