mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' into fix-SER-SER-416-More-Messaging-flow-issues
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
name: Copilot Setup Steps
|
||||
|
||||
# Automatically run the setup steps when they are changed to allow for easy validation, and
|
||||
# allow manual testing through the repository's "Actions" tab
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Dockerize Profiles
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [feat-profiles]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: [feat-profiles]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
dockerize-profiles:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: appwrite/console-profiles
|
||||
tags: |
|
||||
type=ref,event=branch,prefix=branch-
|
||||
type=ref,event=pr
|
||||
type=sha,prefix=sha-
|
||||
type=raw,value=gh-${{ github.run_id}}
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -12,16 +12,31 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: E2E Tests
|
||||
run: pnpm run e2e
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
"PUBLIC_CONSOLE_MODE=cloud"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS="
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=true"
|
||||
"PUBLIC_CONSOLE_EMAIL_VERIFICATION=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 }}"
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
build-args: |
|
||||
"PUBLIC_CONSOLE_MODE=cloud"
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=false"
|
||||
"PUBLIC_CONSOLE_EMAIL_VERIFICATION=true"
|
||||
"PUBLIC_CONSOLE_EMAIL_VERIFICATION=false"
|
||||
"PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=false"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS="
|
||||
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}"
|
||||
|
||||
@@ -15,21 +15,29 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Audit dependencies
|
||||
run: pnpm audit --audit-level high
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Svelte Diagnostics
|
||||
run: pnpm run check
|
||||
|
||||
- name: Linter
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Unit Tests
|
||||
run: pnpm run test
|
||||
|
||||
- name: Build Console
|
||||
run: pnpm run build
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# Appwrite Console - Copilot Instructions
|
||||
|
||||
## Repository Overview
|
||||
|
||||
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: **pnpm 10.15.1**, Node 20+. ~1500 files with extensive component-based architecture.
|
||||
|
||||
## Critical Build & Test Commands
|
||||
|
||||
### Setup (REQUIRED before any commands)
|
||||
|
||||
1. **Install pnpm**: `npm install -g corepack && corepack enable && corepack prepare pnpm@10.15.1 --activate`
|
||||
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: Use `pnpm/action-setup@v4` which handles registry configuration
|
||||
- If network errors persist, check proxy settings: `npm config get proxy` and `npm config get https-proxy`
|
||||
4. **Install dependencies**: `pnpm install --frozen-lockfile` (if pkg.pr.new/pkg.vc fail due to network restrictions, installation may still succeed with cached versions)
|
||||
|
||||
### Development Commands
|
||||
|
||||
**Standard workflow**: `check` → `lint` → `test` → `build` (before committing)
|
||||
|
||||
- `pnpm run check` - TypeScript/Svelte validation (~30-60s)
|
||||
- `pnpm run lint` - ESLint check (~10-20s)
|
||||
- `pnpm run format` - Auto-fix Prettier formatting
|
||||
- `pnpm run test` - Vitest unit tests with TZ=EST (~10-30s)
|
||||
- `pnpm run build` - Production build via build.js (~60-120s)
|
||||
- `pnpm dev` - Dev server on port 3000
|
||||
- `pnpm run preview` - Preview build on port 4173
|
||||
- `pnpm run e2e` - Playwright tests (needs `pnpm exec playwright install --with-deps chromium` first, ~120s+)
|
||||
|
||||
**CI Pipeline** (`.github/workflows/tests.yml`): audit → install → check → lint → test → build
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
**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]`.
|
||||
|
||||
## Key Configuration
|
||||
|
||||
**svelte.config.js**: Adapter = static SPA (fallback: index.html), base path `/console`, aliases: `$lib`, `$routes`, `$themes`
|
||||
**vite.config.ts**: Dev port 3000, Vitest (client=jsdom, server=node), test files: `src/**/*.{test,spec}.{js,ts}`
|
||||
**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
|
||||
|
||||
## Testing
|
||||
|
||||
**Unit (Vitest)**: Tests in `src/lib/helpers/*.test.ts`, run with `TZ=EST` (timezone matters). Setup mocks SvelteKit (`$app/*`) in `vitest-setup-client.ts`.
|
||||
**E2E (Playwright)**: Tests in `e2e/journeys/*.spec.ts`, needs build+preview on port 4173, retries 3x, timeout 120s, Chromium only.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
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; use `pnpm/action-setup@v4` action
|
||||
- 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 `pnpm run test` (sets TZ=EST), not `vitest` directly
|
||||
5. **TS errors not showing**: Run `pnpm run check` explicitly (dev server doesn't always surface them)
|
||||
6. **Format vs lint conflicts**: Run `pnpm run format` before `pnpm 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 && pnpm run build`
|
||||
|
||||
## Code Conventions
|
||||
|
||||
- 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
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Run Appwrite backend locally (see [docs](https://appwrite.io/docs/advanced/self-hosting))
|
||||
2. Configure `.env` with backend endpoint
|
||||
3. `pnpm install --frozen-lockfile`
|
||||
4. `pnpm dev` (hot reload on port 3000)
|
||||
5. Before commit: `pnpm run check && pnpm run format && pnpm run lint && pnpm run test && pnpm run build`
|
||||
6. **Take screenshots**: For any UI changes, capture screenshots and include them in the PR description or comments before finalizing
|
||||
|
||||
## Required Pre-Completion Checklist
|
||||
|
||||
**CRITICAL**: Before finishing any work or marking a task complete, agents MUST run the following commands in order and ensure all pass:
|
||||
|
||||
1. **`pnpm run format`** - Auto-fix all formatting issues
|
||||
2. **`pnpm run check`** - Verify TypeScript/Svelte types (must show 0 errors, 0 warnings)
|
||||
3. **`pnpm run lint`** - Check code style (ignore pre-existing issues in files you didn't modify)
|
||||
4. **`pnpm run test`** - Run all unit tests (all tests must pass)
|
||||
5. **`pnpm run build`** - Ensure production build succeeds
|
||||
|
||||
If any command fails:
|
||||
|
||||
- **Format/Lint**: Run `pnpm 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
|
||||
|
||||
**Never skip these checks** - they are mandatory quality gates before any work is considered complete.
|
||||
|
||||
**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.
|
||||
@@ -17,10 +17,22 @@ export async function createFreeProject(page: Page): Promise<Metadata> {
|
||||
await page.waitForURL(/\/organization-[^/]+/);
|
||||
await page.getByRole('button', { name: 'create project' }).first().click();
|
||||
const dialog = page.locator('dialog[open]');
|
||||
|
||||
await dialog.getByPlaceholder('Project name').fill('test project');
|
||||
|
||||
let region = 'fra'; // for fallback
|
||||
const regionPicker = dialog.locator('button[role="combobox"]');
|
||||
if (await regionPicker.isVisible()) {
|
||||
await regionPicker.click();
|
||||
await page.getByRole('option', { name: /New York/i }).click();
|
||||
|
||||
region = 'nyc';
|
||||
}
|
||||
|
||||
await dialog.getByRole('button', { name: 'create' }).click();
|
||||
await page.waitForURL(/\/project-fra-[^/]+/);
|
||||
expect(page.url()).toContain('/console/project-fra-');
|
||||
|
||||
await page.waitForURL(new RegExp(`/project-${region}-[^/]+`));
|
||||
expect(page.url()).toContain(`/console/project-${region}-`);
|
||||
|
||||
return getProjectIdFromUrl(page.url());
|
||||
});
|
||||
|
||||
@@ -50,10 +50,21 @@ export async function createProProject(page: Page): Promise<Metadata> {
|
||||
await page.waitForURL(/\/organization-[^/]+/);
|
||||
await page.getByRole('button', { name: 'create project' }).first().click();
|
||||
const dialog = page.locator('dialog[open]');
|
||||
|
||||
await dialog.getByPlaceholder('Project name').fill('test project');
|
||||
|
||||
let region = 'fra'; // for fallback
|
||||
const regionPicker = dialog.locator('button[role="combobox"]');
|
||||
if (await regionPicker.isVisible()) {
|
||||
await regionPicker.click();
|
||||
await page.getByRole('option', { name: /New York/i }).click();
|
||||
|
||||
region = 'nyc';
|
||||
}
|
||||
|
||||
await dialog.getByRole('button', { name: 'create' }).click();
|
||||
await page.waitForURL(/\/project-fra-[^/]+/);
|
||||
expect(page.url()).toContain('/console/project-fra-');
|
||||
await page.waitForURL(new RegExp(`/project-${region}-[^/]+`));
|
||||
expect(page.url()).toContain(`/console/project-${region}-`);
|
||||
|
||||
return getProjectIdFromUrl(page.url());
|
||||
});
|
||||
|
||||
+13
-9
@@ -2,7 +2,7 @@
|
||||
"name": "@appwrite/console",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -12,7 +12,7 @@
|
||||
"clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i --force",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"format": "prettier --write --cache .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test": "TZ=EST vitest run",
|
||||
"test:ui": "TZ=EST vitest --ui",
|
||||
@@ -22,15 +22,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f",
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@plausible-analytics/tracker": "^0.4.4",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^8.38.0",
|
||||
"@stripe/stripe-js": "^3.5.0",
|
||||
"@threlte/core": "^8.3.1",
|
||||
"@threlte/extras": "^9.7.1",
|
||||
"ai": "^2.2.37",
|
||||
"analytics": "^0.8.16",
|
||||
"cron-parser": "^4.9.0",
|
||||
@@ -40,11 +43,11 @@
|
||||
"ignore": "^6.0.2",
|
||||
"nanoid": "^5.1.5",
|
||||
"nanotar": "^0.1.1",
|
||||
"plausible-tracker": "^0.3.9",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"prismjs": "^1.30.0",
|
||||
"remarkable": "^2.0.1",
|
||||
"svelte-confetti": "^1.4.0",
|
||||
"three": "^0.181.2",
|
||||
"tippy.js": "^6.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -52,9 +55,9 @@
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.86.5",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@playwright/test": "^1.55.1",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.42.1",
|
||||
"@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",
|
||||
@@ -63,6 +66,7 @@
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@types/three": "^0.182.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
@@ -95,5 +99,5 @@
|
||||
"svelte-preprocess"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@10.15.1"
|
||||
"packageManager": "pnpm@10.20.0"
|
||||
}
|
||||
|
||||
Generated
+321
-81
@@ -12,32 +12,41 @@ importers:
|
||||
specifier: ^1.1.24
|
||||
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39
|
||||
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755
|
||||
'@appwrite.io/pink-icons':
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
'@appwrite.io/pink-icons-svelte':
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4(svelte@5.25.3)
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f(svelte@5.25.3)
|
||||
'@appwrite.io/pink-legacy':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
'@appwrite.io/pink-svelte':
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4(svelte@5.25.3)
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f(svelte@5.25.3)
|
||||
'@faker-js/faker':
|
||||
specifier: ^9.9.0
|
||||
version: 9.9.0
|
||||
'@plausible-analytics/tracker':
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
'@sentry/sveltekit':
|
||||
specifier: ^8.38.0
|
||||
version: 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
version: 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@stripe/stripe-js':
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
'@threlte/core':
|
||||
specifier: ^8.3.1
|
||||
version: 8.3.1(svelte@5.25.3)(three@0.181.2)
|
||||
'@threlte/extras':
|
||||
specifier: ^9.7.1
|
||||
version: 9.7.1(@types/three@0.182.0)(svelte@5.25.3)(three@0.181.2)
|
||||
ai:
|
||||
specifier: ^2.2.37
|
||||
version: 2.2.37(react@18.3.1)(solid-js@1.9.5)(svelte@5.25.3)(vue@3.5.13(typescript@5.8.2))
|
||||
@@ -65,9 +74,6 @@ importers:
|
||||
nanotar:
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1
|
||||
plausible-tracker:
|
||||
specifier: ^0.3.9
|
||||
version: 0.3.9
|
||||
pretty-bytes:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1
|
||||
@@ -80,6 +86,9 @@ importers:
|
||||
svelte-confetti:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0(svelte@5.25.3)
|
||||
three:
|
||||
specifier: ^0.181.2
|
||||
version: 0.181.2
|
||||
tippy.js:
|
||||
specifier: ^6.3.7
|
||||
version: 6.3.7
|
||||
@@ -97,14 +106,14 @@ importers:
|
||||
specifier: ^0.86.5
|
||||
version: 0.86.5(svelte@5.25.3)
|
||||
'@playwright/test':
|
||||
specifier: ^1.51.1
|
||||
version: 1.51.1
|
||||
specifier: ^1.55.1
|
||||
version: 1.56.1
|
||||
'@sveltejs/adapter-static':
|
||||
specifier: ^3.0.8
|
||||
version: 3.0.8(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))
|
||||
version: 3.0.8(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))
|
||||
'@sveltejs/kit':
|
||||
specifier: ^2.42.1
|
||||
version: 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
specifier: ^2.49.5
|
||||
version: 2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
@@ -129,6 +138,9 @@ importers:
|
||||
'@types/remarkable':
|
||||
specifier: ^2.0.8
|
||||
version: 2.0.8
|
||||
'@types/three':
|
||||
specifier: ^0.182.0
|
||||
version: 0.182.0
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^8.28.0
|
||||
version: 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.31.0)(typescript@5.8.2))(eslint@9.31.0)(typescript@5.8.2)
|
||||
@@ -260,17 +272,17 @@ packages:
|
||||
'@analytics/type-utils@0.6.2':
|
||||
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39}
|
||||
version: 1.10.0
|
||||
'@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755}
|
||||
version: 2.1.0
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
|
||||
resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4}
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f}
|
||||
version: 2.0.0-RC.1
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -284,8 +296,8 @@ packages:
|
||||
'@appwrite.io/pink-legacy@1.0.3':
|
||||
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4}
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f}
|
||||
version: 2.0.0-RC.2
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -335,8 +347,8 @@ packages:
|
||||
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.27.1':
|
||||
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
||||
'@babel/helper-validator-identifier@7.28.5':
|
||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-option@7.25.9':
|
||||
@@ -352,8 +364,8 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.28.4':
|
||||
resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
|
||||
'@babel/parser@7.28.6':
|
||||
resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -373,8 +385,8 @@ packages:
|
||||
resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.28.4':
|
||||
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
|
||||
'@babel/types@7.28.6':
|
||||
resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@csstools/color-helpers@5.0.2':
|
||||
@@ -405,6 +417,9 @@ packages:
|
||||
resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@dimforge/rapier3d-compat@0.12.0':
|
||||
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.1':
|
||||
resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -993,8 +1008,11 @@ packages:
|
||||
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@playwright/test@1.51.1':
|
||||
resolution: {integrity: sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==}
|
||||
'@plausible-analytics/tracker@0.4.4':
|
||||
resolution: {integrity: sha512-fz0NOYUEYXtg1TBaPEEvtcBq3FfmLFuTe1VZw4M8sTWX129br5dguu3M15+plOQnc181ShYe67RfwhKgK89VnA==}
|
||||
|
||||
'@playwright/test@1.56.1':
|
||||
resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1264,18 +1282,21 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^2.0.0
|
||||
|
||||
'@sveltejs/kit@2.42.1':
|
||||
resolution: {integrity: sha512-SoWdb/OxEKHMlXZ78eNZa4pR1YdOGnUfcFj5NylJv+ZaTrnHK5xapw+7RJP2MiS1D4T+sEH25/tmMtmEl8p6VA==}
|
||||
'@sveltejs/kit@2.49.5':
|
||||
resolution: {integrity: sha512-dCYqelr2RVnWUuxc+Dk/dB/SjV/8JBndp1UovCyCZdIQezd8TRwFLNZctYkzgHxRJtaNvseCSRsuuHPeUgIN/A==}
|
||||
engines: {node: '>=18.13'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
'@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0
|
||||
svelte: ^4.0.0 || ^5.0.0-next.0
|
||||
typescript: ^5.3.3
|
||||
vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0
|
||||
peerDependenciesMeta:
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1':
|
||||
resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==}
|
||||
@@ -1330,6 +1351,26 @@ packages:
|
||||
peerDependencies:
|
||||
'@testing-library/dom': '>=7.21.4'
|
||||
|
||||
'@threejs-kit/instanced-sprite-mesh@2.5.1':
|
||||
resolution: {integrity: sha512-pmt1ALRhbHhCJQTj2FuthH6PeLIeaM4hOuS2JO3kWSwlnvx/9xuUkjFR3JOi/myMqsH7pSsLIROSaBxDfttjeA==}
|
||||
peerDependencies:
|
||||
three: '>=0.170.0'
|
||||
|
||||
'@threlte/core@8.3.1':
|
||||
resolution: {integrity: sha512-qKjjNCQ+40hyeBcfOMh/8ef5x/j5PG5Wmo/L9Ye0aDCcdD6fCewWxfp7tV/J3CxPzX1dEp1JGK7sjyc7ntZSrg==}
|
||||
peerDependencies:
|
||||
svelte: '>=5'
|
||||
three: '>=0.160'
|
||||
|
||||
'@threlte/extras@9.7.1':
|
||||
resolution: {integrity: sha512-SGm59HDCdHxADFHuweHfFDknwubkCPodyK0pbfsVtOWWOX26gE2xfK7aKolh6YFDiPAjWjGxN0jIgkNbbr1ohg==}
|
||||
peerDependencies:
|
||||
svelte: '>=5'
|
||||
three: '>=0.160'
|
||||
|
||||
'@tweenjs/tween.js@23.1.3':
|
||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||
|
||||
'@types/aria-query@5.0.4':
|
||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||
|
||||
@@ -1393,12 +1434,21 @@ packages:
|
||||
'@types/shimmer@1.2.0':
|
||||
resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==}
|
||||
|
||||
'@types/stats.js@0.17.4':
|
||||
resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==}
|
||||
|
||||
'@types/tedious@4.0.14':
|
||||
resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==}
|
||||
|
||||
'@types/three@0.182.0':
|
||||
resolution: {integrity: sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==}
|
||||
|
||||
'@types/unist@3.0.3':
|
||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||
|
||||
'@types/webxr@0.5.24':
|
||||
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.28.0':
|
||||
resolution: {integrity: sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -1559,6 +1609,9 @@ packages:
|
||||
'@vue/shared@3.5.13':
|
||||
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
||||
|
||||
'@webgpu/types@0.1.67':
|
||||
resolution: {integrity: sha512-uk53+2ECGUkWoDFez/hymwpRfdgdIn6y1ref70fEecGMe5607f4sozNFgBk0oxlr7j2CRGWBEc3IBYMmFdGGTQ==}
|
||||
|
||||
acorn-import-attributes@1.9.5:
|
||||
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
|
||||
peerDependencies:
|
||||
@@ -1674,6 +1727,9 @@ packages:
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
bidi-js@1.0.3:
|
||||
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1713,6 +1769,12 @@ packages:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
camera-controls@3.1.2:
|
||||
resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==}
|
||||
engines: {node: '>=22.0.0', npm: '>=10.5.1'}
|
||||
peerDependencies:
|
||||
three: '>=0.126.1'
|
||||
|
||||
caniuse-lite@1.0.30001707:
|
||||
resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==}
|
||||
|
||||
@@ -1826,6 +1888,9 @@ packages:
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
d3-array@3.2.4:
|
||||
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -2016,12 +2081,15 @@ packages:
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
devalue@5.3.2:
|
||||
resolution: {integrity: sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==}
|
||||
devalue@5.6.2:
|
||||
resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==}
|
||||
|
||||
devlop@1.1.0:
|
||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||
|
||||
diet-sprite@0.0.1:
|
||||
resolution: {integrity: sha512-zSHI2WDAn1wJqJYxcmjWfJv3Iw8oL9reQIbEyx2x2/EZ4/qmUTIo8/5qOCurnAcq61EwtJJaZ0XTy2NRYqpB5A==}
|
||||
|
||||
dlv@1.1.3:
|
||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||
|
||||
@@ -2039,6 +2107,9 @@ packages:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
earcut@2.2.4:
|
||||
resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==}
|
||||
|
||||
echarts@5.6.0:
|
||||
resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
|
||||
|
||||
@@ -2601,14 +2672,20 @@ packages:
|
||||
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||
hasBin: true
|
||||
|
||||
maath@0.10.8:
|
||||
resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==}
|
||||
peerDependencies:
|
||||
'@types/three': '>=0.134.0'
|
||||
three: '>=0.134.0'
|
||||
|
||||
magic-string@0.30.17:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
magic-string@0.30.18:
|
||||
resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==}
|
||||
|
||||
magic-string@0.30.19:
|
||||
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
magic-string@0.30.7:
|
||||
resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==}
|
||||
@@ -2635,6 +2712,9 @@ packages:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
meshoptimizer@0.22.0:
|
||||
resolution: {integrity: sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==}
|
||||
|
||||
micromark-util-character@2.1.1:
|
||||
resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
|
||||
|
||||
@@ -2680,6 +2760,9 @@ packages:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
module-details-from-path@1.0.3:
|
||||
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
|
||||
|
||||
@@ -2826,17 +2909,13 @@ packages:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
plausible-tracker@0.3.9:
|
||||
resolution: {integrity: sha512-hMhneYm3GCPyQon88SZrVJx+LlqhM1kZFQbuAgXPoh/Az2YvO1B6bitT9qlhpiTdJlsT5lsr3gPmzoVjb5CDXA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
playwright-core@1.51.1:
|
||||
resolution: {integrity: sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==}
|
||||
playwright-core@1.56.1:
|
||||
resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.51.1:
|
||||
resolution: {integrity: sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==}
|
||||
playwright@1.56.1:
|
||||
resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2987,6 +3066,10 @@ packages:
|
||||
engines: {node: '>= 6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-in-the-middle@7.5.2:
|
||||
resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
@@ -3053,14 +3136,14 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
seroval-plugins@1.3.3:
|
||||
resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==}
|
||||
seroval-plugins@1.4.2:
|
||||
resolution: {integrity: sha512-X7p4MEDTi+60o2sXZ4bnDBhgsUYDSkQEvzYZuJyFqWg9jcoPsHts5nrg5O956py2wyt28lUrBxk0M0/wU8URpA==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
seroval: ^1.0
|
||||
|
||||
seroval@1.3.2:
|
||||
resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==}
|
||||
seroval@1.4.2:
|
||||
resolution: {integrity: sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
set-cookie-parser@2.7.1:
|
||||
@@ -3286,6 +3369,29 @@ packages:
|
||||
the-new-css-reset@1.11.3:
|
||||
resolution: {integrity: sha512-61SB81vu9foUyEIqoU1CeqxrdlsVjJojj/CBXoG8BdvlKFsllB0Rza63DblnRqH+3uttPj3FGWo7+c9nu7MT+A==}
|
||||
|
||||
three-instanced-uniforms-mesh@0.52.4:
|
||||
resolution: {integrity: sha512-YwDBy05hfKZQtU+Rp0KyDf9yH4GxfhxMbVt9OYruxdgLfPwmDG5oAbGoW0DrKtKZSM3BfFcCiejiOHCjFBTeng==}
|
||||
peerDependencies:
|
||||
three: '>=0.125.0'
|
||||
|
||||
three-mesh-bvh@0.9.3:
|
||||
resolution: {integrity: sha512-LaxfvQpF+At96fS90GnQxHpff9bu78UL5eooJNxYyBbyiWrOBpjRx+5yn/+Dj2lQVhz5A/jHqwpVchYYnz/hWQ==}
|
||||
peerDependencies:
|
||||
three: '>= 0.159.0'
|
||||
|
||||
three-perf@1.0.11:
|
||||
resolution: {integrity: sha512-OgBpZjwL+csQKGKZjpkH/QHdbGFMxqngMbSEJeSnVNfXDYd6On7WHNv/GhUZH4YxIpNMwMahBWrNnsJvnbSJHQ==}
|
||||
peerDependencies:
|
||||
three: '>=0.170'
|
||||
|
||||
three-viewport-gizmo@2.2.0:
|
||||
resolution: {integrity: sha512-Jo9Liur1rUmdKk75FZumLU/+hbF+RtJHi1qsKZpntjKlCYScK6tjbYoqvJ9M+IJphrlQJF5oReFW7Sambh0N4Q==}
|
||||
peerDependencies:
|
||||
three: '>=0.162.0 <1.0.0'
|
||||
|
||||
three@0.181.2:
|
||||
resolution: {integrity: sha512-k/CjiZ80bYss6Qs7/ex1TBlPD11whT9oKfT8oTGiHa34W4JRd1NiH/Tr1DbHWQ2/vMUypxksLnF2CfmlmM5XFQ==}
|
||||
|
||||
tiny-glob@0.2.9:
|
||||
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
|
||||
|
||||
@@ -3353,6 +3459,19 @@ packages:
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
troika-three-text@0.52.4:
|
||||
resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==}
|
||||
peerDependencies:
|
||||
three: '>=0.125.0'
|
||||
|
||||
troika-three-utils@0.52.4:
|
||||
resolution: {integrity: sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==}
|
||||
peerDependencies:
|
||||
three: '>=0.125.0'
|
||||
|
||||
troika-worker-utils@0.52.0:
|
||||
resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==}
|
||||
|
||||
ts-api-utils@2.1.0:
|
||||
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
|
||||
engines: {node: '>=18.12'}
|
||||
@@ -3371,6 +3490,9 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tweakpane@3.1.10:
|
||||
resolution: {integrity: sha512-rqwnl/pUa7+inhI2E9ayGTqqP0EPOOn/wVvSWjZsRbZUItzNShny7pzwL3hVlaN4m9t/aZhsP0aFQ9U5VVR2VQ==}
|
||||
|
||||
type-check@0.4.0:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -3524,6 +3646,9 @@ packages:
|
||||
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
webgl-sdf-generator@1.1.1:
|
||||
resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
@@ -3703,13 +3828,13 @@ snapshots:
|
||||
|
||||
'@analytics/type-utils@0.6.2': {}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39': {}
|
||||
'@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f(svelte@5.25.3)':
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
|
||||
@@ -3722,7 +3847,7 @@ snapshots:
|
||||
'@appwrite.io/pink-icons': 1.0.0
|
||||
the-new-css-reset: 1.11.3
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f(svelte@5.25.3)':
|
||||
dependencies:
|
||||
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
|
||||
'@floating-ui/dom': 1.6.13
|
||||
@@ -3812,7 +3937,7 @@ snapshots:
|
||||
|
||||
'@babel/helper-validator-identifier@7.25.9': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.27.1': {}
|
||||
'@babel/helper-validator-identifier@7.28.5': {}
|
||||
|
||||
'@babel/helper-validator-option@7.25.9': {}
|
||||
|
||||
@@ -3825,9 +3950,9 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.27.0
|
||||
|
||||
'@babel/parser@7.28.4':
|
||||
'@babel/parser@7.28.6':
|
||||
dependencies:
|
||||
'@babel/types': 7.28.4
|
||||
'@babel/types': 7.28.6
|
||||
|
||||
'@babel/runtime@7.27.0':
|
||||
dependencies:
|
||||
@@ -3856,10 +3981,10 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@babel/types@7.28.4':
|
||||
'@babel/types@7.28.6':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@csstools/color-helpers@5.0.2': {}
|
||||
|
||||
@@ -3881,6 +4006,8 @@ snapshots:
|
||||
|
||||
'@csstools/css-tokenizer@3.0.3': {}
|
||||
|
||||
'@dimforge/rapier3d-compat@0.12.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.1':
|
||||
optional: true
|
||||
|
||||
@@ -4453,9 +4580,11 @@ snapshots:
|
||||
'@parcel/watcher-win32-x64': 2.5.1
|
||||
optional: true
|
||||
|
||||
'@playwright/test@1.51.1':
|
||||
'@plausible-analytics/tracker@0.4.4': {}
|
||||
|
||||
'@playwright/test@1.56.1':
|
||||
dependencies:
|
||||
playwright: 1.51.1
|
||||
playwright: 1.56.1
|
||||
|
||||
'@polka/url@1.0.0-next.28': {}
|
||||
|
||||
@@ -4674,14 +4803,14 @@ snapshots:
|
||||
magic-string: 0.30.7
|
||||
svelte: 5.25.3
|
||||
|
||||
'@sentry/sveltekit@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
|
||||
'@sentry/sveltekit@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
|
||||
dependencies:
|
||||
'@sentry/core': 8.55.0
|
||||
'@sentry/node': 8.55.0
|
||||
'@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)
|
||||
'@sentry/svelte': 8.55.0(svelte@5.25.3)
|
||||
'@sentry/vite-plugin': 2.22.6
|
||||
'@sveltejs/kit': 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/kit': 2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
magic-string: 0.30.7
|
||||
magicast: 0.2.8
|
||||
sorcery: 1.0.0
|
||||
@@ -4753,11 +4882,11 @@ snapshots:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))':
|
||||
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))':
|
||||
dependencies:
|
||||
'@sveltejs/kit': 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@sveltejs/kit': 2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
|
||||
'@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
|
||||
'@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
'@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0)
|
||||
@@ -4765,7 +4894,7 @@ snapshots:
|
||||
'@types/cookie': 0.6.0
|
||||
acorn: 8.15.0
|
||||
cookie: 0.6.0
|
||||
devalue: 5.3.2
|
||||
devalue: 5.6.2
|
||||
esm-env: 1.2.2
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.18
|
||||
@@ -4777,6 +4906,7 @@ snapshots:
|
||||
vite: 7.0.6(@types/node@22.13.14)(sass@1.86.0)
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
typescript: 5.8.2
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
|
||||
dependencies:
|
||||
@@ -4844,6 +4974,38 @@ snapshots:
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.0
|
||||
|
||||
'@threejs-kit/instanced-sprite-mesh@2.5.1(@types/three@0.182.0)(three@0.181.2)':
|
||||
dependencies:
|
||||
diet-sprite: 0.0.1
|
||||
earcut: 2.2.4
|
||||
maath: 0.10.8(@types/three@0.182.0)(three@0.181.2)
|
||||
three: 0.181.2
|
||||
three-instanced-uniforms-mesh: 0.52.4(three@0.181.2)
|
||||
troika-three-utils: 0.52.4(three@0.181.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/three'
|
||||
|
||||
'@threlte/core@8.3.1(svelte@5.25.3)(three@0.181.2)':
|
||||
dependencies:
|
||||
mitt: 3.0.1
|
||||
svelte: 5.25.3
|
||||
three: 0.181.2
|
||||
|
||||
'@threlte/extras@9.7.1(@types/three@0.182.0)(svelte@5.25.3)(three@0.181.2)':
|
||||
dependencies:
|
||||
'@threejs-kit/instanced-sprite-mesh': 2.5.1(@types/three@0.182.0)(three@0.181.2)
|
||||
camera-controls: 3.1.2(three@0.181.2)
|
||||
svelte: 5.25.3
|
||||
three: 0.181.2
|
||||
three-mesh-bvh: 0.9.3(three@0.181.2)
|
||||
three-perf: 1.0.11(three@0.181.2)
|
||||
three-viewport-gizmo: 2.2.0(three@0.181.2)
|
||||
troika-three-text: 0.52.4(three@0.181.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/three'
|
||||
|
||||
'@tweenjs/tween.js@23.1.3': {}
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
'@types/chai@5.2.2':
|
||||
@@ -4907,12 +5069,26 @@ snapshots:
|
||||
|
||||
'@types/shimmer@1.2.0': {}
|
||||
|
||||
'@types/stats.js@0.17.4': {}
|
||||
|
||||
'@types/tedious@4.0.14':
|
||||
dependencies:
|
||||
'@types/node': 22.13.14
|
||||
|
||||
'@types/three@0.182.0':
|
||||
dependencies:
|
||||
'@dimforge/rapier3d-compat': 0.12.0
|
||||
'@tweenjs/tween.js': 23.1.3
|
||||
'@types/stats.js': 0.17.4
|
||||
'@types/webxr': 0.5.24
|
||||
'@webgpu/types': 0.1.67
|
||||
fflate: 0.8.2
|
||||
meshoptimizer: 0.22.0
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
'@types/webxr@0.5.24': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.31.0)(typescript@5.8.2))(eslint@9.31.0)(typescript@5.8.2)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
@@ -5124,7 +5300,7 @@ snapshots:
|
||||
|
||||
'@vue/compiler-core@3.5.13':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.4
|
||||
'@babel/parser': 7.28.6
|
||||
'@vue/shared': 3.5.13
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
@@ -5137,13 +5313,13 @@ snapshots:
|
||||
|
||||
'@vue/compiler-sfc@3.5.13':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.4
|
||||
'@babel/parser': 7.28.6
|
||||
'@vue/compiler-core': 3.5.13
|
||||
'@vue/compiler-dom': 3.5.13
|
||||
'@vue/compiler-ssr': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.19
|
||||
magic-string: 0.30.21
|
||||
postcss: 8.5.6
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -5166,7 +5342,7 @@ snapshots:
|
||||
'@vue/reactivity': 3.5.13
|
||||
'@vue/runtime-core': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
csstype: 3.1.3
|
||||
csstype: 3.2.3
|
||||
|
||||
'@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2))':
|
||||
dependencies:
|
||||
@@ -5176,6 +5352,8 @@ snapshots:
|
||||
|
||||
'@vue/shared@3.5.13': {}
|
||||
|
||||
'@webgpu/types@0.1.67': {}
|
||||
|
||||
acorn-import-attributes@1.9.5(acorn@8.14.1):
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
@@ -5285,6 +5463,10 @@ snapshots:
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
bidi-js@1.0.3:
|
||||
dependencies:
|
||||
require-from-string: 2.0.2
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
@@ -5328,6 +5510,10 @@ snapshots:
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
camera-controls@3.1.2(three@0.181.2):
|
||||
dependencies:
|
||||
three: 0.181.2
|
||||
|
||||
caniuse-lite@1.0.30001707: {}
|
||||
|
||||
ccount@2.0.1: {}
|
||||
@@ -5441,6 +5627,8 @@ snapshots:
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
d3-array@3.2.4:
|
||||
dependencies:
|
||||
internmap: 2.0.3
|
||||
@@ -5658,12 +5846,14 @@ snapshots:
|
||||
detect-libc@1.0.3:
|
||||
optional: true
|
||||
|
||||
devalue@5.3.2: {}
|
||||
devalue@5.6.2: {}
|
||||
|
||||
devlop@1.1.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
diet-sprite@0.0.1: {}
|
||||
|
||||
dlv@1.1.3: {}
|
||||
|
||||
dom-accessibility-api@0.5.16: {}
|
||||
@@ -5678,6 +5868,8 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
earcut@2.2.4: {}
|
||||
|
||||
echarts@5.6.0:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
@@ -6277,6 +6469,11 @@ snapshots:
|
||||
|
||||
lz-string@1.5.0: {}
|
||||
|
||||
maath@0.10.8(@types/three@0.182.0)(three@0.181.2):
|
||||
dependencies:
|
||||
'@types/three': 0.182.0
|
||||
three: 0.181.2
|
||||
|
||||
magic-string@0.30.17:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
@@ -6285,7 +6482,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
magic-string@0.30.19:
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
@@ -6321,6 +6518,8 @@ snapshots:
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
meshoptimizer@0.22.0: {}
|
||||
|
||||
micromark-util-character@2.1.1:
|
||||
dependencies:
|
||||
micromark-util-symbol: 2.0.1
|
||||
@@ -6363,6 +6562,8 @@ snapshots:
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
module-details-from-path@1.0.3: {}
|
||||
|
||||
mri@1.2.0: {}
|
||||
@@ -6484,13 +6685,11 @@ snapshots:
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
plausible-tracker@0.3.9: {}
|
||||
playwright-core@1.56.1: {}
|
||||
|
||||
playwright-core@1.51.1: {}
|
||||
|
||||
playwright@1.51.1:
|
||||
playwright@1.56.1:
|
||||
dependencies:
|
||||
playwright-core: 1.51.1
|
||||
playwright-core: 1.56.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
@@ -6626,6 +6825,8 @@ snapshots:
|
||||
argparse: 1.0.10
|
||||
autolinker: 3.16.2
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
require-in-the-middle@7.5.2:
|
||||
dependencies:
|
||||
debug: 4.4.0
|
||||
@@ -6710,11 +6911,11 @@ snapshots:
|
||||
|
||||
semver@7.7.1: {}
|
||||
|
||||
seroval-plugins@1.3.3(seroval@1.3.2):
|
||||
seroval-plugins@1.4.2(seroval@1.4.2):
|
||||
dependencies:
|
||||
seroval: 1.3.2
|
||||
seroval: 1.4.2
|
||||
|
||||
seroval@1.3.2: {}
|
||||
seroval@1.4.2: {}
|
||||
|
||||
set-cookie-parser@2.7.1: {}
|
||||
|
||||
@@ -6791,9 +6992,9 @@ snapshots:
|
||||
|
||||
solid-js@1.9.5:
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
seroval: 1.3.2
|
||||
seroval-plugins: 1.3.3(seroval@1.3.2)
|
||||
csstype: 3.2.3
|
||||
seroval: 1.4.2
|
||||
seroval-plugins: 1.4.2(seroval@1.4.2)
|
||||
|
||||
solid-swr-store@0.10.7(solid-js@1.9.5)(swr-store@0.10.6):
|
||||
dependencies:
|
||||
@@ -6964,6 +7165,27 @@ snapshots:
|
||||
|
||||
the-new-css-reset@1.11.3: {}
|
||||
|
||||
three-instanced-uniforms-mesh@0.52.4(three@0.181.2):
|
||||
dependencies:
|
||||
three: 0.181.2
|
||||
troika-three-utils: 0.52.4(three@0.181.2)
|
||||
|
||||
three-mesh-bvh@0.9.3(three@0.181.2):
|
||||
dependencies:
|
||||
three: 0.181.2
|
||||
|
||||
three-perf@1.0.11(three@0.181.2):
|
||||
dependencies:
|
||||
three: 0.181.2
|
||||
troika-three-text: 0.52.4(three@0.181.2)
|
||||
tweakpane: 3.1.10
|
||||
|
||||
three-viewport-gizmo@2.2.0(three@0.181.2):
|
||||
dependencies:
|
||||
three: 0.181.2
|
||||
|
||||
three@0.181.2: {}
|
||||
|
||||
tiny-glob@0.2.9:
|
||||
dependencies:
|
||||
globalyzer: 0.1.0
|
||||
@@ -7020,6 +7242,20 @@ snapshots:
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
troika-three-text@0.52.4(three@0.181.2):
|
||||
dependencies:
|
||||
bidi-js: 1.0.3
|
||||
three: 0.181.2
|
||||
troika-three-utils: 0.52.4(three@0.181.2)
|
||||
troika-worker-utils: 0.52.0
|
||||
webgl-sdf-generator: 1.1.1
|
||||
|
||||
troika-three-utils@0.52.4(three@0.181.2):
|
||||
dependencies:
|
||||
three: 0.181.2
|
||||
|
||||
troika-worker-utils@0.52.0: {}
|
||||
|
||||
ts-api-utils@2.1.0(typescript@5.8.2):
|
||||
dependencies:
|
||||
typescript: 5.8.2
|
||||
@@ -7032,6 +7268,8 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tweakpane@3.1.10: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
@@ -7201,6 +7439,8 @@ snapshots:
|
||||
dependencies:
|
||||
xml-name-validator: 5.0.0
|
||||
|
||||
webgl-sdf-generator@1.1.1: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webidl-conversions@7.0.0: {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Analytics, { type AnalyticsPlugin } from 'analytics';
|
||||
import Plausible from 'plausible-tracker';
|
||||
import { init, track } from '@plausible-analytics/tracker';
|
||||
import { get } from 'svelte/store';
|
||||
import { page } from '$app/state';
|
||||
import { user } from '$lib/stores/user';
|
||||
@@ -11,30 +11,30 @@ import { getReferrerAndUtmSource, getTrackedQueryParams } from '$lib/helpers/utm
|
||||
function plausible(domain: string): AnalyticsPlugin {
|
||||
if (!browser) return { name: 'analytics-plugin-plausible' };
|
||||
|
||||
const instance = Plausible({
|
||||
domain
|
||||
init({
|
||||
domain,
|
||||
autoCapturePageviews: false
|
||||
});
|
||||
|
||||
return {
|
||||
name: 'analytics-plugin-plausible',
|
||||
page: ({ payload }) => {
|
||||
instance.trackPageview({
|
||||
track('pageview', {
|
||||
url: payload.properties.path,
|
||||
referrer: payload.properties.referrer,
|
||||
deviceWidth: payload.properties.width
|
||||
props: {
|
||||
referrer: payload.properties.referrer,
|
||||
deviceWidth: String(payload.properties.width)
|
||||
}
|
||||
});
|
||||
},
|
||||
track: ({ payload }) => {
|
||||
instance.trackEvent(
|
||||
payload.event,
|
||||
{
|
||||
props: payload.properties
|
||||
},
|
||||
{
|
||||
url: payload.properties.path,
|
||||
deviceWidth: payload.properties.width
|
||||
track(payload.event, {
|
||||
url: payload.properties.path,
|
||||
props: {
|
||||
...payload.properties,
|
||||
deviceWidth: String(payload.properties.width)
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
loaded: () => true
|
||||
};
|
||||
@@ -195,7 +195,10 @@ export enum Click {
|
||||
VariablesCreateClick = 'click_variable_create',
|
||||
VariablesUpdateClick = 'click_variable_update',
|
||||
VariablesImportClick = 'click_variable_import',
|
||||
WebsiteOpenClick = 'click_open_website'
|
||||
WebsiteOpenClick = 'click_open_website',
|
||||
CopyPromptStarterKitClick = 'click_copy_prompt_starter_kit',
|
||||
OpenInCursorClick = 'click_open_in_cursor',
|
||||
OpenInLovableClick = 'click_open_in_lovable'
|
||||
}
|
||||
|
||||
export enum Submit {
|
||||
@@ -244,6 +247,7 @@ export enum Submit {
|
||||
ProjectDelete = 'submit_project_delete',
|
||||
ProjectUpdateName = 'submit_project_update_name',
|
||||
ProjectUpdateTeam = 'submit_project_update_team',
|
||||
ProjectUpdateLabels = 'submit_project_update_labels',
|
||||
ProjectService = 'submit_project_service',
|
||||
ProjectUpdateSMTP = 'submit_project_update_smtp',
|
||||
MemberCreate = 'submit_member_create',
|
||||
@@ -265,7 +269,7 @@ export enum Submit {
|
||||
AuthSessionAlertsUpdate = 'submit_auth_session_alerts_update',
|
||||
AuthMembershipPrivacyUpdate = 'submit_auth_membership_privacy_update',
|
||||
AuthMockNumbersUpdate = 'submit_auth_mock_numbers_update',
|
||||
AuthInvalidateSesssion = 'submit_auth_invalidate_session',
|
||||
AuthInvalidateSession = 'submit_auth_invalidate_session',
|
||||
SessionsLengthUpdate = 'submit_sessions_length_update',
|
||||
SessionsLimitUpdate = 'submit_sessions_limit_update',
|
||||
SessionDelete = 'submit_session_delete',
|
||||
@@ -274,6 +278,7 @@ export enum Submit {
|
||||
DatabaseDelete = 'submit_database_delete',
|
||||
DatabaseUpdateName = 'submit_database_update_name',
|
||||
DatabaseImportCsv = 'submit_database_import_csv',
|
||||
DatabaseBackupDelete = 'submit_database_backup_delete',
|
||||
|
||||
ColumnCreate = 'submit_column_create',
|
||||
ColumnUpdate = 'submit_column_update',
|
||||
@@ -357,6 +362,7 @@ export enum Submit {
|
||||
BucketUpdateSize = 'submit_bucket_update_size',
|
||||
BucketUpdateCompression = 'submit_bucket_update_compression',
|
||||
BucketUpdateExtensions = 'submit_bucket_update_extensions',
|
||||
BucketUpdateTransformations = 'submit_bucket_update_transformations',
|
||||
FileCreate = 'submit_file_create',
|
||||
FileDelete = 'submit_file_delete',
|
||||
FileUpdatePermissions = 'submit_file_update_permissions',
|
||||
|
||||
@@ -30,7 +30,12 @@
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import { get, writable, type Readable } from 'svelte/store';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { commandCenterKeyDownHandler, disableCommands, registerCommands } from './commands';
|
||||
import {
|
||||
commandCenterKeyDownHandler,
|
||||
disableCommands,
|
||||
isTargetInputLike,
|
||||
registerCommands
|
||||
} from './commands';
|
||||
import { RootPanel } from './panels';
|
||||
import { addSubPanel, clearSubPanels, subPanels } from './subPanels';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -95,13 +100,9 @@
|
||||
keys = [];
|
||||
}, 1000);
|
||||
|
||||
function isInputEvent(event: KeyboardEvent) {
|
||||
return ['INPUT', 'TEXTAREA', 'SELECT'].includes((event.target as HTMLElement).tagName);
|
||||
}
|
||||
|
||||
const handleKeydown = (e: KeyboardEvent) => {
|
||||
if (!$subPanels.length) {
|
||||
if (isInputEvent(e)) return;
|
||||
if (isTargetInputLike(e.target)) return;
|
||||
keys = [...keys, e.key].slice(-10);
|
||||
resetKeys();
|
||||
}
|
||||
|
||||
@@ -100,8 +100,11 @@ const commandsEnabled = derived(disabledMap, ($disabledMap) => {
|
||||
return Array.from($disabledMap.values()).every((disabled) => !disabled);
|
||||
});
|
||||
|
||||
function isInputEvent(event: KeyboardEvent) {
|
||||
return ['INPUT', 'TEXTAREA', 'SELECT'].includes((event.target as HTMLElement).tagName);
|
||||
export function isTargetInputLike(element: EventTarget | null) {
|
||||
if (!(element instanceof HTMLElement)) return false;
|
||||
return !!element.closest(
|
||||
'input,textarea,select,[contenteditable],[role="combobox"],[role="textbox"],[role="searchbox"],[data-command-center-ignore]'
|
||||
);
|
||||
}
|
||||
|
||||
function getCommandRank(command: KeyedCommand) {
|
||||
@@ -204,7 +207,12 @@ export const commandCenterKeyDownHandler = derived(
|
||||
for (const command of commandsArr) {
|
||||
if (!isKeyedCommand(command)) continue;
|
||||
if (!command.forceEnable) {
|
||||
if (command.disabled || !enabled || isInputEvent(event) || $wizard.show) {
|
||||
if (
|
||||
command.disabled ||
|
||||
!enabled ||
|
||||
isTargetInputLike(event.target) ||
|
||||
$wizard.show
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ import { base } from '$app/paths';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Searcher } from '../commands';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { Platform, Query } from '@appwrite.io/console';
|
||||
|
||||
export const orgSearcher = (async (query: string) => {
|
||||
const { teams } = !isCloud
|
||||
? await sdk.forConsole.teams.list()
|
||||
: await sdk.forConsole.billing.listOrganization();
|
||||
: await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', Platform.Appwrite)
|
||||
]);
|
||||
|
||||
return teams
|
||||
.filter((organization) => organization.name.toLowerCase().includes(query.toLowerCase()))
|
||||
|
||||
@@ -4,22 +4,68 @@ import { sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Searcher } from '../commands';
|
||||
import { project } from '$routes/(console)/project-[region]-[project]/store';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
export const projectsSearcher = (async (query: string) => {
|
||||
const q = query.toLowerCase().trim();
|
||||
const keywords = [
|
||||
'endpoint',
|
||||
'api key',
|
||||
'api-key',
|
||||
'apikey',
|
||||
'project id',
|
||||
'project-id',
|
||||
'api end'
|
||||
];
|
||||
|
||||
const wantsCredentials = keywords.some((k) => q.includes(k));
|
||||
|
||||
if (wantsCredentials) {
|
||||
const curr = get(project);
|
||||
if (curr?.$id) {
|
||||
return [
|
||||
{
|
||||
label: 'Go to Settings',
|
||||
callback: () => {
|
||||
goto(`${base}/project-${curr.region}-${curr.$id}/settings`);
|
||||
},
|
||||
group: 'navigation'
|
||||
}
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const { projects } = await sdk.forConsole.projects.list({
|
||||
queries: [Query.equal('teamId', get(organization).$id), Query.orderDesc('')]
|
||||
queries: [
|
||||
Query.equal('teamId', get(organization).$id),
|
||||
Query.orderDesc(''),
|
||||
Query.select(['$id', 'name', 'region'])
|
||||
]
|
||||
});
|
||||
|
||||
return projects
|
||||
.filter((project) => project.name.toLowerCase().includes(query.toLowerCase()))
|
||||
.filter((project) => {
|
||||
const searchable = [project.name, project.$id, project.region]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
|
||||
const words = q.split(/\s+/).filter(Boolean);
|
||||
return words.every((w) => searchable.includes(w));
|
||||
})
|
||||
.map((project) => {
|
||||
const href = `${base}/project-${project.region}-${project.$id}`;
|
||||
|
||||
const label = project.name;
|
||||
|
||||
return {
|
||||
label: project.name,
|
||||
label,
|
||||
callback: () => {
|
||||
goto(`${base}/project-${project.region}-${project.$id}`);
|
||||
goto(href);
|
||||
},
|
||||
group: 'projects'
|
||||
} as const;
|
||||
};
|
||||
});
|
||||
}) satisfies Searcher;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { Copy } from '.';
|
||||
import { Icon, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { IconDuplicate } from '@appwrite.io/pink-icons-svelte';
|
||||
import { getProjectEndpoint } from '$lib/helpers/project';
|
||||
</script>
|
||||
|
||||
<Copy value={getProjectEndpoint()} copyText="Copy endpoint">
|
||||
<Tag size="xs" variant="code">
|
||||
<Icon icon={IconDuplicate} size="s" slot="start" />
|
||||
<span style:white-space="nowrap" style:overflow="hidden" style:word-break="break-all">
|
||||
API endpoint
|
||||
</span>
|
||||
</Tag>
|
||||
</Copy>
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import PaginationWithLimit from './paginationWithLimit.svelte';
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import { DropList, GridItem1, CardContainer, Modal } from '$lib/components';
|
||||
import { GridItem1, CardContainer, Modal } from '$lib/components';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import {
|
||||
Badge,
|
||||
Icon,
|
||||
Typography,
|
||||
Tag,
|
||||
Accordion,
|
||||
ActionMenu,
|
||||
Popover,
|
||||
@@ -20,7 +20,6 @@
|
||||
IconFlutter,
|
||||
IconReact,
|
||||
IconUnity,
|
||||
IconInfo,
|
||||
IconDotsHorizontal,
|
||||
IconInboxIn,
|
||||
IconSwitchHorizontal,
|
||||
@@ -29,7 +28,6 @@
|
||||
import { getPlatformInfo } from '$lib/helpers/platform';
|
||||
import { Status, type Models } from '@appwrite.io/console';
|
||||
import type { ComponentType } from 'svelte';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -48,12 +46,23 @@
|
||||
projectsToArchive: Models.Project[];
|
||||
organization: Organization;
|
||||
currentPlan: Plan;
|
||||
archivedTotalOverall: number;
|
||||
archivedOffset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
let { projectsToArchive, organization, currentPlan }: Props = $props();
|
||||
let {
|
||||
projectsToArchive,
|
||||
organization,
|
||||
currentPlan,
|
||||
archivedTotalOverall,
|
||||
archivedOffset,
|
||||
limit
|
||||
}: Props = $props();
|
||||
|
||||
// Check if current plan order is less than Pro (order < 1 means FREE plan)
|
||||
let isPlanBelowPro = $derived(currentPlan?.order < 1);
|
||||
|
||||
// Track Read-only info droplist per archived project
|
||||
let readOnlyInfoOpen = $state<Record<string, boolean>>({});
|
||||
let showUnarchiveModal = $state(false);
|
||||
let projectToUnarchive = $state<Models.Project | null>(null);
|
||||
let showDeleteModal = $state(false);
|
||||
@@ -97,7 +106,7 @@
|
||||
function isUnarchiveDisabled(): boolean {
|
||||
if (!organization || !currentPlan) return true;
|
||||
|
||||
if (organization.billingPlan === BillingPlan.FREE) {
|
||||
if (isPlanBelowPro) {
|
||||
const currentProjectCount = organization.projects?.length || 0;
|
||||
const projectLimit = currentPlan.projects || 0;
|
||||
|
||||
@@ -189,21 +198,26 @@
|
||||
}
|
||||
|
||||
import { formatName as formatNameHelper } from '$lib/helpers/string';
|
||||
function formatName(name: string, limit: number = 19) {
|
||||
function formatName(name: string, limit: number = 16) {
|
||||
return formatNameHelper(name, limit, $isSmallViewport);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if projectsToArchive.length > 0}
|
||||
<div class="archive-projects-margin-top">
|
||||
<Accordion title="Archived projects" badge={`${projectsToArchive.length}`}>
|
||||
<Accordion
|
||||
title={isPlanBelowPro ? 'Archived projects' : 'Pending archive'}
|
||||
badge={`${archivedTotalOverall}`}>
|
||||
<Typography.Text tag="p" size="s">
|
||||
These projects have been archived and are read-only. You can view and migrate their
|
||||
data.
|
||||
{#if isPlanBelowPro}
|
||||
These projects are archived and require a plan upgrade to restore access.
|
||||
{:else}
|
||||
These projects will be archived at the end of your billing cycle.
|
||||
{/if}
|
||||
</Typography.Text>
|
||||
|
||||
<div class="archive-projects-margin">
|
||||
<CardContainer disableEmpty={true} total={projectsToArchive.length}>
|
||||
<CardContainer disableEmpty={true} total={archivedTotalOverall}>
|
||||
{#each projectsToArchive as project}
|
||||
{@const platforms = filterPlatforms(
|
||||
project.platforms.map((platform) => getPlatformInfo(platform.type))
|
||||
@@ -216,36 +230,6 @@
|
||||
<svelte:fragment slot="title">{formatted}</svelte:fragment>
|
||||
<svelte:fragment slot="status">
|
||||
<div class="status-container">
|
||||
<DropList
|
||||
bind:show={readOnlyInfoOpen[project.$id]}
|
||||
placement="bottom-start"
|
||||
noArrow>
|
||||
<Tag
|
||||
size="s"
|
||||
style="white-space: nowrap;"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
readOnlyInfoOpen = {
|
||||
...readOnlyInfoOpen,
|
||||
[project.$id]: !readOnlyInfoOpen[project.$id]
|
||||
};
|
||||
}}>
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span>Read only</span>
|
||||
</Tag>
|
||||
<svelte:fragment slot="list">
|
||||
<li
|
||||
class="drop-list-item u-width-250"
|
||||
style="padding: var(--space-5, 12px) var(--space-6, 16px)">
|
||||
<span class="u-block u-mb-8">
|
||||
Archived projects are read-only. You can view
|
||||
and migrate their data, but they no longer
|
||||
accept edits or requests.
|
||||
</span>
|
||||
</li>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
<Button
|
||||
text
|
||||
@@ -267,6 +251,7 @@
|
||||
>Unarchive project</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconSwitchHorizontal}
|
||||
disabled={isUnarchiveDisabled()}
|
||||
on:click={() => handleMigrateProject(project)}
|
||||
>Migrate project</ActionMenu.Item.Button>
|
||||
<div class="action-menu-divider">
|
||||
@@ -292,7 +277,7 @@
|
||||
</Badge>
|
||||
{/each}
|
||||
|
||||
{#if platforms.length > 3}
|
||||
{#if platforms.length > 2}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={`+${platforms.length - 2}`}
|
||||
@@ -308,6 +293,15 @@
|
||||
</GridItem1>
|
||||
{/each}
|
||||
</CardContainer>
|
||||
|
||||
<PaginationWithLimit
|
||||
name="Archived Projects"
|
||||
{limit}
|
||||
offset={archivedOffset}
|
||||
total={archivedTotalOverall}
|
||||
pageParam="archivedPage"
|
||||
removeOnFirstPage
|
||||
class="pagination-container" />
|
||||
</div>
|
||||
</Accordion>
|
||||
</div>
|
||||
@@ -381,4 +375,7 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
:global(.pagination-container) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import { isCloud, isSelfHosted } from '$lib/system';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import type { BackupArchive, BackupRestoration } from '$lib/sdk/backups';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -13,10 +12,11 @@
|
||||
import { getProjectId } from '$lib/helpers/project';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
|
||||
const backupRestoreItems: {
|
||||
archives: Map<string, BackupArchive>;
|
||||
restorations: Map<string, BackupRestoration>;
|
||||
archives: Map<string, Models.BackupArchive>;
|
||||
restorations: Map<string, Models.BackupRestoration>;
|
||||
} = {
|
||||
archives: new Map(),
|
||||
restorations: new Map()
|
||||
@@ -117,7 +117,7 @@
|
||||
if (which === 'restorations') lastDatabaseRestorationId = null;
|
||||
}
|
||||
|
||||
function backupName(item: BackupArchive | BackupRestoration, key: string) {
|
||||
function backupName(item: Models.BackupArchive | Models.BackupRestoration, key: string) {
|
||||
const column = key === 'archives' ? '$createdAt' : 'startedAt';
|
||||
|
||||
return toLocaleDate(item[column]);
|
||||
@@ -125,20 +125,18 @@
|
||||
|
||||
onMount(() => {
|
||||
// fast path: don't subscribe if org is on a free plan or is self-hosted.
|
||||
if (isSelfHosted || (isCloud && $organization.billingPlan === BillingPlan.FREE)) return;
|
||||
if (isSelfHosted || (isCloud && $organization?.billingPlan === BillingPlan.FREE)) return;
|
||||
|
||||
return realtime
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.subscribe('console', (response) => {
|
||||
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
|
||||
return realtime.forProject(page.params.region, 'console', (response) => {
|
||||
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
|
||||
|
||||
if (
|
||||
response.events.includes('archives.*') ||
|
||||
response.events.includes('restorations.*')
|
||||
) {
|
||||
updateOrAddItem(response.payload);
|
||||
}
|
||||
});
|
||||
if (
|
||||
response.events.includes('archives.*') ||
|
||||
response.events.includes('restorations.*')
|
||||
) {
|
||||
updateOrAddItem(response.payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { hideBillingHeaderRoutes, paymentMissingMandate } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { confirmSetup } from '$lib/stores/stripe';
|
||||
|
||||
async function verifyPaymentMethod() {
|
||||
const method = await sdk.forConsole.billing.setupPaymentMandate(
|
||||
$organization.$id,
|
||||
$paymentMissingMandate.$id
|
||||
);
|
||||
await confirmSetup(method.clientSecret, method.providerMethodId);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $paymentMissingMandate && $paymentMissingMandate?.country?.toLowerCase() === 'in' && $paymentMissingMandate.mandateId === null && !hideBillingHeaderRoutes.includes(page.url.pathname)}
|
||||
<HeaderAlert title="Authorization required" type="info">
|
||||
The payment method for {$organization.name} needs to be verified.
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button secondary on:click={verifyPaymentMethod}>Verify payment method</Button>
|
||||
</svelte:fragment>
|
||||
</HeaderAlert>
|
||||
{/if}
|
||||
@@ -1,8 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { FakeModal } from '$lib/components';
|
||||
import { InputText, Button } from '$lib/elements/forms';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { initializeStripe, setPaymentMethod, submitStripeCard } from '$lib/stores/stripe';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import {
|
||||
initializeStripe,
|
||||
setPaymentMethod,
|
||||
submitStripeCard,
|
||||
unmountPaymentElement
|
||||
} from '$lib/stores/stripe';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -10,16 +15,16 @@
|
||||
import { Spinner } from '@appwrite.io/pink-svelte';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
import StatePicker from './statePicker.svelte';
|
||||
import type { PaymentMethodData } from '$lib/sdk/billing';
|
||||
|
||||
export let show = false;
|
||||
export let onCardSubmit: ((card: PaymentMethodData) => void) | null = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let name: string;
|
||||
let error: string;
|
||||
let modal: FakeModal;
|
||||
let showState: boolean = false;
|
||||
let name: string;
|
||||
let state: string = '';
|
||||
let error: string = null;
|
||||
let showState: boolean = false;
|
||||
let paymentMethod: PaymentMethod | null = null;
|
||||
|
||||
async function handleSubmit() {
|
||||
@@ -32,7 +37,7 @@
|
||||
const card = await setPaymentMethod(paymentMethod.id, name, state);
|
||||
modal.closeModal();
|
||||
await invalidate(Dependencies.PAYMENT_METHODS);
|
||||
dispatch('submit', card);
|
||||
onCardSubmit?.(card);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'A new payment method has been added to your account'
|
||||
@@ -50,7 +55,7 @@
|
||||
}
|
||||
modal.closeModal();
|
||||
await invalidate(Dependencies.PAYMENT_METHODS);
|
||||
dispatch('submit', card);
|
||||
onCardSubmit?.(card as PaymentMethodData);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'A new payment method has been added to your account'
|
||||
@@ -89,6 +94,8 @@
|
||||
};
|
||||
});
|
||||
|
||||
onDestroy(unmountPaymentElement);
|
||||
|
||||
$: if (element) {
|
||||
observer.observe(element, { childList: true });
|
||||
}
|
||||
@@ -99,7 +106,8 @@
|
||||
bind:show
|
||||
title="Add payment method"
|
||||
bind:error
|
||||
onSubmit={handleSubmit}>
|
||||
onSubmit={handleSubmit}
|
||||
skipEnterOnBackdrop={showState}>
|
||||
<slot />
|
||||
{#if showState}
|
||||
<StatePicker card={paymentMethod} bind:state />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { currentPlan, organization } from '$lib/stores/organization';
|
||||
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Badge, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { LabelCard } from '..';
|
||||
import type { Plan } from '$lib/sdk/billing';
|
||||
import { page } from '$app/state';
|
||||
@@ -17,31 +17,42 @@
|
||||
|
||||
// experiment to remove scale plan temporarily
|
||||
$: plansWithoutScale = plans.filter((plan) => plan.$id != BillingPlan.SCALE);
|
||||
|
||||
function shouldShowTooltip(plan: Plan) {
|
||||
if (plan.$id !== BillingPlan.FREE) return true;
|
||||
else return !anyOrgFree;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Layout.Stack>
|
||||
{#each plansWithoutScale as plan}
|
||||
<LabelCard
|
||||
name="plan"
|
||||
bind:group={billingPlan}
|
||||
disabled={!selfService || (plan.$id === BillingPlan.FREE && anyOrgFree)}
|
||||
tooltipShow={plan.$id === BillingPlan.FREE && anyOrgFree}
|
||||
value={plan.$id}
|
||||
title={plan.name}>
|
||||
<svelte:fragment slot="action">
|
||||
{#if $organization?.billingPlan === plan.$id && !isNewOrg}
|
||||
<Badge variant="secondary" size="xs" content="Current plan" />
|
||||
{/if}
|
||||
<Tooltip disabled={shouldShowTooltip(plan)} maxWidth="fit-content">
|
||||
<LabelCard
|
||||
name="plan"
|
||||
bind:group={billingPlan}
|
||||
disabled={!selfService || (plan.$id === BillingPlan.FREE && anyOrgFree)}
|
||||
tooltipShow={plan.$id === BillingPlan.FREE && anyOrgFree}
|
||||
value={plan.$id}
|
||||
title={plan.name}>
|
||||
<svelte:fragment slot="action">
|
||||
{#if $organization?.billingPlan === plan.$id && !isNewOrg}
|
||||
<Badge variant="secondary" size="xs" content="Current plan" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<Typography.Caption variant="400">
|
||||
{plan.desc}
|
||||
</Typography.Caption>
|
||||
<Typography.Text>
|
||||
{@const isZeroPrice = (plan.price ?? 0) <= 0}
|
||||
{@const price = formatCurrency(plan.price ?? 0)}
|
||||
{isZeroPrice ? price : `${price} per month + usage`}
|
||||
</Typography.Text>
|
||||
</LabelCard>
|
||||
|
||||
<svelte:fragment slot="tooltip">
|
||||
Only 1 free organization is allowed per account.
|
||||
</svelte:fragment>
|
||||
<Typography.Caption variant="400">
|
||||
{plan.desc}
|
||||
</Typography.Caption>
|
||||
<Typography.Text>
|
||||
{@const isZeroPrice = (plan.price ?? 0) <= 0}
|
||||
{@const price = formatCurrency(plan.price ?? 0)}
|
||||
{isZeroPrice ? price : `${price} per month + usage`}
|
||||
</Typography.Text>
|
||||
</LabelCard>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
{#if $currentPlan && !currentPlanInList}
|
||||
<LabelCard
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
let showTaxId = false;
|
||||
let showPaymentModal = false;
|
||||
|
||||
async function cardSaved(event: CustomEvent<PaymentMethodData>) {
|
||||
value = event.detail.$id;
|
||||
async function cardSaved(card: PaymentMethodData) {
|
||||
value = card.$id;
|
||||
|
||||
if (value) {
|
||||
methods = {
|
||||
...methods,
|
||||
total: methods.total + 1,
|
||||
paymentMethods: [...methods.paymentMethods, event.detail]
|
||||
paymentMethods: [...methods.paymentMethods, card]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
</Layout.Stack>
|
||||
|
||||
{#if showPaymentModal && isCloud && hasStripePublicKey}
|
||||
<PaymentModal bind:show={showPaymentModal} on:submit={cardSaved}>
|
||||
<PaymentModal bind:show={showPaymentModal} onCardSubmit={cardSaved}>
|
||||
<svelte:fragment slot="end">
|
||||
<Selector.Checkbox
|
||||
id="taxIdCheck"
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
function resolveThemeColor(
|
||||
color: Record<'light' | 'dark', string> | string | undefined
|
||||
): string | undefined {
|
||||
if (!color) return undefined;
|
||||
if (typeof color === 'string') return color;
|
||||
return color[$app.themeInUse];
|
||||
}
|
||||
|
||||
let currentIndex = $state(0);
|
||||
let openModalOnMobile = $state(false);
|
||||
|
||||
@@ -100,6 +108,12 @@
|
||||
message: 'Explore new features to enhance your projects and improve security.'
|
||||
};
|
||||
|
||||
// override
|
||||
if (currentModalAlert?.sameContentOnMobileLayout) {
|
||||
fallback.title = currentModalAlert?.title;
|
||||
fallback.message = currentModalAlert?.message;
|
||||
}
|
||||
|
||||
const shouldApplyConfig = config?.enabled === true && visibleAlerts.length === 1;
|
||||
|
||||
return {
|
||||
@@ -199,7 +213,10 @@
|
||||
</button>
|
||||
|
||||
<div class="content-wrapper u-flex-vertical u-gap-16">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
{#if currentModalAlert.backgroundComponent}
|
||||
{@const BackgroundComponent = currentModalAlert.backgroundComponent}
|
||||
<BackgroundComponent />
|
||||
{:else if $app.themeInUse === 'dark'}
|
||||
<img
|
||||
src={currentModalAlert.src.dark}
|
||||
alt={currentModalAlert.title}
|
||||
@@ -258,17 +275,27 @@
|
||||
<div
|
||||
class="buttons u-flex u-flex-vertical-mobile u-gap-4 u-padding-inline-8 u-padding-block-8">
|
||||
<Button
|
||||
size="xs"
|
||||
fullWidthMobile
|
||||
secondary={!hasOnlyPrimaryCta}
|
||||
class={`${hasOnlyPrimaryCta ? 'only-primary-cta' : ''}`}
|
||||
--bgcolor-accent={resolveThemeColor(
|
||||
currentModalAlert.cta.background
|
||||
)}
|
||||
--bgcolor-accent-secondary={resolveThemeColor(
|
||||
currentModalAlert.cta.backgroundHover
|
||||
)}
|
||||
on:click={() => triggerWindowLink(currentModalAlert)}>
|
||||
{currentModalAlert.cta.text}
|
||||
<span style:color={resolveThemeColor(currentModalAlert.cta.color)}>
|
||||
{currentModalAlert.cta.text}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{#if currentModalAlert.learnMore}
|
||||
<!-- docs, learn-more, etc always external -->
|
||||
<Button
|
||||
text
|
||||
size="xs"
|
||||
class="button"
|
||||
external
|
||||
fullWidthMobile
|
||||
@@ -310,7 +337,10 @@
|
||||
</button>
|
||||
|
||||
<div class="content-wrapper u-flex-vertical u-gap-16">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
{#if currentModalAlert.backgroundComponent}
|
||||
{@const BackgroundComponent = currentModalAlert.backgroundComponent}
|
||||
<BackgroundComponent />
|
||||
{:else if $app.themeInUse === 'dark'}
|
||||
<img
|
||||
src={currentModalAlert.src.dark}
|
||||
alt={currentModalAlert.title}
|
||||
@@ -355,7 +385,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="u-flex-vertical u-gap-8 u-padding-inline-8">
|
||||
<div class="u-flex-vertical u-gap-4 u-padding-inline-8">
|
||||
<Typography.Text variant="m-500" color="--fgcolor-neutral-primary">
|
||||
{currentModalAlert.title}
|
||||
</Typography.Text>
|
||||
@@ -372,22 +402,35 @@
|
||||
<div
|
||||
class="buttons u-flex u-flex-vertical-mobile u-gap-4 u-padding-inline-8 u-padding-block-8">
|
||||
<Button
|
||||
size="xs"
|
||||
secondary={!hasOnlyPrimaryCta}
|
||||
class="button"
|
||||
fullWidthMobile
|
||||
--bgcolor-accent={resolveThemeColor(
|
||||
currentModalAlert.cta.background
|
||||
)}
|
||||
--bgcolor-accent-secondary={resolveThemeColor(
|
||||
currentModalAlert.cta.backgroundHover
|
||||
)}
|
||||
on:click={() => {
|
||||
openModalOnMobile = false;
|
||||
triggerWindowLink(currentModalAlert);
|
||||
}}>
|
||||
{shouldShowUpgrade
|
||||
? 'Upgrade plan'
|
||||
: currentModalAlert.cta.text}
|
||||
<span
|
||||
style:color={resolveThemeColor(
|
||||
currentModalAlert.cta.color
|
||||
)}>
|
||||
{shouldShowUpgrade
|
||||
? 'Upgrade plan'
|
||||
: currentModalAlert.cta.text}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{#if currentModalAlert.learnMore}
|
||||
<!-- docs, learn-more, etc always external -->
|
||||
<Button
|
||||
text
|
||||
size="xs"
|
||||
class="button"
|
||||
external
|
||||
fullWidthMobile
|
||||
@@ -424,7 +467,7 @@
|
||||
<div class="u-flex-vertical u-gap-4">
|
||||
<div class="u-flex u-cross-center u-main-space-between">
|
||||
<Typography.Text variant="m-500" color="--fgcolor-neutral-primary">
|
||||
{currentModalAlert.title}
|
||||
{mobileConfig.title}
|
||||
</Typography.Text>
|
||||
<button onclick={hideAllModalAlerts} aria-label="Close">
|
||||
<span class="icon-x"></span>
|
||||
@@ -447,6 +490,9 @@
|
||||
<style>
|
||||
.card {
|
||||
padding: 0.5rem;
|
||||
box-shadow:
|
||||
0 8px 16px 0 rgba(0, 0, 0, 0.02),
|
||||
0 20px 24px 0 rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.main-alert-wrapper {
|
||||
@@ -466,6 +512,7 @@
|
||||
|
||||
.icon-inline-tag {
|
||||
top: 1rem;
|
||||
z-index: 1;
|
||||
right: 1rem;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
@@ -140,7 +140,8 @@
|
||||
queries: [
|
||||
Query.equal('teamId', organizationId ?? page.data.currentOrgId),
|
||||
Query.limit(5),
|
||||
Query.orderDesc('$updatedAt')
|
||||
Query.orderDesc('$updatedAt'),
|
||||
Query.select(['$id', 'name', 'region', 'teamId'])
|
||||
]
|
||||
})) ?? loadedProjects;
|
||||
|
||||
|
||||
@@ -20,21 +20,23 @@
|
||||
};
|
||||
|
||||
type ButtonProps = {
|
||||
isButton: true;
|
||||
isButton: boolean;
|
||||
href?: never;
|
||||
};
|
||||
|
||||
type AnchorProps = {
|
||||
href: string;
|
||||
isButton?: never;
|
||||
isButton?: boolean;
|
||||
external?: boolean;
|
||||
};
|
||||
|
||||
let classes = '';
|
||||
type $$Props = BaseProps & (ButtonProps | AnchorProps | BaseProps) & BaseCardProps;
|
||||
|
||||
export let isDashed = false;
|
||||
export let isButton = false;
|
||||
export let isDashed: boolean = false;
|
||||
export let isButton: boolean = false;
|
||||
export let href: string = null;
|
||||
let classes = '';
|
||||
export let external: boolean = false;
|
||||
export { classes as class };
|
||||
export let style = '';
|
||||
export let padding: $$Props['padding'] = 'm';
|
||||
@@ -45,7 +47,15 @@
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<Card.Link class={resolvedClasses} {href} {style} {padding} {radius} {variant} on:click>
|
||||
<Card.Link
|
||||
{href}
|
||||
{style}
|
||||
{padding}
|
||||
{radius}
|
||||
{variant}
|
||||
on:click
|
||||
class={resolvedClasses}
|
||||
{...external ? { target: '_blank' } : {}}>
|
||||
<Layout.Stack gap="xl">
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Divider, Layout, Card, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let overflow = true;
|
||||
export let hideFooter = false;
|
||||
export let gap: 'none' | 'xxxs' | 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl' | 'xxxl' = 'l';
|
||||
</script>
|
||||
@@ -16,7 +17,7 @@
|
||||
</Typography.Text>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<div style:overflow="visible">
|
||||
<div style:overflow={overflow ? 'visible' : 'hidden'}>
|
||||
<Layout.Stack {gap}>
|
||||
<slot name="aside" />
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
disabled={tooltipDisabled}
|
||||
portal={tooltipPortal}
|
||||
delay={tooltipDelay}
|
||||
maxWidth="500px"
|
||||
placement={tooltipPlacement}>
|
||||
<span
|
||||
data-private
|
||||
|
||||
@@ -2,22 +2,29 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { realtime, sdk } from '$lib/stores/sdk';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { getProjectId } from '$lib/helpers/project';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Layout, Typography, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconExclamationCircle } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Modal, Code } from '$lib/components';
|
||||
import { type Models, type Payload, Query } from '@appwrite.io/console';
|
||||
|
||||
// re-render the key for sheet UI.
|
||||
import { hash } from '$lib/helpers/string';
|
||||
import { spreadsheetRenderKey } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store';
|
||||
import { Link } from '$lib/elements';
|
||||
|
||||
type CsvImportError = {
|
||||
[key: string]: number | string | null;
|
||||
};
|
||||
|
||||
type ImportItem = {
|
||||
status: string;
|
||||
table?: string;
|
||||
errors?: string[];
|
||||
};
|
||||
|
||||
type ImportItemsMap = Map<string, ImportItem>;
|
||||
@@ -28,7 +35,7 @@
|
||||
* The structure is as follows -
|
||||
* `{ migrationId: { status: status, table: table } }`
|
||||
*/
|
||||
const importItems: Writable<ImportItemsMap> = writable(new Map());
|
||||
let importItems = $state<ImportItemsMap>(new Map());
|
||||
|
||||
async function showCompletionNotification(database: string, table: string, payload: Payload) {
|
||||
const isSuccess = payload.status === 'completed';
|
||||
@@ -37,13 +44,9 @@
|
||||
if (!isSuccess && !isError) return;
|
||||
|
||||
let errorMessage = 'Import failed. Check your CSV for correct fields and required values.';
|
||||
if (isError && Array.isArray(payload.errors)) {
|
||||
try {
|
||||
// the `errors` is a list of json encoded string.
|
||||
errorMessage = JSON.parse(payload.errors[0]).message;
|
||||
} catch {
|
||||
// do nothing, fallback to default message.
|
||||
}
|
||||
const errors = getErrors(payload);
|
||||
if (errors) {
|
||||
errorMessage = extractErrorMessage(errors);
|
||||
}
|
||||
|
||||
const type = isSuccess ? 'success' : 'error';
|
||||
@@ -73,7 +76,7 @@
|
||||
const resourceId = importData.resourceId ?? '';
|
||||
const [databaseId, tableId] = resourceId.split(':') ?? [];
|
||||
|
||||
const current = $importItems.get(importData.$id);
|
||||
const current = importItems.get(importData.$id);
|
||||
let tableName = current?.table ?? null;
|
||||
|
||||
if (!tableName && tableId) {
|
||||
@@ -91,30 +94,27 @@
|
||||
}
|
||||
|
||||
if (tableId && tableName === null) {
|
||||
importItems.update((items) => {
|
||||
const next = new Map(items);
|
||||
next.delete(importData.$id);
|
||||
return next;
|
||||
});
|
||||
const next = new Map(importItems);
|
||||
next.delete(importData.$id);
|
||||
importItems = next;
|
||||
return;
|
||||
}
|
||||
|
||||
importItems.update((items) => {
|
||||
const existing = items.get(importData.$id);
|
||||
const existing = importItems.get(importData.$id);
|
||||
|
||||
const isDone = (s: string) => s === 'completed' || s === 'failed';
|
||||
const isInProgress = (s: string) => ['pending', 'processing', 'uploading'].includes(s);
|
||||
const isDone = (s: string) => s === 'completed' || s === 'failed';
|
||||
const isInProgress = (s: string) => ['pending', 'processing', 'uploading'].includes(s);
|
||||
|
||||
const shouldSkip =
|
||||
(existing && isDone(existing.status) && isInProgress(status)) ||
|
||||
existing?.status === status;
|
||||
const shouldSkip =
|
||||
(existing && isDone(existing.status) && isInProgress(status)) ||
|
||||
existing?.status === status;
|
||||
|
||||
if (shouldSkip) return items;
|
||||
|
||||
const next = new Map(items);
|
||||
next.set(importData.$id, { status, table: tableName ?? undefined });
|
||||
return next;
|
||||
});
|
||||
if (!shouldSkip) {
|
||||
const next = new Map(importItems);
|
||||
const errors = getErrors(importData);
|
||||
next.set(importData.$id, { status, table: tableName ?? undefined, errors });
|
||||
importItems = next;
|
||||
}
|
||||
|
||||
if (status === 'completed' || status === 'failed') {
|
||||
await showCompletionNotification(databaseId, tableId, importData);
|
||||
@@ -122,10 +122,27 @@
|
||||
}
|
||||
|
||||
function clear() {
|
||||
importItems.update((items) => {
|
||||
items.clear();
|
||||
return items;
|
||||
});
|
||||
importItems = new Map();
|
||||
}
|
||||
|
||||
function getErrors(importData: Payload | Models.Migration): string[] | undefined {
|
||||
return Array.isArray(importData.errors) ? importData.errors : undefined;
|
||||
}
|
||||
|
||||
function parseError(error: string): string | CsvImportError {
|
||||
try {
|
||||
return JSON.parse(error) as CsvImportError;
|
||||
} catch {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
function extractErrorMessage(errors: string[]): string {
|
||||
try {
|
||||
return JSON.parse(errors[0]).message;
|
||||
} catch {
|
||||
return 'Import failed. Check your CSV for correct fields and required values.';
|
||||
}
|
||||
}
|
||||
|
||||
function graphSize(status: string): number {
|
||||
@@ -148,8 +165,9 @@
|
||||
const name = collectionName ? `<b>${collectionName}</b>` : '';
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return `CSV import completed${name ? ` to ${name}` : ''}`;
|
||||
case 'failed':
|
||||
return `Import to ${name} ${status}`;
|
||||
return `CSV import failed${name ? ` to ${name}` : ''}`;
|
||||
case 'processing':
|
||||
return `Importing CSV file${name ? ` to ${name}` : ''}`;
|
||||
default:
|
||||
@@ -169,7 +187,7 @@
|
||||
migrations.migrations.forEach(updateOrAddItem);
|
||||
});
|
||||
|
||||
return sdk.forConsoleIn(page.params.region).client.subscribe('console', (response) => {
|
||||
return realtime.forConsole(page.params.region, 'console', (response) => {
|
||||
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
|
||||
if (response.events.includes('migrations.*')) {
|
||||
updateOrAddItem(response.payload as Payload);
|
||||
@@ -177,8 +195,18 @@
|
||||
});
|
||||
});
|
||||
|
||||
$: isOpen = true;
|
||||
$: showCsvImportBox = $importItems.size > 0;
|
||||
let isOpen = $state(true);
|
||||
let showCsvImportBox = $derived(importItems.size > 0);
|
||||
|
||||
let showDetails = $state(false);
|
||||
let selectedErrors = $state<string[]>([]);
|
||||
let parsedErrors = $state<Array<string | CsvImportError>>([]);
|
||||
|
||||
function openDetails(errors: string[] | undefined) {
|
||||
selectedErrors = errors ?? [];
|
||||
parsedErrors = selectedErrors.map(parseError);
|
||||
showDetails = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if showCsvImportBox}
|
||||
@@ -187,26 +215,23 @@
|
||||
<header class="upload-box-header">
|
||||
<h4 class="upload-box-title">
|
||||
<Typography.Text variant="m-500">
|
||||
Importing rows ({$importItems.size})
|
||||
Importing rows ({importItems.size})
|
||||
</Typography.Text>
|
||||
</h4>
|
||||
<button
|
||||
class="upload-box-button"
|
||||
class:is-open={isOpen}
|
||||
aria-label="toggle upload box"
|
||||
on:click={() => (isOpen = !isOpen)}>
|
||||
onclick={() => (isOpen = !isOpen)}>
|
||||
<span class="icon-cheveron-up" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button
|
||||
class="upload-box-button"
|
||||
aria-label="close backup restore box"
|
||||
on:click={clear}>
|
||||
<button class="upload-box-button" aria-label="close CSV import box" onclick={clear}>
|
||||
<span class="icon-x" aria-hidden="true"></span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="upload-box-content-list">
|
||||
{#each [...$importItems.entries()] as [key, value] (key)}
|
||||
{#each [...importItems.entries()] as [key, value] (key)}
|
||||
<div class="upload-box-content" class:is-open={isOpen}>
|
||||
<ul class="upload-box-list">
|
||||
<li class="upload-box-item">
|
||||
@@ -222,6 +247,25 @@
|
||||
class:is-danger={value.status === 'failed'}
|
||||
style="--graph-size:{graphSize(value.status)}%">
|
||||
</div>
|
||||
{#if value.status === 'failed'}
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
gap="xs"
|
||||
alignItems="center"
|
||||
inline>
|
||||
<Icon
|
||||
icon={IconExclamationCircle}
|
||||
color="--fgcolor-error"
|
||||
size="s" />
|
||||
<Typography.Text color="--fgcolor-error">
|
||||
There was an import issue.
|
||||
<Link
|
||||
style="color: inherit"
|
||||
onclick={() => openDetails(value.errors)}
|
||||
>View details</Link>
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</section>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -232,6 +276,18 @@
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<Modal title="Import error" bind:show={showDetails} hideFooter>
|
||||
<Layout.Stack gap="m">
|
||||
<Layout.Stack>
|
||||
<Code
|
||||
language="json"
|
||||
code={JSON.stringify(parsedErrors, null, 2)}
|
||||
withCopy
|
||||
allowScroll />
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Modal>
|
||||
|
||||
<style lang="scss">
|
||||
.upload-box {
|
||||
display: flex;
|
||||
@@ -252,7 +308,7 @@
|
||||
}
|
||||
|
||||
.upload-box-content {
|
||||
width: 304px;
|
||||
width: 324px;
|
||||
}
|
||||
|
||||
.upload-box-button {
|
||||
|
||||
@@ -35,16 +35,22 @@
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<Table.Root columns={3} let:root>
|
||||
<Table.Root
|
||||
columns={[
|
||||
{ id: 'type', width: { min: 150 } },
|
||||
{ id: 'name', width: { min: 80 } },
|
||||
{ id: 'value', width: { min: 100 } }
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell {root}>CNAME</Table.Cell>
|
||||
<Table.Cell {root}>{subdomain}</Table.Cell>
|
||||
<Table.Cell {root}>
|
||||
<Table.Cell column="type" {root}>CNAME</Table.Cell>
|
||||
<Table.Cell column="name" {root}>{subdomain}</Table.Cell>
|
||||
<Table.Cell column="value" {root}>
|
||||
<InteractiveText
|
||||
variant="copy"
|
||||
isVisible
|
||||
|
||||
@@ -3,8 +3,15 @@
|
||||
|
||||
import { Badge, Layout, Typography, Table, InteractiveText } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let domain: string;
|
||||
export let verified = undefined;
|
||||
let {
|
||||
domain,
|
||||
verified,
|
||||
ruleStatus
|
||||
}: {
|
||||
domain: string;
|
||||
verified?: boolean;
|
||||
ruleStatus?: 'created' | 'verifying' | 'unverified' | 'verified';
|
||||
} = $props();
|
||||
|
||||
const nameserverList = $regionalConsoleVariables?._APP_DOMAINS_NAMESERVERS
|
||||
? $regionalConsoleVariables?._APP_DOMAINS_NAMESERVERS?.split(',')
|
||||
@@ -16,14 +23,24 @@
|
||||
<Typography.Text variant="l-500" color="--fgcolor-neutral-primary">
|
||||
{domain}
|
||||
</Typography.Text>
|
||||
{#if verified === true}
|
||||
<Badge variant="secondary" type="success" size="xs" content="Verified" />
|
||||
{:else if verified === false}
|
||||
<Badge variant="secondary" type="warning" size="xs" content="Verification failed" />
|
||||
{#if verified !== undefined}
|
||||
{#if ruleStatus === 'created'}
|
||||
<Badge variant="secondary" type="error" size="xs" content="Verification failed" />
|
||||
{:else if ruleStatus === 'verifying'}
|
||||
<Badge variant="secondary" size="xs" content="Generating certificate" />
|
||||
{:else if ruleStatus === 'unverified'}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
type="error"
|
||||
size="xs"
|
||||
content="Certificate generation failed" />
|
||||
{:else if verified === true}
|
||||
<Badge variant="secondary" type="success" size="xs" content="Verified" />
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<Typography.Text variant="m-400">
|
||||
Add the following nameservers on your DNS provider. Note that changes may take up to 48
|
||||
Add the following nameservers on your DNS provider. Note that DNS changes may take up to 48
|
||||
hours to propagate fully.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -9,20 +9,56 @@
|
||||
Alert
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
|
||||
import { getSubdomain } from '$lib/helpers/tlds';
|
||||
import { isCloud } from '$lib/system';
|
||||
|
||||
export let domain: string;
|
||||
export let verified = undefined;
|
||||
export let variant: 'cname' | 'a' | 'aaaa';
|
||||
export let service: 'sites' | 'general' = 'general';
|
||||
let {
|
||||
domain,
|
||||
verified,
|
||||
variant,
|
||||
service = 'general',
|
||||
ruleStatus,
|
||||
onNavigateToNameservers = () => {},
|
||||
onNavigateToA = () => {},
|
||||
onNavigateToAAAA = () => {}
|
||||
}: {
|
||||
domain: string;
|
||||
verified?: boolean;
|
||||
variant: 'cname' | 'a' | 'aaaa';
|
||||
service?: 'sites' | 'functions' | 'general';
|
||||
ruleStatus?: 'created' | 'verifying' | 'unverified' | 'verified';
|
||||
onNavigateToNameservers?: () => void;
|
||||
onNavigateToA?: () => void;
|
||||
onNavigateToAAAA?: () => void;
|
||||
} = $props();
|
||||
|
||||
let subdomain = domain?.split('.')?.slice(0, -2)?.join('.');
|
||||
const subdomain = $derived(getSubdomain(domain));
|
||||
const caaText = $derived(
|
||||
$regionalConsoleVariables._APP_DOMAIN_TARGET_CAA?.includes(' ')
|
||||
? $regionalConsoleVariables._APP_DOMAIN_TARGET_CAA
|
||||
: `0 issue "${$regionalConsoleVariables._APP_DOMAIN_TARGET_CAA}"`
|
||||
);
|
||||
const aTabVisible = $derived(
|
||||
!isCloud &&
|
||||
Boolean($regionalConsoleVariables._APP_DOMAIN_TARGET_A) &&
|
||||
$regionalConsoleVariables._APP_DOMAIN_TARGET_A !== '127.0.0.1'
|
||||
);
|
||||
const aaaaTabVisible = $derived(
|
||||
!isCloud &&
|
||||
Boolean($regionalConsoleVariables._APP_DOMAIN_TARGET_AAAA) &&
|
||||
$regionalConsoleVariables._APP_DOMAIN_TARGET_AAAA !== '::1'
|
||||
);
|
||||
|
||||
function setTarget() {
|
||||
switch (variant) {
|
||||
case 'cname':
|
||||
return service === 'general'
|
||||
? $regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME
|
||||
: $regionalConsoleVariables._APP_DOMAIN_SITES;
|
||||
if (service === 'sites') {
|
||||
return $regionalConsoleVariables._APP_DOMAIN_SITES;
|
||||
} else if (service === 'functions') {
|
||||
return $regionalConsoleVariables._APP_DOMAIN_FUNCTIONS;
|
||||
} else {
|
||||
return $regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME;
|
||||
}
|
||||
case 'a':
|
||||
return $regionalConsoleVariables._APP_DOMAIN_TARGET_A;
|
||||
case 'aaaa':
|
||||
@@ -37,42 +73,94 @@
|
||||
<Typography.Text variant="l-500" color="--fgcolor-neutral-primary">
|
||||
{domain}
|
||||
</Typography.Text>
|
||||
{#if verified === true}
|
||||
<Badge variant="secondary" type="success" size="xs" content="Verified" />
|
||||
{:else if verified === false}
|
||||
<Badge variant="secondary" type="warning" size="xs" content="Verification failed" />
|
||||
{#if verified !== undefined}
|
||||
{#if ruleStatus === 'created'}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
type="error"
|
||||
size="xs"
|
||||
content="Verification failed" />
|
||||
{:else if ruleStatus === 'verifying'}
|
||||
<Badge variant="secondary" size="xs" content="Generating certificate" />
|
||||
{:else if ruleStatus === 'unverified'}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
type="error"
|
||||
size="xs"
|
||||
content="Certificate generation failed" />
|
||||
{:else if verified === true}
|
||||
<Badge variant="secondary" type="success" size="xs" content="Verified" />
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<Typography.Text variant="m-400">
|
||||
Add the following record on your DNS provider. Note that DNS changes may take time to
|
||||
propagate fully.
|
||||
Add the following {$regionalConsoleVariables._APP_DOMAIN_TARGET_CAA
|
||||
? 'records'
|
||||
: 'record'} on your DNS provider. Note that DNS changes may take up to 48 hours to propagate
|
||||
fully.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<Table.Root columns={3} let:root>
|
||||
<Table.Root
|
||||
columns={[
|
||||
{ id: 'type', width: { min: 150 } },
|
||||
{ id: 'name', width: { min: 80 } },
|
||||
{ id: 'value', width: { min: 100 } }
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell {root}>{variant.toUpperCase()}</Table.Cell>
|
||||
<Table.Cell {root}>{subdomain || '@'}</Table.Cell>
|
||||
<Table.Cell {root}>
|
||||
<Table.Cell column="type" {root}>{variant.toUpperCase()}</Table.Cell>
|
||||
<Table.Cell column="name" {root}>
|
||||
<InteractiveText variant="copy" isVisible text={subdomain || '@'} />
|
||||
</Table.Cell>
|
||||
<Table.Cell column="value" {root}>
|
||||
<InteractiveText variant="copy" isVisible text={setTarget()} />
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{#if $regionalConsoleVariables._APP_DOMAIN_TARGET_CAA}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="type" {root}>
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<span>CAA</span>
|
||||
<Badge variant="secondary" size="xs" content="Recommended" />
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="name" {root}>@</Table.Cell>
|
||||
<Table.Cell column="value" {root}>
|
||||
<InteractiveText variant="copy" isVisible text={caaText} />
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/if}
|
||||
</Table.Root>
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
{#if variant === 'cname'}
|
||||
<Alert.Inline>
|
||||
If your domain uses CAA records, ensure certainly.com is authorized — otherwise, SSL
|
||||
setup may fail. A list of all domain providers and their DNS setting is available <Link
|
||||
variant="muted"
|
||||
external
|
||||
href="https://appwrite.io/docs/advanced/platform/custom-domains">here</Link
|
||||
>.
|
||||
</Alert.Inline>
|
||||
{#if variant === 'cname' && !subdomain}
|
||||
{#if isCloud}
|
||||
<Alert.Inline>
|
||||
Since <Badge variant="secondary" size="s" content={domain} /> is an apex domain,
|
||||
CNAME record is only supported by certain providers. If yours doesn't, please verify
|
||||
using
|
||||
<Link variant="muted" on:click={onNavigateToNameservers}>nameservers</Link> instead.
|
||||
</Alert.Inline>
|
||||
{:else if aTabVisible || aaaaTabVisible}
|
||||
<Alert.Inline>
|
||||
Since <Badge variant="secondary" size="s" content={domain} /> is an apex domain,
|
||||
CNAME record is only supported by certain providers. If yours doesn't, please verify
|
||||
using
|
||||
{#if aTabVisible}
|
||||
<Link variant="muted" on:click={onNavigateToA}>A record</Link>
|
||||
{#if aaaaTabVisible}
|
||||
or <Link variant="muted" on:click={onNavigateToAAAA}>AAAA record</Link
|
||||
>{/if}
|
||||
{:else if aaaaTabVisible}
|
||||
<Link variant="muted" on:click={onNavigateToAAAA}>AAAA record</Link>
|
||||
{/if} instead.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
{:else}
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
A list of all domain providers and their DNS setting is available <Link
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { createPopper, type Instance } from '@popperjs/core';
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
import { createEventDispatcher, onDestroy, onMount, hasContext } from 'svelte';
|
||||
|
||||
export let show = false;
|
||||
export let noArrow = false;
|
||||
@@ -17,6 +17,10 @@
|
||||
export let display = 'block';
|
||||
export let arrowSize = 10;
|
||||
export let isPopover = false;
|
||||
export let portal = false;
|
||||
|
||||
const inModal = hasContext('dialog-group');
|
||||
const shouldPortalTooltip = portal || inModal;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
blur: undefined;
|
||||
@@ -27,6 +31,22 @@
|
||||
let arrow: HTMLDivElement;
|
||||
let instance: Instance;
|
||||
|
||||
function portalAction(node: HTMLElement) {
|
||||
if (!shouldPortalTooltip) return;
|
||||
|
||||
const bodyElement = document.body;
|
||||
const target = inModal ? node.closest('dialog[open]') || bodyElement : bodyElement;
|
||||
target.appendChild(node);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
if (node.parentElement === target) {
|
||||
target.removeChild(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
instance = createPopper(element, tooltip, {
|
||||
placement,
|
||||
@@ -116,6 +136,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
use:portalAction
|
||||
class="drop-tooltip"
|
||||
bind:this={tooltip}
|
||||
class:u-width-full-line={fullWidth}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
export let paddingInline: string = undefined;
|
||||
export let resetListPadding: boolean = false;
|
||||
export let gap: string = undefined;
|
||||
export let portal = false;
|
||||
let classes: string = '';
|
||||
export { classes as class };
|
||||
</script>
|
||||
@@ -31,6 +32,7 @@
|
||||
{fullWidth}
|
||||
{wrapperFullWidth}
|
||||
{fixed}
|
||||
{portal}
|
||||
on:blur>
|
||||
<slot />
|
||||
<svelte:fragment slot="list">
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
<script>
|
||||
import { getNextTier, tierToPlan } from '$lib/stores/billing';
|
||||
<script lang="ts">
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { getNextTier, tierToPlan } from '$lib/stores/billing';
|
||||
import { Card, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let source = 'empty_state_card';
|
||||
export let responsive = false;
|
||||
|
||||
// type def for Layout.Stack!
|
||||
let direction: 'column' | 'row' | 'row-reverse' | 'column-reverse' = 'row';
|
||||
|
||||
$: direction = responsive ? ($isSmallViewport ? 'column' : 'row') : 'row';
|
||||
</script>
|
||||
|
||||
<Card.Base variant="secondary" padding="s" radius="s">
|
||||
<Layout.Stack direction="row" gap="l">
|
||||
<Layout.Stack {direction} gap="l">
|
||||
{#if $$slots?.image}
|
||||
<div style="flex-shrink:0">
|
||||
<slot name="image" />
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { InputDateTime, InputSelect } from '$lib/elements/forms';
|
||||
import { isSameDay, isValidDate, toLocaleDate } from '$lib/helpers/date';
|
||||
import { InputDate, InputSelect } from '$lib/elements/forms';
|
||||
import { isSameDay, isValidDate, toLocaleDate, toLocaleDateISO } from '$lib/helpers/date';
|
||||
|
||||
function incrementToday(value: number, type: 'day' | 'month' | 'year'): string {
|
||||
const date = new Date();
|
||||
@@ -74,6 +74,10 @@
|
||||
export let resourceType: string | 'key' | 'token' | undefined = 'key';
|
||||
export let expiryOptions: 'default' | 'limited' | ExpirationOptions[] = 'default';
|
||||
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
let minDate: string = toLocaleDateISO(tomorrow.getTime());
|
||||
|
||||
const options = Array.isArray(expiryOptions)
|
||||
? expiryOptions
|
||||
: expiryOptions === 'default'
|
||||
@@ -129,6 +133,11 @@
|
||||
if (hasUserInteracted && !isSameDay(new Date(expirationSelect), new Date(value))) {
|
||||
value = expirationSelect === 'custom' ? expirationCustom : expirationSelect;
|
||||
}
|
||||
|
||||
// Only convert to ISO date if value is not null
|
||||
if (value !== null) {
|
||||
value = toLocaleDateISO(new Date(value).getTime());
|
||||
}
|
||||
}
|
||||
|
||||
$: helper =
|
||||
@@ -147,11 +156,11 @@
|
||||
on:change={() => (hasUserInteracted = true)} />
|
||||
|
||||
{#if expirationSelect === 'custom'}
|
||||
<InputDateTime
|
||||
<InputDate
|
||||
required
|
||||
type="date"
|
||||
id="expire"
|
||||
min={minDate}
|
||||
label={dateSelectorLabel}
|
||||
bind:value={expirationCustom}
|
||||
on:change={() => (hasUserInteracted = true)} />
|
||||
on:input={() => (hasUserInteracted = true)} />
|
||||
{/if}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
import Form from '$lib/elements/forms/form.svelte';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
|
||||
export let title = '';
|
||||
export let show = false;
|
||||
export let size: 'small' | 'big' = 'big';
|
||||
export let icon: string = null;
|
||||
@@ -15,12 +15,14 @@
|
||||
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
|
||||
return;
|
||||
};
|
||||
export let title = '';
|
||||
|
||||
/**
|
||||
* needed when using `StatePicker`
|
||||
*/
|
||||
export let skipEnterOnBackdrop = false;
|
||||
|
||||
let backdrop: HTMLDivElement;
|
||||
|
||||
onMount(async () => {});
|
||||
|
||||
function handleBLur(event: MouseEvent) {
|
||||
if (event.target === backdrop) {
|
||||
trackEvent(Click.ModalCloseClick, {
|
||||
@@ -60,12 +62,12 @@
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
{#if show}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
bind:this={backdrop}
|
||||
onclick={handleBLur}
|
||||
class="payment-modal-backdrop"
|
||||
on:keyup={clickOnEnter}
|
||||
on:click={handleBLur}
|
||||
bind:this={backdrop}>
|
||||
onkeyup={skipEnterOnBackdrop ? undefined : clickOnEnter}>
|
||||
<div
|
||||
class="modal"
|
||||
class:is-small={size === 'small'}
|
||||
@@ -99,11 +101,12 @@
|
||||
style="--button-size:1.5rem;"
|
||||
aria-label="Close Modal"
|
||||
title="Close Modal"
|
||||
on:click={() =>
|
||||
onclick={() => {
|
||||
closeModal();
|
||||
trackEvent(Click.ModalCloseClick, {
|
||||
from: 'button'
|
||||
})}
|
||||
on:click={closeModal}>
|
||||
});
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true"></span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
import { IconInfo, IconPlus, IconViewGrid, IconViewList } from '@appwrite.io/pink-icons-svelte';
|
||||
import { showCreateBucket } from '$routes/(console)/project-[region]-[project]/storage/+page.svelte';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let show: boolean;
|
||||
export let mimeTypeQuery: string = 'image/';
|
||||
@@ -110,7 +111,10 @@
|
||||
}
|
||||
selectFile(file);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: e.message
|
||||
});
|
||||
} finally {
|
||||
uploading = false;
|
||||
}
|
||||
@@ -351,7 +355,9 @@
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Title size="s" color="--fgcolor-neutral-primary"
|
||||
>{localFileBucketTitle}</Typography.Title>
|
||||
<Upload.Dropzone bind:files={localFile} extensions={[allowedExtension]}>
|
||||
<Upload.Dropzone
|
||||
bind:files={localFile}
|
||||
extensions={allowedExtension === '*' ? undefined : [allowedExtension]}>
|
||||
<Layout.Stack alignItems="center" gap="s">
|
||||
<Layout.Stack alignItems="center" gap="s">
|
||||
<Layout.Stack
|
||||
@@ -370,7 +376,9 @@
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
</Layout.Stack>
|
||||
<svelte:fragment slot="tooltip"
|
||||
>{allowedExtension} files are allowed</svelte:fragment>
|
||||
>{allowedExtension === '*'
|
||||
? `${mimeTypeQuery} files are allowed`
|
||||
: `${allowedExtension} files are allowed`}</svelte:fragment>
|
||||
</Tooltip>
|
||||
</Layout.Stack>
|
||||
<Typography.Caption variant="400"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { TagList } from '.';
|
||||
import { toLocalDateTimeISO } from '$lib/helpers/date';
|
||||
import { Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
@@ -79,7 +80,7 @@
|
||||
value = column?.array ? [] : null;
|
||||
if (column?.type === 'datetime') {
|
||||
const now = new Date();
|
||||
value = now.toISOString().slice(0, 16);
|
||||
value = toLocalDateTimeISO(now.toISOString()).slice(0, 16);
|
||||
}
|
||||
// Initialize spatial data with default values
|
||||
if (column?.type === 'point') {
|
||||
@@ -108,7 +109,11 @@
|
||||
if (isDistanceOperator && distanceValue !== null && value !== null) {
|
||||
addFilter(columnsArray, columnId, operatorKey, value, arrayValues, distanceValue);
|
||||
} else {
|
||||
addFilter(columnsArray, columnId, operatorKey, value, arrayValues);
|
||||
const preparedValue =
|
||||
column?.type === 'datetime' && typeof value === 'string' && value
|
||||
? new Date(value).toISOString()
|
||||
: value;
|
||||
addFilter(columnsArray, columnId, operatorKey, preparedValue, arrayValues);
|
||||
}
|
||||
|
||||
columnId = null;
|
||||
|
||||
@@ -57,7 +57,11 @@
|
||||
}
|
||||
|
||||
function addCondition() {
|
||||
const newTag = generateTag(selectedColumn, operatorKey, value || arrayValues);
|
||||
const preparedValue =
|
||||
column?.type === 'datetime' && typeof value === 'string' && value
|
||||
? new Date(value).toISOString()
|
||||
: value;
|
||||
const newTag = generateTag(selectedColumn, operatorKey, preparedValue || arrayValues);
|
||||
if (localTags.some((t) => t.tag === newTag.tag && t.value === newTag.value)) {
|
||||
return;
|
||||
} else {
|
||||
@@ -66,7 +70,7 @@
|
||||
{
|
||||
id: selectedColumn,
|
||||
operator: operatorKey,
|
||||
value: value,
|
||||
value: preparedValue,
|
||||
arrayValues: arrayValues
|
||||
}
|
||||
];
|
||||
|
||||
@@ -45,7 +45,7 @@ export function addFilterAndApply(
|
||||
if (value || arrayValues?.length) {
|
||||
if (colId === 'sourceSize' || colId === 'buildSize') {
|
||||
addSizeFilter(value, colId, columns);
|
||||
} else if (colId === 'statusCode') {
|
||||
} else if (colId === 'responseStatusCode') {
|
||||
addStatusCodeFilter(value, colId, columns);
|
||||
} else if (
|
||||
colId === '$createdAt' ||
|
||||
|
||||
@@ -112,7 +112,6 @@
|
||||
repository.set(e);
|
||||
repositoryName = e.name;
|
||||
selectedRepository = e.id;
|
||||
connectRepo();
|
||||
}} />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { EmptySearch, Paginator } from '$lib/components';
|
||||
import { EmptySearch, PaginationInline } from '$lib/components';
|
||||
import { Button, InputSearch, InputSelect } from '$lib/elements/forms';
|
||||
import { timeFromNow } from '$lib/helpers/date';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -17,7 +17,7 @@
|
||||
import { IconLockClosed, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import ConnectGit from './connectGit.svelte';
|
||||
import SvgIcon from '../svgIcon.svelte';
|
||||
import { VCSDetectionType, type Models } from '@appwrite.io/console';
|
||||
import { Query, VCSDetectionType, type Models } from '@appwrite.io/console';
|
||||
import { getFrameworkIcon } from '$lib/stores/sites';
|
||||
import { connectGitHub } from '$lib/stores/git';
|
||||
import { page } from '$app/state';
|
||||
@@ -48,6 +48,8 @@
|
||||
let selectedInstallation = $state(null);
|
||||
let isLoadingRepositories = $state(null);
|
||||
let installationsMap = $state(null);
|
||||
let offset = $state(0);
|
||||
const limit = 5;
|
||||
|
||||
onMount(() => {
|
||||
loadInstallations();
|
||||
@@ -65,8 +67,18 @@
|
||||
300
|
||||
);
|
||||
|
||||
const loadRepositoryPage = async () => {
|
||||
isLoadingRepositories = true;
|
||||
try {
|
||||
await loadRepositories(selectedInstallation, search);
|
||||
} finally {
|
||||
isLoadingRepositories = false;
|
||||
}
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
if (selectedInstallation && search !== undefined) {
|
||||
offset = 0; // reset offset to 0 when search changes
|
||||
debouncedLoadRepositories(selectedInstallation, search);
|
||||
}
|
||||
});
|
||||
@@ -103,27 +115,23 @@
|
||||
}
|
||||
|
||||
async function loadRepositories(installationId: string, search: string) {
|
||||
if (product === 'functions') {
|
||||
$repositories.repositories = (
|
||||
(await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.vcs.listRepositories({
|
||||
installationId,
|
||||
type: VCSDetectionType.Runtime,
|
||||
search: search || undefined
|
||||
})) as unknown as Models.ProviderRepositoryRuntimeList
|
||||
).runtimeProviderRepositories; //TODO: remove forced cast after backend fixes
|
||||
} else {
|
||||
$repositories.repositories = (
|
||||
(await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.vcs.listRepositories({
|
||||
installationId,
|
||||
type: VCSDetectionType.Framework,
|
||||
search: search || undefined
|
||||
})) as unknown as Models.ProviderRepositoryFrameworkList
|
||||
).frameworkProviderRepositories;
|
||||
}
|
||||
const result = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.vcs.listRepositories({
|
||||
installationId,
|
||||
type:
|
||||
product === 'functions' ? VCSDetectionType.Runtime : VCSDetectionType.Framework,
|
||||
search: search || undefined,
|
||||
queries: [Query.limit(limit), Query.offset(offset)]
|
||||
});
|
||||
|
||||
$repositories.repositories =
|
||||
product === 'functions'
|
||||
? (result as unknown as Models.ProviderRepositoryRuntimeList)
|
||||
.runtimeProviderRepositories
|
||||
: (result as unknown as Models.ProviderRepositoryFrameworkList)
|
||||
.frameworkProviderRepositories; //TODO: remove forced cast after backend fixes
|
||||
$repositories.total = result.total;
|
||||
$repositories.search = search;
|
||||
$repositories.installationId = installationId;
|
||||
return $repositories.repositories;
|
||||
@@ -188,94 +196,85 @@
|
||||
{#if selectedInstallation}
|
||||
<!-- manual installation change -->
|
||||
{#if isLoadingRepositories}
|
||||
<SkeletonRepoList />
|
||||
{:else if $repositories?.repositories?.length}
|
||||
<Paginator
|
||||
items={$repositories.repositories}
|
||||
hideFooter={$repositories.repositories?.length <= 6}
|
||||
limit={6}>
|
||||
{#snippet children(
|
||||
paginatedItems: Models.ProviderRepositoryRuntime[] &
|
||||
Models.ProviderRepositoryFramework[]
|
||||
)}
|
||||
<Table.Root columns={1} let:root>
|
||||
{#each paginatedItems as repo}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell {root}>
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
{#if action === 'select'}
|
||||
<input
|
||||
class="is-small u-margin-inline-end-8"
|
||||
type="radio"
|
||||
name="repositories"
|
||||
bind:group={selectedRepository}
|
||||
onchange={() => repository.set(repo)}
|
||||
value={repo.id} />
|
||||
<SkeletonRepoList count={limit} />
|
||||
{:else if $repositories.total > 0}
|
||||
<Table.Root columns={1} let:root>
|
||||
{#each $repositories.repositories as repo}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell {root}>
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
{#if action === 'select'}
|
||||
<input
|
||||
class="is-small u-margin-inline-end-8"
|
||||
type="radio"
|
||||
name="repositories"
|
||||
bind:group={selectedRepository}
|
||||
onchange={() => repository.set(repo)}
|
||||
value={repo.id} />
|
||||
{/if}
|
||||
{#if product === 'sites'}
|
||||
{#if 'framework' in repo && repo?.framework && repo.framework !== 'other'}
|
||||
<Avatar size="xs" alt={repo.name}>
|
||||
<SvgIcon
|
||||
name={getFrameworkIcon(repo.framework)}
|
||||
iconSize="small" />
|
||||
</Avatar>
|
||||
{:else}
|
||||
<Avatar size="xs" alt={repo.name} empty />
|
||||
{/if}
|
||||
{:else}
|
||||
{@const iconName =
|
||||
'runtime' in repo && repo?.runtime
|
||||
? repo.runtime.split('-')[0]
|
||||
: undefined}
|
||||
<Avatar size="xs" alt={repo.name} empty={!iconName}>
|
||||
{#if iconName}
|
||||
<SvgIcon name={iconName} iconSize="small" />
|
||||
{/if}
|
||||
{#if product === 'sites'}
|
||||
{#if repo?.framework && repo.framework !== 'other'}
|
||||
<Avatar size="xs" alt={repo.name}>
|
||||
<SvgIcon
|
||||
name={getFrameworkIcon(repo.framework)}
|
||||
iconSize="small" />
|
||||
</Avatar>
|
||||
{:else}
|
||||
<Avatar size="xs" alt={repo.name} empty />
|
||||
{/if}
|
||||
{:else}
|
||||
{@const iconName = repo?.runtime
|
||||
? repo.runtime.split('-')[0]
|
||||
: undefined}
|
||||
<Avatar size="xs" alt={repo.name} empty={!iconName}>
|
||||
{#if iconName}
|
||||
<SvgIcon name={iconName} iconSize="small" />
|
||||
{/if}
|
||||
</Avatar>
|
||||
{/if}
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap="s"
|
||||
style="flex: 1; min-width: 0;">
|
||||
<Typography.Text
|
||||
</Avatar>
|
||||
{/if}
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap="s"
|
||||
style="flex: 1; min-width: 0;">
|
||||
<Typography.Text
|
||||
truncate
|
||||
color="--fgcolor-neutral-secondary"
|
||||
style="flex: 1; min-width: 0;">
|
||||
{repo.name}
|
||||
</Typography.Text>
|
||||
{#if repo.private}
|
||||
<Icon
|
||||
size="s"
|
||||
icon={IconLockClosed}
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
{/if}
|
||||
{#if !$isSmallViewport}
|
||||
<time datetime={repo.pushedAt}>
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
truncate
|
||||
color="--fgcolor-neutral-secondary"
|
||||
style="flex: 1; min-width: 0;">
|
||||
{repo.name}
|
||||
</Typography.Text>
|
||||
{#if repo.private}
|
||||
<Icon
|
||||
size="s"
|
||||
icon={IconLockClosed}
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
{/if}
|
||||
{#if !$isSmallViewport}
|
||||
<time datetime={repo.pushedAt}>
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
truncate
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
{timeFromNow(repo.pushedAt)}
|
||||
</Typography.Caption>
|
||||
</time>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#if action === 'button'}
|
||||
<PinkButton.Button
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
style="flex-shrink: 0;"
|
||||
on:click={() => connect(repo)}>
|
||||
Connect
|
||||
</PinkButton.Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{/snippet}
|
||||
</Paginator>
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
{timeFromNow(repo.pushedAt)}
|
||||
</Typography.Caption>
|
||||
</time>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#if action === 'button'}
|
||||
<PinkButton.Button
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
style="flex-shrink: 0;"
|
||||
on:click={() => connect(repo)}>
|
||||
Connect
|
||||
</PinkButton.Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{:else if search}
|
||||
<EmptySearch hidePages hidePagination bind:search target="repositories">
|
||||
<svelte:fragment slot="actions">
|
||||
@@ -293,6 +292,24 @@
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
{#if isLoadingRepositories || $repositories.total > 0}
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
wrap="wrap">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
Total results: {$repositories.total}
|
||||
</Typography.Text>
|
||||
<PaginationInline
|
||||
{limit}
|
||||
bind:offset
|
||||
total={$repositories.total}
|
||||
hidePages={true}
|
||||
on:change={loadRepositoryPage} />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Layout, Table, Skeleton } from '@appwrite.io/pink-svelte';
|
||||
|
||||
const { count = 4 }: { count?: number } = $props();
|
||||
</script>
|
||||
|
||||
<Table.Root columns={1} let:root>
|
||||
{#each Array(4) as _}
|
||||
{#each Array(count) as _}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell {root}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
|
||||
@@ -95,10 +95,11 @@
|
||||
export let tooltipPortal = false;
|
||||
export let tooltipDelay: number = 0;
|
||||
export let tooltipPlacement: TooltipPlacement = undefined;
|
||||
export let copyText: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
{#key value}
|
||||
<Copy {value} {event} {tooltipPortal} {tooltipDelay} {tooltipPlacement}>
|
||||
<Copy {value} {event} {tooltipPortal} {tooltipDelay} {tooltipPlacement} {copyText}>
|
||||
<Tag size="xs" variant="code">
|
||||
<Icon icon={IconDuplicate} size="s" slot="start" />
|
||||
<span
|
||||
|
||||
@@ -82,8 +82,11 @@ export { default as BottomSheet } from './bottom-sheet/index';
|
||||
export { default as Confirm } from './confirm.svelte';
|
||||
export { default as UsageCard } from './usageCard.svelte';
|
||||
export { default as ViewToggle } from './viewToggle.svelte';
|
||||
export { default as ApiEndpoint } from './apiEndpoint.svelte';
|
||||
export { default as RegionEndpoint } from './regionEndpoint.svelte';
|
||||
export { default as ExpirationInput } from './expirationInput.svelte';
|
||||
export { default as EstimatedCard } from './estimatedCard.svelte';
|
||||
export { default as SortButton, type SortDirection } from './sortButton.svelte';
|
||||
export { default as SendVerificationEmailModal } from './account/sendVerificationEmailModal.svelte';
|
||||
export { default as MultiSelectionTable } from './multiSelectTable.svelte';
|
||||
export * from './multiSelectTable.svelte';
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
export let sum: number;
|
||||
export let limit: number;
|
||||
export let name: string;
|
||||
export let pageParam: string = 'page';
|
||||
export let removeOnFirstPage: boolean = false;
|
||||
|
||||
const options = [
|
||||
{ label: '6', value: 6 },
|
||||
@@ -23,10 +25,17 @@
|
||||
url.searchParams.set('limit', limit.toString());
|
||||
await preferences.setLimit(limit);
|
||||
|
||||
if (url.searchParams.has('page')) {
|
||||
const page = Number(url.searchParams.get('page'));
|
||||
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
if (url.searchParams.has(pageParam)) {
|
||||
const page = Number(url.searchParams.get(pageParam));
|
||||
const prev =
|
||||
Number.isFinite(previousLimit) && previousLimit > 0 ? previousLimit : limit;
|
||||
const newPage = Math.floor(((page - 1) * prev) / limit) + 1;
|
||||
const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
|
||||
if (removeOnFirstPage && safePage === 1) {
|
||||
url.searchParams.delete(pageParam);
|
||||
} else {
|
||||
url.searchParams.set(pageParam, safePage.toString());
|
||||
}
|
||||
}
|
||||
|
||||
await goto(url.toString());
|
||||
|
||||
@@ -50,15 +50,14 @@
|
||||
})();
|
||||
|
||||
onMount(() => {
|
||||
return realtime
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.subscribe<Models.Migration>(['console'], async (response) => {
|
||||
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
|
||||
if (response.events.includes('migrations.*')) {
|
||||
if (response.payload.source === 'Backup') return;
|
||||
migration = response.payload;
|
||||
}
|
||||
});
|
||||
return realtime.forProject(page.params.region, ['console'], async (response) => {
|
||||
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
|
||||
if (response.events.includes('migrations.*')) {
|
||||
const payload = response.payload as Models.Migration;
|
||||
if (payload.source === 'Backup') return;
|
||||
migration = payload;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
<script lang="ts" module>
|
||||
export type DeleteOperationState = {
|
||||
error?: Error;
|
||||
deleted: string[];
|
||||
} | void;
|
||||
|
||||
export type DeleteOperation = (
|
||||
deleteFn: (id: string) => Promise<unknown>,
|
||||
batchSize?: number
|
||||
) => Promise<Exclude<DeleteOperationState, void>>;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import {
|
||||
Table,
|
||||
Badge,
|
||||
Typography,
|
||||
FloatingActionBar,
|
||||
type TableColumn,
|
||||
type TableRootProps
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import Confirm from './confirm.svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
let {
|
||||
columns,
|
||||
resource,
|
||||
allowSelection = true,
|
||||
confirmDeletion = true,
|
||||
showSuccessNotification = true,
|
||||
computeKey = 'multiSelectionTable',
|
||||
header,
|
||||
children,
|
||||
onDelete,
|
||||
onCancel,
|
||||
deleteContent,
|
||||
deleteContentNotice
|
||||
}: {
|
||||
resource: string;
|
||||
allowSelection?: boolean;
|
||||
confirmDeletion?: boolean;
|
||||
computeKey?: string | number;
|
||||
showSuccessNotification?: boolean;
|
||||
columns: Array<TableColumn> | number;
|
||||
header: Snippet<[root: TableRootProps]>;
|
||||
children: Snippet<[root: TableRootProps]>;
|
||||
deleteContent?: Snippet<[count: number]>;
|
||||
deleteContentNotice?: Snippet;
|
||||
onDelete?: (
|
||||
batchDelete: DeleteOperation,
|
||||
/**
|
||||
* this is useful when you have a custom deletion logic
|
||||
* and the default `batchDelete` helper doesn't fit the use-case!
|
||||
*/
|
||||
selectedRows: string[]
|
||||
) => Promise<DeleteOperationState> | DeleteOperationState;
|
||||
onCancel?: () => Promise<void> | void;
|
||||
} = $props();
|
||||
|
||||
let selectedRows: string[] = $state([]);
|
||||
let disableModal: boolean = $state(false);
|
||||
let onDeleteError: string | null = $state(null);
|
||||
let showConfirmDeletion: boolean = $state(false);
|
||||
|
||||
function notifySuccess(count: number) {
|
||||
if (!showSuccessNotification) return;
|
||||
if (count === 0) return;
|
||||
|
||||
const label = `${resource}${count > 1 ? 's' : ''}`;
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${count} ${label} deleted`
|
||||
});
|
||||
}
|
||||
|
||||
// this is kept very basic!
|
||||
function getPluralResource() {
|
||||
if (resource.endsWith('ty')) {
|
||||
return `${resource}ies`;
|
||||
}
|
||||
|
||||
return `${resource}s`;
|
||||
}
|
||||
|
||||
async function batchDelete(
|
||||
ids: string[],
|
||||
deleteFn: (id: string) => Promise<unknown>,
|
||||
batchSize: number | undefined = undefined
|
||||
): Promise<Exclude<DeleteOperationState, void>> {
|
||||
const deleted: string[] = [];
|
||||
let firstError: Error | undefined;
|
||||
|
||||
// prevent infinite loop
|
||||
if (batchSize !== undefined) {
|
||||
batchSize = Math.max(1, Math.floor(Math.abs(batchSize)));
|
||||
}
|
||||
|
||||
async function processBatch(batch: string[]) {
|
||||
// build promises
|
||||
const results = await Promise.allSettled(batch.map((id) => deleteFn(id)));
|
||||
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
// success, log it!
|
||||
deleted.push(batch[index]);
|
||||
} else if (!firstError) {
|
||||
// error
|
||||
firstError =
|
||||
result.reason instanceof Error
|
||||
? result.reason
|
||||
: new Error(String(result.reason));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// batch when needed.
|
||||
// example: >= 100 items to delete!
|
||||
if (batchSize && batchSize < ids.length) {
|
||||
for (let i = 0; i < ids.length; i += batchSize) {
|
||||
const batch = ids.slice(i, i + batchSize);
|
||||
await processBatch(batch);
|
||||
}
|
||||
} else {
|
||||
await processBatch(ids);
|
||||
}
|
||||
|
||||
return {
|
||||
deleted,
|
||||
error: firstError
|
||||
};
|
||||
}
|
||||
|
||||
async function consumeDeleteOperation() {
|
||||
const state = await onDelete?.((deleteFn, batchSize) => {
|
||||
return batchDelete(selectedRows, deleteFn, batchSize);
|
||||
}, selectedRows);
|
||||
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const deletedCount = state.deleted.length;
|
||||
selectedRows = selectedRows.filter((id) => !state.deleted.includes(id));
|
||||
|
||||
if (state.error) {
|
||||
onDeleteError = `Some ${getPluralResource()} were not deleted. Error: ${state.error.message}`;
|
||||
return false;
|
||||
}
|
||||
|
||||
notifySuccess(deletedCount);
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key computeKey}
|
||||
<Table.Root let:root {columns} {allowSelection} bind:selectedRows>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
{@render header?.(root)}
|
||||
</svelte:fragment>
|
||||
|
||||
{@render children(root)}
|
||||
</Table.Root>
|
||||
|
||||
{#if allowSelection && selectedRows.length > 0}
|
||||
<FloatingActionBar>
|
||||
<svelte:fragment slot="start">
|
||||
<Badge content={selectedRows.length.toString()} />
|
||||
<span>
|
||||
{selectedRows.length > 1 ? getPluralResource() : resource}
|
||||
selected
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="end">
|
||||
<Button
|
||||
text
|
||||
on:click={() => {
|
||||
onCancel?.();
|
||||
selectedRows = [];
|
||||
}}>Cancel</Button>
|
||||
<Button
|
||||
secondary
|
||||
on:click={async () => {
|
||||
if (confirmDeletion) {
|
||||
showConfirmDeletion = true;
|
||||
} else {
|
||||
await consumeDeleteOperation();
|
||||
}
|
||||
}}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</FloatingActionBar>
|
||||
{/if}
|
||||
|
||||
{#if allowSelection && confirmDeletion}
|
||||
<Confirm
|
||||
submissionLoader
|
||||
confirmDeletion
|
||||
error={onDeleteError}
|
||||
disabled={disableModal}
|
||||
title="Delete {getPluralResource()}"
|
||||
bind:open={showConfirmDeletion}
|
||||
onSubmit={async () => {
|
||||
disableModal = true;
|
||||
onDeleteError = null;
|
||||
|
||||
const allDeleted = await consumeDeleteOperation();
|
||||
|
||||
if (allDeleted) {
|
||||
showConfirmDeletion = false;
|
||||
}
|
||||
|
||||
disableModal = false;
|
||||
}}>
|
||||
<Typography.Text>
|
||||
{@const selectionCount = selectedRows.length}
|
||||
{#if deleteContent}
|
||||
<!-- some show extra info -->
|
||||
{@render deleteContent(selectionCount)}
|
||||
{:else}
|
||||
Are you sure you want to delete <strong>{selectionCount}</strong>
|
||||
{selectionCount > 1 ? getPluralResource() : resource}?
|
||||
{/if}
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text variant="m-500">
|
||||
{#if deleteContentNotice}
|
||||
<!-- some show extra info -->
|
||||
{@render deleteContentNotice()}
|
||||
{:else}
|
||||
This action is irreversible.
|
||||
{/if}
|
||||
</Typography.Text>
|
||||
</Confirm>
|
||||
{/if}
|
||||
{/key}
|
||||
@@ -6,15 +6,21 @@
|
||||
export let limit: number;
|
||||
export let offset: number;
|
||||
export let useCreateLink = true;
|
||||
export let pageParam: string = 'page';
|
||||
export let removeOnFirstPage: boolean = false;
|
||||
|
||||
$: currentPage = Math.floor(offset / limit + 1);
|
||||
|
||||
function getLink(page: number): string {
|
||||
const url = new URL(pageStore.url);
|
||||
if (page === 1) {
|
||||
url.searchParams.delete('page');
|
||||
if (removeOnFirstPage) {
|
||||
url.searchParams.delete(pageParam);
|
||||
} else {
|
||||
url.searchParams.set(pageParam, '1');
|
||||
}
|
||||
} else {
|
||||
url.searchParams.set('page', page.toString());
|
||||
url.searchParams.set(pageParam, page.toString());
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
|
||||
@@ -8,13 +8,19 @@
|
||||
offset,
|
||||
total,
|
||||
name,
|
||||
useCreateLink = true
|
||||
useCreateLink = true,
|
||||
pageParam = 'page',
|
||||
removeOnFirstPage = false,
|
||||
...restProps
|
||||
}: {
|
||||
limit: number;
|
||||
offset: number;
|
||||
total: number;
|
||||
name: string;
|
||||
useCreateLink?: boolean;
|
||||
pageParam?: string;
|
||||
removeOnFirstPage?: boolean;
|
||||
[key: string]: unknown;
|
||||
} = $props();
|
||||
|
||||
const showLimit = $derived(!!useCreateLink);
|
||||
@@ -22,10 +28,17 @@
|
||||
const alignItems = $derived(showLimit ? 'center' : 'flex-end');
|
||||
</script>
|
||||
|
||||
<Layout.Stack wrap="wrap" {direction} {alignItems} justifyContent="space-between">
|
||||
<Layout.Stack wrap="wrap" {direction} {alignItems} justifyContent="space-between" {...restProps}>
|
||||
{#if showLimit}
|
||||
<Limit {limit} sum={total} {name} />
|
||||
<Limit {limit} sum={total} {name} {pageParam} {removeOnFirstPage} />
|
||||
{/if}
|
||||
|
||||
<Pagination on:page {limit} {offset} sum={total} {useCreateLink} />
|
||||
<Pagination
|
||||
on:page
|
||||
{limit}
|
||||
{offset}
|
||||
sum={total}
|
||||
{useCreateLink}
|
||||
{pageParam}
|
||||
{removeOnFirstPage} />
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
name = 'items',
|
||||
gap = 's',
|
||||
offset = $bindable(0),
|
||||
children
|
||||
children,
|
||||
...restProps
|
||||
}: {
|
||||
items: T[];
|
||||
limit?: number;
|
||||
@@ -26,6 +27,7 @@
|
||||
| undefined;
|
||||
offset?: number;
|
||||
children: Snippet<[T[], number]>;
|
||||
[key: string]: unknown;
|
||||
} = $props();
|
||||
|
||||
let total = $derived(items.length);
|
||||
@@ -33,7 +35,7 @@
|
||||
let paginatedItems = $derived(items.slice(offset, offset + limit));
|
||||
</script>
|
||||
|
||||
<Layout.Stack {gap}>
|
||||
<Layout.Stack {gap} {...restProps}>
|
||||
{@render children(paginatedItems, limit)}
|
||||
|
||||
{#if !hideFooter}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
|
||||
import type { PinkColumn } from '$lib/helpers/types';
|
||||
import { Card } from '$lib/components';
|
||||
import { TableScroll } from '$lib/elements/table';
|
||||
|
||||
export let withCreate = false;
|
||||
export let permissions: string[] = [];
|
||||
@@ -113,32 +114,18 @@
|
||||
}, []);
|
||||
}
|
||||
|
||||
function sortRoles([a]: [string, Permission], [b]: [string, Permission]) {
|
||||
if ((a === 'any') !== (b === 'any')) {
|
||||
return a === 'any' ? -1 : 1;
|
||||
}
|
||||
if ((a === 'users') !== (b === 'users')) {
|
||||
return a === 'users' ? -1 : 1;
|
||||
}
|
||||
if ((a === 'guests') !== (b === 'guests')) {
|
||||
return a === 'guests' ? -1 : 1;
|
||||
}
|
||||
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
|
||||
const columns: PinkColumn[] = [
|
||||
{ id: 'role', width: { min: 80 } },
|
||||
{ id: 'create', width: { min: 80 }, hide: !withCreate },
|
||||
{ id: 'read', width: { min: 80 } },
|
||||
{ id: 'update', width: { min: 80 } },
|
||||
{ id: 'delete', width: { min: 80 } },
|
||||
{ id: 'role', width: { min: 220 } },
|
||||
{ id: 'create', width: { min: 64 }, hide: !withCreate },
|
||||
{ id: 'read', width: { min: 64 } },
|
||||
{ id: 'update', width: { min: 64 } },
|
||||
{ id: 'delete', width: { min: 64 } },
|
||||
{ id: 'action', width: 40 }
|
||||
];
|
||||
</script>
|
||||
|
||||
{#if [...$groups]?.length}
|
||||
<div class="table-wrapper">
|
||||
<TableScroll>
|
||||
<Table.Root {columns} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="role" {root}>Role</Table.Header.Cell>
|
||||
@@ -150,13 +137,14 @@
|
||||
<Table.Header.Cell column="delete" {root}>Delete</Table.Header.Cell>
|
||||
<Table.Header.Cell column="action" {root} />
|
||||
</svelte:fragment>
|
||||
{#each [...$groups].sort(sortRoles) as [role, permission] (role)}
|
||||
{#each [...$groups] as [role, permission] (role)}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="role" {root}>
|
||||
<Row {role} />
|
||||
</Table.Cell>
|
||||
<Table.Cell column="create" {root}>
|
||||
<Selector.Checkbox
|
||||
size="s"
|
||||
checked={permission.create}
|
||||
on:change={() => togglePermission(role, 'create')} />
|
||||
</Table.Cell>
|
||||
@@ -187,7 +175,7 @@
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
</div>
|
||||
</TableScroll>
|
||||
|
||||
<div>
|
||||
<Actions
|
||||
@@ -223,14 +211,3 @@
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.table-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { type ComponentProps, type Snippet, onMount } from 'svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { AvatarInitials } from '../';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import {
|
||||
Button,
|
||||
Badge,
|
||||
Divider,
|
||||
Icon,
|
||||
InteractiveText,
|
||||
Layout,
|
||||
Link,
|
||||
Popover,
|
||||
@@ -13,31 +16,134 @@
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import Avatar from '../avatar.svelte';
|
||||
import { IconAnonymous, IconExternalLink, IconMinusSm } from '@appwrite.io/pink-icons-svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { IconAnonymous, IconMinusSm } from '@appwrite.io/pink-icons-svelte';
|
||||
import { page } from '$app/state';
|
||||
import { menuOpen } from '$lib/components/menu/store';
|
||||
import { base } from '$app/paths';
|
||||
import { formatName } from '$lib/helpers/string';
|
||||
|
||||
export let role: string;
|
||||
type PermissionData = Partial<Models.User & Models.Team> & {
|
||||
notFound?: boolean;
|
||||
roleName?: string;
|
||||
customName?: string;
|
||||
};
|
||||
|
||||
async function getData(
|
||||
permission: string
|
||||
): Promise<
|
||||
Partial<Models.User<Record<string, unknown>> & Models.Team<Record<string, unknown>>>
|
||||
> {
|
||||
const role = permission.split(':')[0];
|
||||
const id = permission.split(':')[1].split('/')[0];
|
||||
if (role === 'user') {
|
||||
const user = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.users.get({ userId: id });
|
||||
return user;
|
||||
const permissionDataCache: Map<string, Promise<PermissionData>> = new Map();
|
||||
|
||||
interface Props {
|
||||
role: string;
|
||||
placement?: ComponentProps<Popover>['placement'];
|
||||
children?: Snippet;
|
||||
onNotFound?: (role: string) => void;
|
||||
}
|
||||
|
||||
let { role, placement = 'bottom-start', children, onNotFound }: Props = $props();
|
||||
|
||||
type ParsedPermission = {
|
||||
type: 'user' | 'team' | 'other';
|
||||
id: string;
|
||||
roleName?: string;
|
||||
isValid: boolean;
|
||||
};
|
||||
|
||||
function parsePermission(permission: string): ParsedPermission {
|
||||
try {
|
||||
const [type, rest] = permission.split(':');
|
||||
if (!rest) {
|
||||
return { type: 'other', id: permission, isValid: false };
|
||||
}
|
||||
|
||||
const [id, roleName] = rest.split('/');
|
||||
if (!id) {
|
||||
return { type: 'other', id: permission, isValid: false };
|
||||
}
|
||||
|
||||
if (type === 'user' || type === 'team') {
|
||||
return {
|
||||
type: type as 'user' | 'team',
|
||||
id,
|
||||
roleName,
|
||||
isValid: true
|
||||
};
|
||||
}
|
||||
|
||||
return { type: 'other', id: permission, isValid: false };
|
||||
} catch (error) {
|
||||
return { type: 'other', id: permission, isValid: false };
|
||||
}
|
||||
if (role === 'team') {
|
||||
const team = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.teams.get({ teamId: id });
|
||||
return team;
|
||||
}
|
||||
|
||||
async function fetchPermissionData(parsed: ParsedPermission): Promise<PermissionData> {
|
||||
if (!parsed.isValid || parsed.type === 'other') {
|
||||
return { notFound: true, roleName: parsed.roleName, customName: parsed.id };
|
||||
}
|
||||
|
||||
if (parsed.type === 'user') {
|
||||
try {
|
||||
return await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.users.get({ userId: parsed.id });
|
||||
} catch (error) {
|
||||
return { notFound: true, roleName: parsed.roleName, customName: parsed.id };
|
||||
}
|
||||
}
|
||||
|
||||
if (parsed.type === 'team') {
|
||||
try {
|
||||
return await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.teams.get({ teamId: parsed.id });
|
||||
} catch (error) {
|
||||
return { notFound: true, roleName: parsed.roleName, customName: parsed.id };
|
||||
}
|
||||
}
|
||||
|
||||
return { notFound: true, roleName: parsed.roleName, customName: parsed.id };
|
||||
}
|
||||
|
||||
async function getData(permission: string): Promise<PermissionData> {
|
||||
const cached = permissionDataCache.get(permission);
|
||||
if (cached) return cached;
|
||||
|
||||
const parsed = parsePermission(permission);
|
||||
const fetchPromise = fetchPermissionData(parsed);
|
||||
|
||||
permissionDataCache.set(permission, fetchPromise);
|
||||
return fetchPromise;
|
||||
}
|
||||
|
||||
async function verifyExistence() {
|
||||
try {
|
||||
const data = await getData(role);
|
||||
if (data?.notFound) {
|
||||
onNotFound?.(role);
|
||||
}
|
||||
} catch {
|
||||
// Intentionally ignore fetch/parse errors; UI handles missing data state
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
verifyExistence();
|
||||
});
|
||||
|
||||
let isMouseOverTooltip = $state(false);
|
||||
function hidePopover(hideTooltip: () => void, timeout = true) {
|
||||
if (!timeout) {
|
||||
isMouseOverTooltip = false;
|
||||
return hideTooltip();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!isMouseOverTooltip) {
|
||||
hideTooltip();
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
function isCustomPermission(role: string): boolean {
|
||||
const parsed = parsePermission(role);
|
||||
return !!parsed.roleName || !parsed.isValid;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -48,70 +154,190 @@
|
||||
{:else if role === 'any'}
|
||||
<div>Any</div>
|
||||
{:else}
|
||||
<Popover let:toggle placement="bottom-start">
|
||||
<Link.Button on:click={toggle}>{role}</Link.Button>
|
||||
<div let:showing slot="tooltip" style:width="200px">
|
||||
{#key showing}
|
||||
{#await getData(role)}
|
||||
<Layout.Stack alignItems="center">
|
||||
<Spinner />
|
||||
</Layout.Stack>
|
||||
{:then data}
|
||||
{@const isUser = role.startsWith('user')}
|
||||
{@const isTeam = role.startsWith('team')}
|
||||
{@const isAnonymous = !data.email && !data.phone && !data.name && isUser}
|
||||
<Layout.Stack>
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center">
|
||||
{#if isAnonymous}
|
||||
<Avatar alt="avatar" size="xs">
|
||||
<Icon icon={IconAnonymous} size="s" />
|
||||
</Avatar>
|
||||
{:else if data.name}
|
||||
<AvatarInitials name={data.name} size="xs" />
|
||||
{:else}
|
||||
<Avatar alt="avatar" size="xs">
|
||||
<Icon icon={IconMinusSm} size="s" />
|
||||
</Avatar>
|
||||
{/if}
|
||||
<Typography.Text truncate color="--fgcolor-neutral-primary">
|
||||
{data.name ?? data?.email ?? data?.phone ?? '-'}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Popover let:show let:hide {placement} portal>
|
||||
<button
|
||||
type="button"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
onkeydown={(e) => e.stopPropagation()}
|
||||
onmouseenter={() => {
|
||||
if (!$menuOpen) {
|
||||
setTimeout(show, 150);
|
||||
}
|
||||
}}
|
||||
onmouseleave={() => hidePopover(hide)}>
|
||||
{@render children?.()}
|
||||
{#if isCustomPermission(role)}
|
||||
<Typography.Text style="text-decoration: underline;">
|
||||
{formatName(role, $isSmallViewport ? 8 : 15)}
|
||||
</Typography.Text>
|
||||
{:else}
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center" inline>
|
||||
<Typography.Text>
|
||||
{#await getData(role)}
|
||||
{role}
|
||||
{:then data}
|
||||
{formatName(
|
||||
data.name ?? data?.email ?? data?.phone ?? '-',
|
||||
$isSmallViewport ? 16 : 20
|
||||
)}
|
||||
{/await}
|
||||
</Typography.Text>
|
||||
<Badge
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
content={role.startsWith('user') ? 'User' : 'Team'} />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<Divider />
|
||||
{#if isUser}
|
||||
{#if data?.email}
|
||||
<Typography.Text truncate>Email: {data?.email}</Typography.Text>
|
||||
{/if}
|
||||
{#if data?.phone}
|
||||
<Typography.Text truncate>Phone: {data?.phone}</Typography.Text>
|
||||
{/if}
|
||||
<div>
|
||||
<Button.Anchor
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/auth/user-${data?.$id}`}
|
||||
size="xs"
|
||||
target="_blank"
|
||||
variant="secondary">
|
||||
View user
|
||||
<Icon slot="end" icon={IconExternalLink} size="s" />
|
||||
</Button.Anchor>
|
||||
</div>
|
||||
{:else if isTeam}
|
||||
<Typography.Text>Members: {data?.total}</Typography.Text>
|
||||
<div>
|
||||
<Button.Anchor
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${data?.$id}`}
|
||||
size="s"
|
||||
target="_blank"
|
||||
variant="secondary">
|
||||
View team
|
||||
<Icon slot="end" icon={IconExternalLink} size="s" />
|
||||
</Button.Anchor>
|
||||
</div>
|
||||
<div
|
||||
let:hide
|
||||
let:showing
|
||||
slot="tooltip"
|
||||
role="tooltip"
|
||||
class="popover"
|
||||
onmouseenter={() => (isMouseOverTooltip = true)}
|
||||
onmouseleave={() => hidePopover(hide, false)}>
|
||||
{#if showing}
|
||||
<Layout.Stack gap="s" alignContent="flex-start">
|
||||
{#await getData(role)}
|
||||
<Layout.Stack alignItems="center">
|
||||
<Spinner />
|
||||
</Layout.Stack>
|
||||
{:then data}
|
||||
{#if data.notFound}
|
||||
<Layout.Stack gap="s" alignItems="flex-start">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
gap="s"
|
||||
alignItems="center"
|
||||
justifyContent="flex-start">
|
||||
<Avatar alt="avatar" size="m">
|
||||
<Icon icon={IconMinusSm} size="s" />
|
||||
</Avatar>
|
||||
|
||||
<Layout.Stack alignItems="flex-start" gap="xxs">
|
||||
<Layout.Stack style="padding-left: 0.25rem;">
|
||||
<Typography.Text
|
||||
size="m"
|
||||
color="--fgcolor-neutral-primary">
|
||||
{formatName(
|
||||
data.customName ?? '',
|
||||
$isSmallViewport ? 20 : 28
|
||||
)}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{#if data.roleName}
|
||||
<InteractiveText
|
||||
isVisible
|
||||
variant="copy"
|
||||
text={formatName(
|
||||
data.roleName,
|
||||
$isSmallViewport ? 20 : 28
|
||||
)}
|
||||
value={data.roleName} />
|
||||
{:else}
|
||||
<InteractiveText
|
||||
isVisible
|
||||
variant="copy"
|
||||
text={formatName(role, $isSmallViewport ? 20 : 28)}
|
||||
value={role} />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
{@const isUser = role.startsWith('user')}
|
||||
{@const isAnonymous =
|
||||
!data.email && !data.phone && !data.name && isUser}
|
||||
{@const parsed = parsePermission(role)}
|
||||
{@const id = parsed.id}
|
||||
|
||||
<Layout.Stack gap="s" alignItems="flex-start">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
gap="s"
|
||||
alignItems="center"
|
||||
justifyContent="flex-start">
|
||||
{#if isAnonymous}
|
||||
<Avatar alt="avatar" size="m">
|
||||
<Icon icon={IconAnonymous} size="s" />
|
||||
</Avatar>
|
||||
{:else if data.name}
|
||||
<AvatarInitials name={data.name} size="m" />
|
||||
{:else}
|
||||
<Avatar alt="avatar" size="m">
|
||||
<Icon icon={IconMinusSm} size="s" />
|
||||
</Avatar>
|
||||
{/if}
|
||||
|
||||
<Layout.Stack alignItems="flex-start" gap="xxs">
|
||||
<Layout.Stack style="padding-left: 0.25rem;">
|
||||
<Link.Anchor
|
||||
variant="quiet"
|
||||
href={role.startsWith('user')
|
||||
? `${base}/project-${page.params.region}-${page.params.project}/auth/user-${id}`
|
||||
: `${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${id}`}>
|
||||
<Typography.Text
|
||||
size="m"
|
||||
color="--fgcolor-neutral-primary">
|
||||
{formatName(
|
||||
data.name ??
|
||||
data?.email ??
|
||||
data?.phone ??
|
||||
'-',
|
||||
$isSmallViewport ? 18 : 24
|
||||
)}
|
||||
</Typography.Text>
|
||||
</Link.Anchor>
|
||||
</Layout.Stack>
|
||||
<InteractiveText
|
||||
isVisible
|
||||
variant="copy"
|
||||
text={id}
|
||||
value={id} />
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
{#if isUser && (data.email || data.phone)}
|
||||
<Divider />
|
||||
<Layout.Stack gap="xxs" alignItems="flex-start">
|
||||
{#if data.email}
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--fgcolor-neutral-secondary">
|
||||
Email: {formatName(
|
||||
data.email,
|
||||
$isSmallViewport ? 24 : 32
|
||||
)}
|
||||
</Typography.Caption>
|
||||
{/if}
|
||||
{#if data.phone}
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--fgcolor-neutral-secondary">
|
||||
Phone: {data.phone}
|
||||
</Typography.Caption>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/await}
|
||||
{/key}
|
||||
{/await}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</div>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.popover {
|
||||
display: flex;
|
||||
width: 280px;
|
||||
min-width: 260px;
|
||||
padding: var(--space-5, 10px) var(--space-6, 12px);
|
||||
align-items: flex-start;
|
||||
gap: var(--gap-XXS, 4px);
|
||||
margin: -1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<Card.Base padding="none">
|
||||
<Empty title="You have no teams. Create a team to see them here." type="secondary">
|
||||
<Empty title="No teams yet. Create a team to see it here." type="secondary">
|
||||
<Typography.Text slot="description">
|
||||
Need a hand? Learn more in our <Link.Anchor
|
||||
href="https://appwrite.io/docs/products/auth/teams"
|
||||
|
||||
@@ -176,10 +176,10 @@
|
||||
</div>
|
||||
{:else}
|
||||
<Card.Base padding="none">
|
||||
<Empty title="You have no users. Create a user to see them here." type="secondary">
|
||||
<Empty title="No users yet. Create a user to see it here." type="secondary">
|
||||
<Typography.Text slot="description">
|
||||
Need a hand? Learn more in our <Link.Anchor
|
||||
href="https://appwrite.io/docs/products/auth/quick-start"
|
||||
href="https://appwrite.io/docs/products/auth/users"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
documentation</Link.Anchor
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<section class="progressbar__container">
|
||||
{#each $$props.data as item}
|
||||
<Tooltip disabled={!item.tooltip}>
|
||||
<Tooltip disabled={!item.tooltip} maxWidth="max-content">
|
||||
<div
|
||||
class="progressbar__content"
|
||||
style:background-color={item.color}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<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>
|
||||
@@ -0,0 +1,61 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,109 @@
|
||||
<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';
|
||||
import { useViewport } from '@threlte/extras';
|
||||
|
||||
const { invalidate } = useThrelte();
|
||||
const viewport = useViewport();
|
||||
|
||||
let uAspect: Uniform;
|
||||
let uOpacity: Uniform;
|
||||
let uTime: Uniform;
|
||||
let uViewportSize: Uniform;
|
||||
|
||||
$effect(() => {
|
||||
const width = $viewport.width;
|
||||
const height = $viewport.height;
|
||||
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(() => {
|
||||
uAspect = new Uniform($viewport.width / $viewport.height);
|
||||
uOpacity = new Uniform(0);
|
||||
uTime = new Uniform(0);
|
||||
uViewportSize = new Uniform(new Vector2($viewport.width, $viewport.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={[$viewport.width, $viewport.height, 1]} {material}>
|
||||
<T.PlaneGeometry args={[1, 1]} />
|
||||
</T.Mesh>
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Copy } from '.';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Layout, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { Icon, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { IconDuplicate } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Flag, type Models } from '@appwrite.io/console';
|
||||
import { truncateText } from '$lib/components/id.svelte';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { getProjectEndpoint } from '$lib/helpers/project';
|
||||
|
||||
@@ -21,28 +21,28 @@
|
||||
</script>
|
||||
|
||||
{#if region}
|
||||
<Copy value={getProjectEndpoint()} copyText="Copy endpoint">
|
||||
<Tag size="xs" variant="default">
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center" inline>
|
||||
{#if flagSrc}
|
||||
<img
|
||||
width={16}
|
||||
height={12}
|
||||
src={flagSrc}
|
||||
alt={region?.name}
|
||||
style:border-radius="2.5px" />
|
||||
{/if}
|
||||
|
||||
<span
|
||||
style:white-space="nowrap"
|
||||
class="text u-line-height-1-5"
|
||||
style:overflow="hidden"
|
||||
style:word-break="break-all"
|
||||
use:truncateText
|
||||
style:font-family="unset">
|
||||
{region?.name}
|
||||
</span>
|
||||
</Layout.Stack>
|
||||
<Copy value={getProjectEndpoint()} copyText={`Copy endpoint (${region.name})`}>
|
||||
<Tag size="xs" variant="code">
|
||||
<Icon icon={IconDuplicate} size="s" slot="start" />
|
||||
<span class="endpoint-label"> API endpoint </span>
|
||||
{#if flagSrc}
|
||||
<img class="region-flag" src={flagSrc} alt={region?.name} />
|
||||
{/if}
|
||||
</Tag>
|
||||
</Copy>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.endpoint-label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.region-flag {
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
border-radius: 2.5px;
|
||||
margin-inline-start: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { onDestroy, untrack } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { Icon, Input } from '@appwrite.io/pink-svelte';
|
||||
@@ -23,7 +23,10 @@
|
||||
autofocus = false
|
||||
}: Props = $props();
|
||||
|
||||
let inputValue = $state(page.url.searchParams.get('search') ?? '');
|
||||
const initialSearch = page.url.searchParams.get('search') ?? '';
|
||||
let inputValue = $state(initialSearch);
|
||||
let previousInputValue = initialSearch;
|
||||
let previousUrlSearch = initialSearch;
|
||||
|
||||
const runSearch = createDebounce((value: string) => {
|
||||
const trimmed = value.trim();
|
||||
@@ -56,9 +59,25 @@
|
||||
|
||||
$effect(() => {
|
||||
const urlSearch = page.url.searchParams.get('search') ?? '';
|
||||
if (urlSearch !== previousUrlSearch) {
|
||||
previousUrlSearch = urlSearch;
|
||||
const currentInput = untrack(() => inputValue);
|
||||
if (urlSearch !== currentInput) {
|
||||
inputValue = urlSearch;
|
||||
previousInputValue = urlSearch;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (urlSearch !== inputValue) {
|
||||
runSearch(inputValue);
|
||||
$effect(() => {
|
||||
if (inputValue !== previousInputValue) {
|
||||
const urlSearch = untrack(() => page.url.searchParams.get('search') ?? '');
|
||||
if (inputValue !== urlSearch) {
|
||||
previousInputValue = inputValue;
|
||||
runSearch(inputValue);
|
||||
} else {
|
||||
previousInputValue = inputValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
Button,
|
||||
Layout,
|
||||
Avatar,
|
||||
Typography,
|
||||
Badge
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
|
||||
import {
|
||||
@@ -80,13 +79,7 @@
|
||||
{ name: 'Functions', icon: IconLightningBolt, slug: 'functions', category: 'build' },
|
||||
{ name: 'Messaging', icon: IconChatBubble, slug: 'messaging', category: 'build' },
|
||||
{ name: 'Storage', icon: IconFolder, slug: 'storage', category: 'build' },
|
||||
{
|
||||
name: 'Sites',
|
||||
icon: IconGlobeAlt,
|
||||
slug: 'sites',
|
||||
category: 'deploy',
|
||||
badge: 'Early access'
|
||||
}
|
||||
{ name: 'Sites', icon: IconGlobeAlt, slug: 'sites', category: 'deploy' }
|
||||
];
|
||||
|
||||
const isSelected = (service: string): boolean => {
|
||||
@@ -239,12 +232,6 @@
|
||||
class:has-text={state === 'open'}
|
||||
class="link-text">
|
||||
{projectOption.name}
|
||||
{#if projectOption?.badge}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={projectOption.badge}
|
||||
size="xs" />
|
||||
{/if}
|
||||
</span>
|
||||
</a>
|
||||
<span slot="tooltip">{projectOption.name}</span>
|
||||
@@ -272,7 +259,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{:else if $isSmallViewport}
|
||||
{:else if $isTabletViewport}
|
||||
<div class="action-buttons">
|
||||
<Layout.Stack direction="column" gap="s">
|
||||
<DropList show={$feedback.show} scrollable>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
hideColumns?: boolean;
|
||||
allowNoColumns?: boolean;
|
||||
showAnyway?: boolean;
|
||||
disableButton?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -32,7 +33,8 @@
|
||||
hideView = false,
|
||||
hideColumns = false,
|
||||
allowNoColumns = false,
|
||||
showAnyway = false
|
||||
showAnyway = false,
|
||||
disableButton = false
|
||||
}: Props = $props();
|
||||
|
||||
let showCountBadge = $state(false);
|
||||
@@ -70,7 +72,7 @@
|
||||
icon={onlyIcon}
|
||||
onclick={toggle}
|
||||
variant="secondary"
|
||||
disabled={!$columns.length && showAnyway}
|
||||
disabled={(!$columns.length && showAnyway) || disableButton}
|
||||
class={onlyIcon && !$isSmallViewport ? 'width-fix' : undefined}
|
||||
badge={showCountBadge ? selectedColumnsNumber.toString() : undefined}>
|
||||
<Icon slot="start" icon={IconViewBoards} />
|
||||
|
||||
+45
-4
@@ -1,6 +1,7 @@
|
||||
export const PAGE_LIMIT = 12; // default page limit
|
||||
export const SPREADSHEET_PAGE_LIMIT = 50; // default sheet page limit
|
||||
export const CARD_LIMIT = 6; // default card limit
|
||||
export const DEFAULT_BILLING_PROJECTS_LIMIT = 5; // default billing projects page limit
|
||||
export const INTERVAL = 5 * 60000; // default interval to check for feedback
|
||||
export const NEW_DEV_PRO_UPGRADE_COUPON = 'appw50';
|
||||
|
||||
@@ -9,12 +10,14 @@ export const REGION_SYD = 'syd';
|
||||
export const REGION_NYC = 'nyc';
|
||||
export const REGION_SFO = 'sfo';
|
||||
export const REGION_SGP = 'sgp';
|
||||
export const REGION_TOR = 'tor';
|
||||
|
||||
export const SUBDOMAIN_FRA = 'fra.';
|
||||
export const SUBDOMAIN_SYD = 'syd.';
|
||||
export const SUBDOMAIN_NYC = 'nyc.';
|
||||
export const SUBDOMAIN_SFO = 'sfo.';
|
||||
export const SUBDOMAIN_SGP = 'sgp.';
|
||||
export const SUBDOMAIN_TOR = 'tor.';
|
||||
|
||||
export enum Dependencies {
|
||||
FACTORS = 'dependency:factors',
|
||||
@@ -22,6 +25,7 @@ export enum Dependencies {
|
||||
CREDIT = 'dependency:credit',
|
||||
INVOICES = 'dependency:invoices',
|
||||
ADDRESS = 'dependency:address',
|
||||
BILLING_AGGREGATION = 'dependency:billing_aggregation',
|
||||
UPGRADE_PLAN = 'dependency:upgrade_plan',
|
||||
ORGANIZATIONS = 'dependency:organizations',
|
||||
PAYMENT_METHODS = 'dependency:paymentMethods',
|
||||
@@ -59,7 +63,6 @@ export enum Dependencies {
|
||||
DEPLOYMENTS = 'dependency:deployments',
|
||||
EXECUTIONS = 'dependency:executions',
|
||||
PLATFORM = 'dependency:platform',
|
||||
PLATFORMS = 'dependency:platforms',
|
||||
KEY = 'dependency:key',
|
||||
KEYS = 'dependency:keys',
|
||||
DEV_KEY = 'dependency:dev_key',
|
||||
@@ -251,14 +254,13 @@ export const scopes: {
|
||||
},
|
||||
{
|
||||
scope: 'indexes.read',
|
||||
description: "Access to read your project's database collection's indexes",
|
||||
description: "Access to read your project's database table's indexes",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'indexes.write',
|
||||
description:
|
||||
"Access to create, update, and delete your project's database collection's indexes",
|
||||
description: "Access to create, update, and delete your project's database table's indexes",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
@@ -464,6 +466,45 @@ export const scopes: {
|
||||
}
|
||||
];
|
||||
|
||||
export const cloudOnlyBackupScopes = [
|
||||
{
|
||||
scope: 'policies.read',
|
||||
description: 'Access to read your database backup policies',
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'policies.write',
|
||||
description: 'Access to create, update and delete your backup policies',
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'archives.read',
|
||||
description: 'Access to read your database backup archives',
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'archives.write',
|
||||
description: 'Access to create and delete your backup archives',
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'restorations.read',
|
||||
description: 'Access to read your backup restorations',
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'restorations.write',
|
||||
description: 'Access to create backup restorations',
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
}
|
||||
];
|
||||
|
||||
export type EventService = {
|
||||
name: string;
|
||||
resources: EventResource[];
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
export let optionalText: string = null;
|
||||
export let tooltip: string = null;
|
||||
export let isPopoverDefined = true;
|
||||
export let mimeTypeQuery: string = 'image/';
|
||||
export let allowedExtension: string = '*';
|
||||
|
||||
let show = false;
|
||||
|
||||
@@ -93,5 +95,12 @@
|
||||
</div>
|
||||
|
||||
{#if show}
|
||||
<FilePicker selectedFile={value?.$id} selectedBucket={value?.bucketId} bind:show {onSelect} />
|
||||
<FilePicker
|
||||
selectedFile={value?.$id}
|
||||
selectedBucket={value?.bucketId}
|
||||
bind:show
|
||||
{onSelect}
|
||||
showLocalFileBucket
|
||||
{mimeTypeQuery}
|
||||
{allowedExtension} />
|
||||
{/if}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
onDeletePoint: (index: number) => void;
|
||||
onChangePoint: (pointIndex: number, coordIndex: number, newValue: number) => void;
|
||||
addLineButton?: Snippet;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -24,7 +25,8 @@
|
||||
onAddPoint,
|
||||
onDeletePoint,
|
||||
onChangePoint,
|
||||
addLineButton
|
||||
addLineButton,
|
||||
disabled
|
||||
}: Props = $props();
|
||||
|
||||
function isDeleteDisabled(index: number) {
|
||||
@@ -40,6 +42,7 @@
|
||||
<Layout.Stack>
|
||||
{#each values as value, index}
|
||||
<InputPoint
|
||||
{disabled}
|
||||
{nullable}
|
||||
values={value}
|
||||
deletePoints
|
||||
@@ -52,7 +55,11 @@
|
||||
|
||||
{#if values}
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center">
|
||||
<Button size="xs" compact on:click={() => onAddPoint(-1)} disabled={nullable}>
|
||||
<Button
|
||||
size="xs"
|
||||
compact
|
||||
on:click={() => onAddPoint(-1)}
|
||||
disabled={nullable || disabled}>
|
||||
<Icon icon={IconPlus} size="s" /> Add coordinate
|
||||
</Button>
|
||||
{@render addLineButton?.()}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
deletePoints?: boolean;
|
||||
onDeletePoint?: () => void;
|
||||
disableDelete?: boolean;
|
||||
disabled?: boolean;
|
||||
onChangePoint: (index: number, newValue: number) => void;
|
||||
}
|
||||
|
||||
@@ -21,7 +22,8 @@
|
||||
deletePoints = false,
|
||||
disableDelete = false,
|
||||
onDeletePoint,
|
||||
onChangePoint
|
||||
onChangePoint,
|
||||
disabled
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
@@ -38,6 +40,7 @@
|
||||
placeholder="Enter value"
|
||||
step={0.0001}
|
||||
value={values[index]}
|
||||
{disabled}
|
||||
on:change={(e) => onChangePoint(index, Number.parseFloat(`${e.detail}`))} />
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -45,7 +48,7 @@
|
||||
<Button
|
||||
size="s"
|
||||
secondary
|
||||
disabled={nullable || disableDelete}
|
||||
disabled={nullable || disableDelete || disabled}
|
||||
on:click={() => onDeletePoint?.()}>
|
||||
<Icon icon={IconX} size="s" />
|
||||
</Button>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
coordIndex: number,
|
||||
newValue: number
|
||||
) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -26,7 +27,8 @@
|
||||
onAddPoint,
|
||||
onAddLine,
|
||||
onDeletePoint,
|
||||
onChangePoint
|
||||
onChangePoint,
|
||||
disabled
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
@@ -34,6 +36,7 @@
|
||||
{#each values as value, index}
|
||||
<Layout.Stack gap="xs">
|
||||
<InputLine
|
||||
{disabled}
|
||||
values={value}
|
||||
onAddPoint={() => onAddPoint(index)}
|
||||
{nullable}
|
||||
|
||||
@@ -21,16 +21,17 @@
|
||||
}[];
|
||||
export let leadingIcon: ComponentType | undefined = undefined;
|
||||
|
||||
let element: HTMLSelectElement;
|
||||
let error: string;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
const element = event.target as HTMLInputElement;
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
@@ -38,12 +39,6 @@
|
||||
return typeof value === 'boolean' ? true : !!value;
|
||||
};
|
||||
|
||||
$: if (required && !isNotEmpty(value)) {
|
||||
element?.setCustomValidity('This field is required');
|
||||
} else {
|
||||
element?.setCustomValidity('');
|
||||
}
|
||||
|
||||
$: if (isNotEmpty(value)) {
|
||||
error = null;
|
||||
}
|
||||
@@ -61,6 +56,7 @@
|
||||
helper={error ?? helper}
|
||||
{required}
|
||||
state={error ? 'error' : 'default'}
|
||||
data-command-center-ignore
|
||||
on:invalid={handleInvalid}
|
||||
on:input
|
||||
on:change
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
{#if href}
|
||||
<Link.Anchor
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:mousedown
|
||||
on:click={track}
|
||||
@@ -42,7 +43,16 @@
|
||||
<slot />
|
||||
</Link.Anchor>
|
||||
{:else}
|
||||
<Link.Button on:click on:mousedown on:click={track} {type} {disabled} {variant} {size} {icon}>
|
||||
<Link.Button
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:mousedown
|
||||
on:click={track}
|
||||
{type}
|
||||
{disabled}
|
||||
{variant}
|
||||
{size}
|
||||
{icon}>
|
||||
<slot />
|
||||
</Link.Button>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
/**
|
||||
* Checks if a build has exceeded the maximum build timeout duration
|
||||
*/
|
||||
function isBuildTimedOut(createdAt: string, status: string, timeoutSeconds: number): boolean {
|
||||
if (!['waiting', 'processing', 'building'].includes(status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!timeoutSeconds || timeoutSeconds <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const created = new Date(createdAt);
|
||||
const elapsedSeconds = Math.floor((Date.now() - created.getTime()) / 1000);
|
||||
|
||||
return elapsedSeconds >= timeoutSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the effective status for a build, considering timeout
|
||||
*/
|
||||
export function getEffectiveBuildStatus(
|
||||
deployment: Models.Deployment,
|
||||
consoleVariables: Models.ConsoleVariables | undefined
|
||||
): string {
|
||||
const originalStatus = deployment.status;
|
||||
const createdAt = deployment.$createdAt;
|
||||
|
||||
const timeoutSeconds = getBuildTimeoutSeconds(consoleVariables);
|
||||
if (isBuildTimedOut(createdAt, originalStatus, timeoutSeconds)) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
const isReady = originalStatus === 'ready';
|
||||
const hasScreenshot = deployment.screenshotLight && deployment.screenshotDark;
|
||||
|
||||
if (isReady && !hasScreenshot) {
|
||||
return 'finalizing';
|
||||
}
|
||||
|
||||
return originalStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get timeout value from console variables
|
||||
*/
|
||||
function getBuildTimeoutSeconds(consoleVariables: Models.ConsoleVariables | undefined): number {
|
||||
if (!consoleVariables?._APP_COMPUTE_BUILD_TIMEOUT) {
|
||||
return 0;
|
||||
}
|
||||
const timeout = parseInt(String(consoleVariables._APP_COMPUTE_BUILD_TIMEOUT), 10);
|
||||
return isNaN(timeout) ? 0 : timeout;
|
||||
}
|
||||
@@ -206,6 +206,18 @@ export function timeFromNow(datetime: string): string {
|
||||
return dayjs().to(dayjs(datetime));
|
||||
}
|
||||
|
||||
export function timeFromNowShort(datetime: string): string {
|
||||
if (!datetime || !isValidDate(datetime)) {
|
||||
return 'n/a';
|
||||
}
|
||||
|
||||
const timeStr = dayjs().to(dayjs(datetime));
|
||||
return timeStr
|
||||
.replace('second', 'sec') // seconds > secs
|
||||
.replace('minute', 'min') // minutes > mins
|
||||
.replace('hour', 'hr'); // hours > hrs
|
||||
}
|
||||
|
||||
export function hoursToDays(hours: number) {
|
||||
if (hours > 24) {
|
||||
return `${Math.floor(hours / 24)} days`;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
/**
|
||||
* Checks if an execution has exceeded the maximum execution timeout duration
|
||||
*/
|
||||
function isExecutionTimedOut(createdAt: string, status: string, timeoutSeconds: number): boolean {
|
||||
if (!['waiting', 'processing'].includes(status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!timeoutSeconds || timeoutSeconds <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const created = new Date(createdAt);
|
||||
const elapsedSeconds = Math.floor((Date.now() - created.getTime()) / 1000);
|
||||
|
||||
return elapsedSeconds >= timeoutSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the effective status for an execution, considering timeout
|
||||
*/
|
||||
export function getEffectiveExecutionStatus(
|
||||
execution: Models.Execution,
|
||||
func: Models.Function
|
||||
): string {
|
||||
const originalStatus = execution.status;
|
||||
const createdAt = execution.$createdAt;
|
||||
|
||||
const timeoutSeconds = func.timeout;
|
||||
if (isExecutionTimedOut(createdAt, originalStatus, timeoutSeconds)) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
return originalStatus;
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
/**
|
||||
* Build VCS repo URL from the template response model.
|
||||
* Example (GitHub): https://github.com/appwrite/templates-for-sites
|
||||
* build VCS repo URL from the template response model.
|
||||
* supports GitHub, GitLab, and Bitbucket.
|
||||
*
|
||||
* important: We use 'master' as the branch name because GitHub (and other providers)
|
||||
* redirect 'master' to the repository's default branch, regardless of whether
|
||||
* its actually named 'main', 'master', or something else. This ensures the
|
||||
* link works across all repositories without needing to know their default branch.
|
||||
*
|
||||
* Example (GitHub): https://github.com/appwrite/templates-for-sites/tree/master/sveltekit/starter
|
||||
*/
|
||||
export function getTemplateSourceUrl(
|
||||
t: Models.TemplateSite | Models.TemplateFunction
|
||||
@@ -20,7 +27,41 @@ export function getTemplateSourceUrl(
|
||||
bitbucket: 'bitbucket.org'
|
||||
};
|
||||
|
||||
const host = hostMap[provider.toLowerCase()] ?? provider; // fallback
|
||||
const host = hostMap[provider.toLowerCase()];
|
||||
if (!host) return null;
|
||||
|
||||
return `https://${host}/${owner}/${repo}`;
|
||||
let folderPath: string | undefined;
|
||||
if (
|
||||
'providerRootDirectory' in t &&
|
||||
t.providerRootDirectory &&
|
||||
typeof t.providerRootDirectory === 'string'
|
||||
) {
|
||||
folderPath = t.providerRootDirectory;
|
||||
} else if (
|
||||
'frameworks' in t &&
|
||||
t.frameworks?.length > 0 &&
|
||||
t.frameworks[0]?.providerRootDirectory &&
|
||||
typeof t.frameworks[0].providerRootDirectory === 'string'
|
||||
) {
|
||||
folderPath = t.frameworks[0].providerRootDirectory;
|
||||
}
|
||||
|
||||
let url = `https://${host}/${owner}/${repo}`;
|
||||
|
||||
if (folderPath) {
|
||||
const normalizedPath = folderPath.replace(/^\/+|\/+$/g, '');
|
||||
if (normalizedPath) {
|
||||
const providerLower = provider.toLowerCase();
|
||||
// Use 'master' as branch name - GitHub/GitLab/Bitbucket redirect it to default branch
|
||||
if (providerLower === 'github') {
|
||||
url = `${url}/tree/master/${normalizedPath}`;
|
||||
} else if (providerLower === 'gitlab') {
|
||||
url = `${url}/-/tree/master/${normalizedPath}`;
|
||||
} else if (providerLower === 'bitbucket') {
|
||||
url = `${url}/src/master/${normalizedPath}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -18,3 +18,12 @@ export function isASubdomain(domain: string | null): boolean {
|
||||
|
||||
return !!subdomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subdomain part from a full domain string.
|
||||
*/
|
||||
export function getSubdomain(domain: string): string {
|
||||
if (!domain) return '';
|
||||
|
||||
return parse(domain, { allowPrivateDomains: true }).subdomain || '';
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 834 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 856 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 91 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 83 KiB |
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { AvatarInitials, PaginationWithLimit, Trim } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import DualTimeView from '$lib/components/dualTimeView.svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Layout, Table, Card, Empty } from '@appwrite.io/pink-svelte';
|
||||
import { Layout, Table, Card, Empty, InteractiveText } from '@appwrite.io/pink-svelte';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import type { PinkColumn } from '$lib/helpers/types';
|
||||
|
||||
@@ -14,12 +14,32 @@
|
||||
export let databasesScreen = false;
|
||||
export let useCreateLinkForPagination = true;
|
||||
|
||||
function getColumnWidth(columnId: string): Pick<PinkColumn, 'width'> {
|
||||
const widthConfig: Record<
|
||||
string,
|
||||
{ insideSheet: number | { min: number }; default: number | { min: number } }
|
||||
> = {
|
||||
user: { insideSheet: 140, default: { min: 100 } },
|
||||
event: { insideSheet: 125, default: { min: 160 } },
|
||||
location: { insideSheet: 100, default: { min: 120 } },
|
||||
ip: { insideSheet: { min: 150 }, default: { min: 250 } },
|
||||
date: { insideSheet: { min: 200 }, default: { min: 180 } }
|
||||
};
|
||||
|
||||
const config = widthConfig[columnId];
|
||||
if (!config) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { width: insideSideSheet ? config.insideSheet : config.default };
|
||||
}
|
||||
|
||||
const columns: PinkColumn[] = [
|
||||
{ id: 'user', ...(insideSideSheet ? { width: 140 } : {}) },
|
||||
{ id: 'event', ...(insideSideSheet ? { width: 125 } : {}) },
|
||||
{ id: 'location', ...(insideSideSheet ? { width: 100 } : {}) },
|
||||
{ id: 'ip', ...(insideSideSheet ? { width: { min: 150 } } : {}) },
|
||||
{ id: 'date', ...(insideSideSheet ? { width: { min: 200 } } : {}) }
|
||||
{ id: 'user', ...getColumnWidth('user') },
|
||||
{ id: 'event', ...getColumnWidth('event') },
|
||||
{ id: 'location', ...getColumnWidth('location') },
|
||||
{ id: 'ip', ...getColumnWidth('ip') },
|
||||
{ id: 'date', ...getColumnWidth('date') }
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -29,55 +49,57 @@
|
||||
expanded={databasesScreen && !insideSideSheet}
|
||||
slotSpacing={databasesScreen && !insideSideSheet}>
|
||||
{#if logs.total}
|
||||
<div>
|
||||
<Table.Root {columns} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="user" {root}>User</Table.Header.Cell>
|
||||
<Table.Header.Cell column="event" {root}>Event</Table.Header.Cell>
|
||||
<Table.Header.Cell column="location" {root}>Location</Table.Header.Cell>
|
||||
<Table.Header.Cell column="ip" {root}>IP</Table.Header.Cell>
|
||||
<Table.Header.Cell column="date" {root}>Date</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
{#each logs.logs as log}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="user" {root}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
{#if log.userEmail}
|
||||
{#if log.userName}
|
||||
<AvatarInitials size="xs" name={log.userName} />
|
||||
<Trim>{log.userName}</Trim>
|
||||
{:else}
|
||||
<AvatarInitials size="xs" name={log.userEmail} />
|
||||
<Trim>{log.userEmail}</Trim>
|
||||
{/if}
|
||||
<Table.Root {columns} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="user" {root}>User</Table.Header.Cell>
|
||||
<Table.Header.Cell column="event" {root}>Event</Table.Header.Cell>
|
||||
<Table.Header.Cell column="location" {root}>Location</Table.Header.Cell>
|
||||
<Table.Header.Cell column="ip" {root}>IP</Table.Header.Cell>
|
||||
<Table.Header.Cell column="date" {root}>Date</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
{#each logs.logs as log}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="user" {root}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
{#if log.userEmail}
|
||||
{#if log.userName}
|
||||
<AvatarInitials size="xs" name={log.userName} />
|
||||
<Trim>{log.userName}</Trim>
|
||||
{:else}
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-anonymous" aria-hidden="true"></span>
|
||||
</div>
|
||||
<span class="text u-trim">{log.userName ?? 'Anonymous'}</span>
|
||||
<AvatarInitials size="xs" name={log.userEmail} />
|
||||
<Trim>{log.userEmail}</Trim>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="event" {root}>
|
||||
{log.event}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="location" {root}>
|
||||
{#if log.countryCode !== '--'}
|
||||
{log.countryName}
|
||||
{:else}
|
||||
Unknown
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-anonymous" aria-hidden="true"></span>
|
||||
</div>
|
||||
<span class="text u-trim">{log.userName ?? 'Anonymous'}</span>
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="ip" {root}>
|
||||
{log.ip}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="date" {root}>
|
||||
{toLocaleDateTime(log.time)}
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
|
||||
<Table.Cell column="event" {root}>
|
||||
{log.event}
|
||||
</Table.Cell>
|
||||
|
||||
<Table.Cell column="location" {root}>
|
||||
{#if log.countryCode !== '--'}
|
||||
{log.countryName}
|
||||
{:else}
|
||||
Unknown
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
|
||||
<Table.Cell column="ip" {root}>
|
||||
<InteractiveText variant="copy" text={log.ip} isVisible />
|
||||
</Table.Cell>
|
||||
|
||||
<Table.Cell column="date" {root}>
|
||||
<DualTimeView time={log.time} showDatetime />
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
|
||||
<PaginationWithLimit
|
||||
{limit}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<Icon size="s" icon={IconCloud} />
|
||||
{/if}
|
||||
|
||||
{#if $version}
|
||||
{#if $version && !isCloud}
|
||||
<Link.Anchor
|
||||
size="s"
|
||||
variant="quiet"
|
||||
@@ -137,7 +137,7 @@
|
||||
</Link.Anchor>
|
||||
{/if}
|
||||
{#if $isSmallViewport}
|
||||
{#if $version}
|
||||
{#if $version && !isCloud}
|
||||
<span class="divider-wrapper">
|
||||
<Divider vertical />
|
||||
</span>
|
||||
@@ -151,15 +151,18 @@
|
||||
style="white-space: nowrap;">
|
||||
Version {$version}
|
||||
</Link.Anchor>
|
||||
{#if isCloud}
|
||||
<Icon size="s" icon={IconCloud} />
|
||||
<Badge
|
||||
size="xs"
|
||||
type="success"
|
||||
variant="secondary"
|
||||
content="Generally Available"
|
||||
style="white-space: nowrap;" />
|
||||
{/if}
|
||||
{/if}
|
||||
{#if isCloud}
|
||||
<span class="divider-wrapper">
|
||||
<Divider vertical />
|
||||
</span>
|
||||
<Icon size="s" icon={IconCloud} />
|
||||
<Badge
|
||||
size="xs"
|
||||
type="success"
|
||||
variant="secondary"
|
||||
content="Generally Available"
|
||||
style="white-space: nowrap;" />
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -173,7 +173,8 @@
|
||||
|
||||
$: subNavigation = $page.data.subNavigation;
|
||||
|
||||
$: isProjectPage = $page.route?.id?.includes('project-');
|
||||
$: shouldRenderSidebar = !$isNewWizardStatusOpen && showSideNavigation;
|
||||
$: hasSidebarSpace = shouldRenderSidebar && !$isTabletViewport && !!selectedProject;
|
||||
|
||||
$: {
|
||||
if ($isSidebarOpen) {
|
||||
@@ -205,7 +206,7 @@
|
||||
<Navbar {...navbarProps} bind:sideBarIsOpen={$isSidebarOpen} bind:showAccountMenu />
|
||||
{/if}
|
||||
|
||||
{#if !$isNewWizardStatusOpen && isProjectPage}
|
||||
{#if shouldRenderSidebar}
|
||||
<Sidebar
|
||||
project={selectedProject}
|
||||
progressCard={getProgressCard()}
|
||||
@@ -221,8 +222,8 @@
|
||||
<div
|
||||
class="content"
|
||||
class:has-transition={showContentTransition}
|
||||
class:icons-content={state === 'icons' && isProjectPage}
|
||||
class:no-sidebar={!showSideNavigation}>
|
||||
class:icons-content={state === 'icons' && selectedProject}
|
||||
class:no-sidebar={!hasSidebarSpace}>
|
||||
<section class="main-content" data-test={showSideNavigation}>
|
||||
{#if $page.data?.header}
|
||||
<div class="layout-header">
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
.responsive-table {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
scrollbar-width: thin;
|
||||
position: relative;
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
import { AppwriteException, Client, type Payload } from '@appwrite.io/console';
|
||||
|
||||
export type BackupPolicyList = {
|
||||
total: number;
|
||||
policies: BackupPolicy[];
|
||||
};
|
||||
|
||||
export type BackupArchiveList = {
|
||||
total: number;
|
||||
archives: BackupArchive[];
|
||||
};
|
||||
|
||||
export type BackupRestorationList = {
|
||||
total: number;
|
||||
restorations: BackupRestoration[];
|
||||
};
|
||||
|
||||
export type BackupPolicy = {
|
||||
$id: string;
|
||||
name: string;
|
||||
$createdAt: string;
|
||||
$updatedAt: string;
|
||||
services: string[];
|
||||
resources: string[];
|
||||
resourceId?: string;
|
||||
resourceType?: string;
|
||||
retention: number;
|
||||
schedule: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type BackupArchive = {
|
||||
$id: string;
|
||||
$createdAt: string;
|
||||
$updatedAt: string;
|
||||
policyId: string;
|
||||
size: number;
|
||||
status: string;
|
||||
startedAt: string;
|
||||
migrationId: string;
|
||||
services: string[];
|
||||
resources: string[];
|
||||
resourceId?: string;
|
||||
resourceType?: string;
|
||||
};
|
||||
|
||||
export type BackupRestoration = {
|
||||
$id: string;
|
||||
$createdAt: string;
|
||||
$updatedAt: string;
|
||||
archiveId: string;
|
||||
policyId: string;
|
||||
status: string;
|
||||
startedAt: string;
|
||||
migrationId: string;
|
||||
services: string[];
|
||||
resources: string[];
|
||||
options: string;
|
||||
};
|
||||
|
||||
export class Backups {
|
||||
client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async createArchive(services: string[], resourceId?: string): Promise<BackupArchive> {
|
||||
if (typeof services === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "services"');
|
||||
}
|
||||
const apiPath = '/backups/archives';
|
||||
const payload: Payload = {};
|
||||
if (typeof services !== 'undefined') {
|
||||
payload['services'] = services;
|
||||
}
|
||||
if (typeof resourceId !== 'undefined') {
|
||||
payload['resourceId'] = resourceId;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('post', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async deleteArchive(archiveId: string): Promise<object> {
|
||||
if (typeof archiveId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "archiveId"');
|
||||
}
|
||||
const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('delete', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async listArchives(queries?: string[]): Promise<BackupArchiveList> {
|
||||
const apiPath = '/backups/archives';
|
||||
const payload: Payload = {};
|
||||
if (typeof queries !== 'undefined') {
|
||||
payload['queries'] = queries;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async getArchive(archiveId: string): Promise<BackupArchive> {
|
||||
if (typeof archiveId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "archiveId"');
|
||||
}
|
||||
const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async listPolicies(queries?: string[]): Promise<BackupPolicyList> {
|
||||
const apiPath = '/backups/policies';
|
||||
const payload: Payload = {};
|
||||
if (typeof queries !== 'undefined') {
|
||||
payload['queries'] = queries;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async createPolicy(
|
||||
policyId: string,
|
||||
services: string[],
|
||||
retention: number,
|
||||
schedule: string,
|
||||
name?: string,
|
||||
resourceId?: string,
|
||||
enabled?: boolean
|
||||
): Promise<BackupPolicy> {
|
||||
if (typeof policyId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "policyId"');
|
||||
}
|
||||
if (typeof services === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "services"');
|
||||
}
|
||||
if (typeof retention === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "retention"');
|
||||
}
|
||||
if (typeof schedule === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "schedule"');
|
||||
}
|
||||
const apiPath = '/backups/policies';
|
||||
const payload: Payload = {};
|
||||
if (typeof policyId !== 'undefined') {
|
||||
payload['policyId'] = policyId;
|
||||
}
|
||||
if (typeof name !== 'undefined') {
|
||||
payload['name'] = name;
|
||||
}
|
||||
if (typeof services !== 'undefined') {
|
||||
payload['services'] = services;
|
||||
}
|
||||
if (typeof resourceId !== 'undefined') {
|
||||
payload['resourceId'] = resourceId;
|
||||
}
|
||||
if (typeof enabled !== 'undefined') {
|
||||
payload['enabled'] = enabled;
|
||||
}
|
||||
if (typeof retention !== 'undefined') {
|
||||
payload['retention'] = retention;
|
||||
}
|
||||
if (typeof schedule !== 'undefined') {
|
||||
payload['schedule'] = schedule;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('post', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async getPolicy(policyId: string): Promise<BackupPolicy> {
|
||||
if (typeof policyId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "policyId"');
|
||||
}
|
||||
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async updatePolicy(
|
||||
policyId: string,
|
||||
name?: string,
|
||||
retention?: number,
|
||||
schedule?: string,
|
||||
enabled?: boolean
|
||||
): Promise<BackupPolicy> {
|
||||
if (typeof policyId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "policyId"');
|
||||
}
|
||||
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
|
||||
const payload: Payload = {};
|
||||
if (typeof name !== 'undefined') {
|
||||
payload['name'] = name;
|
||||
}
|
||||
if (typeof retention !== 'undefined') {
|
||||
payload['retention'] = retention;
|
||||
}
|
||||
if (typeof schedule !== 'undefined') {
|
||||
payload['schedule'] = schedule;
|
||||
}
|
||||
if (typeof enabled !== 'undefined') {
|
||||
payload['enabled'] = enabled;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('patch', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async deletePolicy(policyId: string): Promise<object> {
|
||||
if (typeof policyId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "policyId"');
|
||||
}
|
||||
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('delete', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async createRestoration(
|
||||
archiveId: string,
|
||||
services: string[],
|
||||
newResourceId?: string,
|
||||
newResourceName?: string
|
||||
): Promise<BackupRestoration> {
|
||||
if (typeof archiveId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "archiveId"');
|
||||
}
|
||||
if (typeof services === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "services"');
|
||||
}
|
||||
const apiPath = '/backups/restoration';
|
||||
const payload: Payload = {};
|
||||
if (typeof archiveId !== 'undefined') {
|
||||
payload['archiveId'] = archiveId;
|
||||
}
|
||||
if (typeof services !== 'undefined') {
|
||||
payload['services'] = services;
|
||||
}
|
||||
if (typeof newResourceId !== 'undefined') {
|
||||
payload['newResourceId'] = newResourceId;
|
||||
}
|
||||
if (typeof newResourceName !== 'undefined') {
|
||||
payload['newResourceName'] = newResourceName;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('post', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async listRestorations(queries?: string[]): Promise<BackupRestorationList> {
|
||||
const apiPath = '/backups/restorations';
|
||||
const payload: Payload = {};
|
||||
if (typeof queries !== 'undefined') {
|
||||
payload['queries'] = queries;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async getRestoration(restorationId: string): Promise<BackupArchive> {
|
||||
if (typeof restorationId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "restorationId"');
|
||||
}
|
||||
const apiPath = '/backups/restorations/{restorationId}'.replace(
|
||||
'{restorationId}',
|
||||
restorationId
|
||||
);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
}
|
||||
+18
-25
@@ -490,7 +490,7 @@ export class Billing {
|
||||
name: string,
|
||||
billingPlan: string,
|
||||
paymentMethodId: string,
|
||||
billingAddressId: string = null,
|
||||
billingAddressId: string = undefined,
|
||||
couponId: string = null,
|
||||
invites: Array<string> = [],
|
||||
budget: number = undefined,
|
||||
@@ -628,6 +628,7 @@ export class Billing {
|
||||
budget,
|
||||
taxId
|
||||
};
|
||||
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
'patch',
|
||||
@@ -934,12 +935,24 @@ export class Billing {
|
||||
);
|
||||
}
|
||||
|
||||
async getAggregation(organizationId: string, aggregationId: string): Promise<AggregationTeam> {
|
||||
async getAggregation(
|
||||
organizationId: string,
|
||||
aggregationId: string,
|
||||
limit?: number,
|
||||
offset?: number
|
||||
): Promise<AggregationTeam> {
|
||||
const path = `/organizations/${organizationId}/aggregations/${aggregationId}`;
|
||||
const params = {
|
||||
const params: {
|
||||
organizationId: string;
|
||||
aggregationId: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
} = {
|
||||
organizationId,
|
||||
aggregationId
|
||||
};
|
||||
if (typeof limit === 'number') params.limit = limit;
|
||||
if (typeof offset === 'number') params.offset = offset;
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
'get',
|
||||
@@ -1287,26 +1300,6 @@ export class Billing {
|
||||
);
|
||||
}
|
||||
|
||||
async setupPaymentMandate(
|
||||
organizationId: string,
|
||||
paymentMethodId: string
|
||||
): Promise<PaymentMethodData> {
|
||||
const path = `/account/payment-methods/${paymentMethodId}/setup`;
|
||||
const params = {
|
||||
organizationId,
|
||||
paymentMethodId
|
||||
};
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
'patch',
|
||||
uri,
|
||||
{
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
async listAddresses(queries: string[] = []): Promise<AddressesList> {
|
||||
const path = `/account/billing-addresses`;
|
||||
const params = {
|
||||
@@ -1411,10 +1404,10 @@ export class Billing {
|
||||
);
|
||||
}
|
||||
|
||||
async listRegions(teamId: string): Promise<Models.ConsoleRegionList> {
|
||||
async listRegions(organizationId: string): Promise<Models.ConsoleRegionList> {
|
||||
const path = `/console/regions`;
|
||||
const params = {
|
||||
teamId
|
||||
organizationId
|
||||
};
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
|
||||
@@ -8,7 +8,7 @@ import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.
|
||||
import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte';
|
||||
import newDevUpgradePro from '$lib/components/billing/alerts/newDevUpgradePro.svelte';
|
||||
import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequired.svelte';
|
||||
import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
|
||||
|
||||
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
import { type Size, sizeToBytes } from '$lib/helpers/sizeConvertion';
|
||||
@@ -18,13 +18,12 @@ import type {
|
||||
Invoice,
|
||||
InvoiceList,
|
||||
PaymentList,
|
||||
PaymentMethodData,
|
||||
Plan,
|
||||
PlansMap
|
||||
} from '$lib/sdk/billing';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { activeHeaderAlert, orgMissingPaymentMethod } from '$routes/(console)/store';
|
||||
import { AppwriteException, Query } from '@appwrite.io/console';
|
||||
import { AppwriteException, Query, Platform } from '@appwrite.io/console';
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import { headerAlert } from './headerAlert';
|
||||
import { addNotification, notifications } from './notifications';
|
||||
@@ -100,7 +99,7 @@ export function tierToPlan(tier: Tier) {
|
||||
case BillingPlan.ENTERPRISE:
|
||||
return tierEnterprise;
|
||||
default:
|
||||
return tierFree;
|
||||
return tierCustom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +487,9 @@ export async function paymentExpired(org: Organization) {
|
||||
const nots = get(notifications);
|
||||
const expiredNotification = nots.some((n) => n.message === expiredMessage);
|
||||
const expiringNotification = nots.some((n) => n.message === expiringMessage);
|
||||
const cardExpiry = new Date(payment.expiryYear, payment.expiryMonth, 1);
|
||||
const nextMonth = new Date(year, month + 1, 1);
|
||||
const isExpiringNextMonth = cardExpiry.getTime() === nextMonth.getTime();
|
||||
if (payment.expired && !expiredNotification) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
@@ -503,7 +505,7 @@ export async function paymentExpired(org: Organization) {
|
||||
}
|
||||
]
|
||||
});
|
||||
} else if (!expiringNotification && payment.expiryYear <= year && payment.expiryMonth < month) {
|
||||
} else if (!expiringNotification && !payment.expired && isExpiringNextMonth) {
|
||||
addNotification({
|
||||
type: 'warning',
|
||||
isHtml: true,
|
||||
@@ -532,29 +534,12 @@ export function checkForMarkedForDeletion(org: Organization) {
|
||||
}
|
||||
}
|
||||
|
||||
export const paymentMissingMandate = writable<PaymentMethodData>(null);
|
||||
|
||||
export async function checkForMandate(org: Organization) {
|
||||
const paymentId = org.paymentMethodId ?? org.backupPaymentMethodId;
|
||||
if (!paymentId) return;
|
||||
const paymentMethod = await sdk.forConsole.billing.getPaymentMethod(paymentId);
|
||||
if (paymentMethod?.mandateId === null && paymentMethod?.country.toLowerCase() === 'in') {
|
||||
headerAlert.add({
|
||||
id: 'paymentMandate',
|
||||
component: PaymentMandate,
|
||||
show: true,
|
||||
importance: 8
|
||||
});
|
||||
activeHeaderAlert.set(headerAlert.get());
|
||||
paymentMissingMandate.set(paymentMethod);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkForMissingPaymentMethod() {
|
||||
const orgs = await sdk.forConsole.billing.listOrganization([
|
||||
Query.notEqual('billingPlan', BillingPlan.FREE),
|
||||
Query.isNull('paymentMethodId'),
|
||||
Query.isNull('backupPaymentMethodId')
|
||||
Query.isNull('backupPaymentMethodId'),
|
||||
Query.equal('platform', Platform.Appwrite)
|
||||
]);
|
||||
if (orgs?.total) {
|
||||
orgMissingPaymentMethod.set(orgs.teams[0]);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { NotificationCoolOffOptions } from '$lib/helpers/notifications';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import type { Component } from 'svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import type { NotificationCoolOffOptions } from '$lib/helpers/notifications';
|
||||
|
||||
export type BottomModalAlertAction = {
|
||||
text: string;
|
||||
color?: Record<'light' | 'dark', string> | string;
|
||||
background?: Record<'light' | 'dark', string> | string;
|
||||
backgroundHover?: Record<'light' | 'dark', string> | string;
|
||||
hideOnClick?: boolean;
|
||||
link: (ctx: { organization: Organization; project: Models.Project }) => string;
|
||||
external?: boolean;
|
||||
@@ -32,7 +36,10 @@ export type BottomModalAlertItem = {
|
||||
title: string;
|
||||
message: string;
|
||||
|
||||
src: Record<'dark' | 'light', string>;
|
||||
// use either of these!
|
||||
src?: Record<'dark' | 'light', string>;
|
||||
backgroundComponent?: Component;
|
||||
|
||||
cta: BottomModalAlertAction;
|
||||
learnMore?: BottomModalAlertAction;
|
||||
plan: 'free' | 'pro' | 'scale' /*| 'enterprise'*/;
|
||||
@@ -43,6 +50,12 @@ export type BottomModalAlertItem = {
|
||||
closed?: () => void;
|
||||
scope: 'organization' | 'project' | 'everywhere';
|
||||
notificationHideOptions?: NotificationCoolOffOptions;
|
||||
|
||||
/**
|
||||
* if true,
|
||||
* uses same title, message on mobile floating window.
|
||||
*/
|
||||
sameContentOnMobileLayout?: boolean;
|
||||
};
|
||||
|
||||
type BottomModalAlertState = {
|
||||
|
||||
@@ -27,9 +27,9 @@ export async function checkForDatabaseBackupPolicies(
|
||||
|
||||
if (isCloud && backupsEnabled) {
|
||||
try {
|
||||
const policies = await sdk
|
||||
.forProject(region, projectId)
|
||||
.backups.listPolicies([Query.limit(1), Query.equal('resourceId', database.$id)]);
|
||||
const policies = await sdk.forProject(region, projectId).backups.listPolicies({
|
||||
queries: [Query.limit(1), Query.equal('resourceId', database.$id)]
|
||||
});
|
||||
|
||||
total = policies.total;
|
||||
} catch (e) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export type Provider = {
|
||||
name: string;
|
||||
icon: string;
|
||||
docs?: string;
|
||||
internal?: true;
|
||||
component: Component;
|
||||
};
|
||||
|
||||
@@ -26,7 +27,7 @@ export const oAuthProviders: Record<string, Provider> = {
|
||||
apple: {
|
||||
name: 'Apple',
|
||||
icon: 'apple',
|
||||
docs: 'https://developer.apple.com/',
|
||||
docs: 'https://developer.apple.com/sign-in-with-apple/',
|
||||
component: Apple
|
||||
},
|
||||
auth0: {
|
||||
@@ -113,6 +114,13 @@ export const oAuthProviders: Record<string, Provider> = {
|
||||
docs: 'https://developer.github.com',
|
||||
component: Main
|
||||
},
|
||||
githubImagine: {
|
||||
name: 'GitHub',
|
||||
icon: 'github',
|
||||
docs: 'https://developer.github.com',
|
||||
component: Main,
|
||||
internal: true
|
||||
},
|
||||
gitlab: {
|
||||
name: 'GitLab',
|
||||
icon: 'gitlab',
|
||||
@@ -125,6 +133,13 @@ export const oAuthProviders: Record<string, Provider> = {
|
||||
docs: 'https://support.google.com/googleapi/answer/6158849',
|
||||
component: Google
|
||||
},
|
||||
googleImagine: {
|
||||
name: 'Google',
|
||||
icon: 'google',
|
||||
docs: 'https://support.google.com/googleapi/answer/6158849',
|
||||
component: Google,
|
||||
internal: true
|
||||
},
|
||||
linkedin: {
|
||||
name: 'LinkedIn',
|
||||
icon: 'linkedin',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { page } from '$app/stores';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import type { Tier } from './billing';
|
||||
import type { Plan } from '$lib/sdk/billing';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import { type Models, Platform } from '@appwrite.io/console';
|
||||
|
||||
export type OrganizationError = {
|
||||
status: number;
|
||||
@@ -16,6 +16,8 @@ export type OrganizationError = {
|
||||
export type Organization = Models.Team<Record<string, unknown>> & {
|
||||
billingBudget: number;
|
||||
billingPlan: Tier;
|
||||
billingPlanId: Tier /* unused for now! */;
|
||||
billingPlanDetails: Plan /* unused for now! */;
|
||||
budgetAlerts: number[];
|
||||
paymentMethodId: string;
|
||||
backupPaymentMethodId: string;
|
||||
@@ -35,6 +37,7 @@ export type Organization = Models.Team<Record<string, unknown>> & {
|
||||
status: string;
|
||||
remarks: string;
|
||||
projects: string[];
|
||||
platform: Platform;
|
||||
};
|
||||
|
||||
export type OrganizationList = {
|
||||
|
||||
+55
-11
@@ -3,6 +3,7 @@ import {
|
||||
Account,
|
||||
Assistant,
|
||||
Avatars,
|
||||
Backups,
|
||||
Client,
|
||||
Console,
|
||||
Functions,
|
||||
@@ -21,10 +22,11 @@ import {
|
||||
Sites,
|
||||
Tokens,
|
||||
TablesDB,
|
||||
Domains
|
||||
Domains,
|
||||
Realtime,
|
||||
Organizations
|
||||
} from '@appwrite.io/console';
|
||||
import { Billing } from '../sdk/billing';
|
||||
import { Backups } from '../sdk/backups';
|
||||
import { Sources } from '$lib/sdk/sources';
|
||||
import {
|
||||
REGION_FRA,
|
||||
@@ -32,14 +34,15 @@ import {
|
||||
REGION_SYD,
|
||||
REGION_SFO,
|
||||
REGION_SGP,
|
||||
REGION_TOR,
|
||||
SUBDOMAIN_FRA,
|
||||
SUBDOMAIN_NYC,
|
||||
SUBDOMAIN_SFO,
|
||||
SUBDOMAIN_SYD,
|
||||
SUBDOMAIN_SGP
|
||||
SUBDOMAIN_SGP,
|
||||
SUBDOMAIN_TOR
|
||||
} from '$lib/constants';
|
||||
import { building } from '$app/environment';
|
||||
import { getProjectId } from '$lib/helpers/project';
|
||||
|
||||
export function getApiEndpoint(region?: string): string {
|
||||
if (building) return '';
|
||||
@@ -67,6 +70,8 @@ const getSubdomain = (region?: string) => {
|
||||
return SUBDOMAIN_SFO;
|
||||
case REGION_SGP:
|
||||
return SUBDOMAIN_SGP;
|
||||
case REGION_TOR:
|
||||
return SUBDOMAIN_TOR;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -90,7 +95,9 @@ function createConsoleSdk(client: Client) {
|
||||
sources: new Sources(client),
|
||||
sites: new Sites(client),
|
||||
domains: new Domains(client),
|
||||
storage: new Storage(client)
|
||||
storage: new Storage(client),
|
||||
realtime: new Realtime(client),
|
||||
organizations: new Organizations(client)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,7 +113,6 @@ if (!building) {
|
||||
scopedConsoleClient.setProject('console');
|
||||
clientConsole.setEndpoint(endpoint).setProject('console');
|
||||
|
||||
clientRealtime.setEndpoint(endpoint).setProject('console');
|
||||
clientProject.setEndpoint(endpoint).setMode('admin');
|
||||
clientRealtime.setEndpoint(endpoint).setProject('console');
|
||||
}
|
||||
@@ -135,12 +141,32 @@ const sdkForProject = {
|
||||
};
|
||||
|
||||
export const realtime = {
|
||||
forProject(region: string, _projectId: string) {
|
||||
forProject(
|
||||
region: string,
|
||||
channels: string | string[],
|
||||
callback: AppwriteRealtimeResponseEvent
|
||||
) {
|
||||
const endpoint = getApiEndpoint(region);
|
||||
if (endpoint !== clientRealtime.config.endpoint) {
|
||||
clientRealtime.setEndpoint(endpoint);
|
||||
}
|
||||
return clientRealtime;
|
||||
|
||||
// because uses a different client!
|
||||
const realtime = new Realtime(clientRealtime);
|
||||
|
||||
return createRealtimeSubscription(realtime, channels, callback);
|
||||
},
|
||||
|
||||
forConsole(
|
||||
region: string,
|
||||
channels: string | string[],
|
||||
callback: AppwriteRealtimeResponseEvent
|
||||
): () => void {
|
||||
const realtimeInstance = region
|
||||
? sdk.forConsoleIn(region).realtime
|
||||
: sdk.forConsole.realtime;
|
||||
|
||||
return createRealtimeSubscription(realtimeInstance, channels, callback);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -170,8 +196,8 @@ export const sdk = {
|
||||
};
|
||||
|
||||
export enum RuleType {
|
||||
DEPLOYMENT = 'deployment',
|
||||
API = 'api',
|
||||
DEPLOYMENT = 'deployment',
|
||||
REDIRECT = 'redirect'
|
||||
}
|
||||
|
||||
@@ -185,6 +211,24 @@ export enum RuleTrigger {
|
||||
MANUAL = 'manual'
|
||||
}
|
||||
|
||||
export const createAdminClient = () => {
|
||||
return new Client().setEndpoint(getApiEndpoint()).setMode('admin').setProject(getProjectId());
|
||||
export type RealtimeResponse = {
|
||||
events: string[];
|
||||
channels: string[];
|
||||
timestamp: string;
|
||||
payload: unknown;
|
||||
};
|
||||
|
||||
export type AppwriteRealtimeResponseEvent = (response: RealtimeResponse) => void;
|
||||
|
||||
function createRealtimeSubscription(
|
||||
realtimeInstance: Realtime,
|
||||
channels: string | string[],
|
||||
callback: AppwriteRealtimeResponseEvent
|
||||
): () => void {
|
||||
const channelsArray = Array.isArray(channels) ? channels : [channels];
|
||||
const subscriptionPromise = realtimeInstance.subscribe(channelsArray, callback);
|
||||
|
||||
return () => {
|
||||
subscriptionPromise.then((sub) => sub.close());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ export function getFrameworkIcon(framework: string) {
|
||||
return 'vite';
|
||||
case framework.toLocaleLowerCase().includes('lynx'):
|
||||
return 'lynx';
|
||||
case framework.toLocaleLowerCase().includes('tanstack'):
|
||||
return 'tanstack';
|
||||
case framework.toLocaleLowerCase().includes('other'):
|
||||
return 'empty';
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ export const isStripeInitialized = writable(false);
|
||||
|
||||
export async function initializeStripe(node: HTMLElement) {
|
||||
if (!get(stripe)) return;
|
||||
|
||||
// cleanup any existing state
|
||||
await unmountPaymentElement();
|
||||
|
||||
isStripeInitialized.set(true);
|
||||
|
||||
const methods = await sdk.forConsole.billing.listPaymentMethods();
|
||||
@@ -54,10 +58,20 @@ export async function initializeStripe(node: HTMLElement) {
|
||||
|
||||
export async function unmountPaymentElement() {
|
||||
isStripeInitialized.set(false);
|
||||
paymentElement?.unmount();
|
||||
|
||||
if (paymentElement) {
|
||||
try {
|
||||
paymentElement.unmount();
|
||||
paymentElement.destroy();
|
||||
} catch (e) {
|
||||
console.debug('Payment element cleanup:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
elements = null;
|
||||
clientSecret = null;
|
||||
paymentMethod = null;
|
||||
elements = null;
|
||||
paymentElement = null;
|
||||
}
|
||||
|
||||
export async function submitStripeCard(name: string, organizationId?: string) {
|
||||
@@ -102,13 +116,32 @@ export async function submitStripeCard(name: string, organizationId?: string) {
|
||||
}
|
||||
|
||||
if (setupIntent && setupIntent.status === 'succeeded') {
|
||||
if ((setupIntent.payment_method as PaymentMethod).card?.country === 'US') {
|
||||
const pm = setupIntent.payment_method as PaymentMethod | string | undefined;
|
||||
// If Stripe returned an expanded PaymentMethod object, check the card country.
|
||||
// If it returned a string id (common), `typeof pm === 'string'` and we skip this.
|
||||
if (typeof pm !== 'string' && pm?.card?.country === 'US') {
|
||||
// need to get state
|
||||
return setupIntent.payment_method as PaymentMethod;
|
||||
return pm as PaymentMethod;
|
||||
}
|
||||
|
||||
// The backend expects a provider method ID (string). Extract the id
|
||||
// whether Stripe returned the id string or an expanded object.
|
||||
let providerId: string | undefined;
|
||||
if (typeof pm === 'string') {
|
||||
providerId = pm;
|
||||
} else {
|
||||
providerId = (pm as PaymentMethod)?.id;
|
||||
}
|
||||
|
||||
if (!providerId) {
|
||||
const e = new Error('Unable to verify payment method.');
|
||||
trackError(e, Submit.PaymentMethodCreate);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const method = await sdk.forConsole.billing.setPaymentMethod(
|
||||
paymentMethod.$id,
|
||||
(setupIntent.payment_method as PaymentMethod).id,
|
||||
providerId,
|
||||
name
|
||||
);
|
||||
paymentElement.destroy();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, ID, type Models, Sites, Storage } from '@appwrite.io/console';
|
||||
import { Client, Functions, ID, type Models, Sites, Storage } from '@appwrite.io/console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { getApiEndpoint } from '$lib/stores/sdk';
|
||||
import { page } from '$app/state';
|
||||
@@ -32,6 +32,13 @@ const temporarySites = (region: string, projectId: string) => {
|
||||
return new Sites(clientProject);
|
||||
};
|
||||
|
||||
const temporaryFunctions = (region: string, projectId: string) => {
|
||||
const clientProject = new Client().setMode('admin');
|
||||
const endpoint = getApiEndpoint(region);
|
||||
clientProject.setEndpoint(endpoint).setProject(projectId);
|
||||
return new Functions(clientProject);
|
||||
};
|
||||
|
||||
const createUploader = () => {
|
||||
const { subscribe, set, update } = writable<Uploader>({
|
||||
isOpen: false,
|
||||
@@ -110,7 +117,19 @@ const createUploader = () => {
|
||||
newFile.status = 'success';
|
||||
updateFile(newFile.$id, newFile);
|
||||
},
|
||||
uploadSiteDeployment: async (siteId: string, code: File) => {
|
||||
uploadSiteDeployment: async ({
|
||||
siteId,
|
||||
code,
|
||||
buildCommand,
|
||||
installCommand,
|
||||
outputDirectory
|
||||
}: {
|
||||
siteId: string;
|
||||
code: File;
|
||||
buildCommand?: string;
|
||||
installCommand?: string;
|
||||
outputDirectory?: string;
|
||||
}) => {
|
||||
const newDeployment: UploaderFile = {
|
||||
$id: '',
|
||||
resourceId: siteId,
|
||||
@@ -132,6 +151,49 @@ const createUploader = () => {
|
||||
siteId,
|
||||
code,
|
||||
activate: true,
|
||||
buildCommand,
|
||||
installCommand,
|
||||
outputDirectory,
|
||||
onProgress: (progress) => {
|
||||
newDeployment.$id = progress.$id;
|
||||
newDeployment.progress = progress.progress;
|
||||
newDeployment.status = progress.progress === 100 ? 'success' : 'pending';
|
||||
updateFile(progress.$id, newDeployment);
|
||||
}
|
||||
});
|
||||
newDeployment.$id = uploadedFile.$id;
|
||||
newDeployment.progress = 100;
|
||||
newDeployment.status = 'success';
|
||||
updateFile(newDeployment.$id, newDeployment);
|
||||
},
|
||||
uploadFunctionDeployment: async ({
|
||||
functionId,
|
||||
code
|
||||
}: {
|
||||
functionId: string;
|
||||
code: File;
|
||||
}) => {
|
||||
const newDeployment: UploaderFile = {
|
||||
$id: '',
|
||||
resourceId: functionId,
|
||||
name: code.name,
|
||||
size: code.size,
|
||||
progress: 0,
|
||||
status: 'pending'
|
||||
};
|
||||
update((n) => {
|
||||
n.isOpen = true;
|
||||
n.isCollapsed = false;
|
||||
n.files.unshift(newDeployment);
|
||||
return n;
|
||||
});
|
||||
const uploadedFile = await temporaryFunctions(
|
||||
page.params.region,
|
||||
page.params.project
|
||||
).createDeployment({
|
||||
functionId,
|
||||
code,
|
||||
activate: true,
|
||||
onProgress: (progress) => {
|
||||
newDeployment.$id = progress.$id;
|
||||
newDeployment.progress = progress.progress;
|
||||
|
||||
@@ -1,10 +1,51 @@
|
||||
<script>
|
||||
import { Container } from '$lib/layout';
|
||||
import { base } from '$app/paths';
|
||||
import { loading } from '$routes/store';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
loading.set(false);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<slot />
|
||||
</Container>
|
||||
<Layout.Stack
|
||||
height="100vh"
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
style="background: var(--bgcolor-neutral-primary, #fff);">
|
||||
<section class="console-container">
|
||||
<slot />
|
||||
</section>
|
||||
<footer>
|
||||
<Typography.Eyebrow color="--fgcolor-neutral-secondary">POWERED BY</Typography.Eyebrow>
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img
|
||||
src="{base}/images/appwrite-logo-dark.svg"
|
||||
width="120"
|
||||
height="22"
|
||||
alt="Appwrite Logo" />
|
||||
{:else}
|
||||
<img
|
||||
src="{base}/images/appwrite-logo-light.svg"
|
||||
width="120"
|
||||
height="22"
|
||||
alt="Appwrite Logo" />
|
||||
{/if}
|
||||
</footer>
|
||||
</Layout.Stack>
|
||||
|
||||
<style lang="scss">
|
||||
section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
footer {
|
||||
padding: 2rem 1rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { app } from '$lib/stores/app';
|
||||
import AppwriteLogoDark from '$lib/images/appwrite-logo-dark.svg';
|
||||
import AppwriteLogoLight from '$lib/images/appwrite-logo-light.svg';
|
||||
import { Vcs, Client } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import { getApiEndpoint } from '$lib/stores/sdk';
|
||||
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
export let data;
|
||||
const { data } = $props();
|
||||
|
||||
const endpoint = getApiEndpoint();
|
||||
const client = new Client();
|
||||
const vcs = new Vcs(client);
|
||||
|
||||
let installationId: string;
|
||||
let repositoryId: string;
|
||||
let providerPullRequestId: string;
|
||||
|
||||
let error = '';
|
||||
let success = '';
|
||||
let loading = false;
|
||||
let error = $state('');
|
||||
let success = $state('');
|
||||
let loading = $state(false);
|
||||
|
||||
onMount(async () => {
|
||||
repositoryId = data.repositoryId;
|
||||
installationId = data.installationId;
|
||||
providerPullRequestId = data.providerPullRequestId;
|
||||
|
||||
client.setEndpoint(endpoint).setProject(data.projectId).setMode('admin');
|
||||
});
|
||||
|
||||
@@ -39,9 +30,9 @@
|
||||
|
||||
try {
|
||||
await vcs.updateExternalDeployments({
|
||||
installationId,
|
||||
repositoryId,
|
||||
providerPullRequestId
|
||||
installationId: data.installationId,
|
||||
repositoryId: data.repositoryId,
|
||||
providerPullRequestId: data.providerPullRequestId
|
||||
});
|
||||
success = 'Deployment approved successfully! Build will start soon.';
|
||||
} catch (e) {
|
||||
@@ -52,49 +43,15 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="container" style="display: grid; place-items: center; min-height: 100vh;">
|
||||
<div class="u-flex u-flex-vertical u-cross-center" style="width: 100%">
|
||||
<div class="card" style="min-width: 600px; max-width: 100%;">
|
||||
<h1 class="heading-level-2">Authorize External Deployment</h1>
|
||||
<small style="margin-block-start: 8px;display: block;"
|
||||
>The deployment for pull request <code class="inline-code"
|
||||
>#{providerPullRequestId}</code> is awaiting approval. When authorized, deployments
|
||||
will be started.
|
||||
</small>
|
||||
|
||||
<div class="with-borders" style="margin-block-start: 1rem;display: block;">
|
||||
<button disabled={loading} on:click={approveDeployment} class="button" type="button"
|
||||
>Approve Deployment</button>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<p style="margin-block-start: 1rem" class="u-color-text-danger">{error}</p>
|
||||
{/if}
|
||||
|
||||
{#if success}
|
||||
<p style="margin-block-start: 1rem" class="u-color-text-success">{success}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="u-gap-4 u-flex u-main-center u-cross-center" style="margin-block-start: 2rem;">
|
||||
<span class="text">Powered by</span>
|
||||
<a
|
||||
href="https://appwrite.io/"
|
||||
target="_blank"
|
||||
style="display: grid;place-items: center;">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img
|
||||
src={AppwriteLogoDark}
|
||||
width="120"
|
||||
class="u-block u-only-dark"
|
||||
alt="Appwrite Logo" />
|
||||
{:else}
|
||||
<img
|
||||
src={AppwriteLogoLight}
|
||||
width="120"
|
||||
class="u-block u-only-light"
|
||||
alt="Appwrite Logo" />
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Layout.Stack gap="l" alignItems="center" style="max-width: 500px;">
|
||||
{#if success}
|
||||
<Badge type="success" variant="secondary" content={success} />
|
||||
{:else if error}
|
||||
<Badge type="error" variant="secondary" content={error} />
|
||||
{/if}
|
||||
<Typography.Title size="l" align="center">
|
||||
The deployment for pull request #{data.providerPullRequestId}
|
||||
is awaiting approval. When authorized, deployments will be started.
|
||||
</Typography.Title>
|
||||
<Button on:click={approveDeployment} secondary>Approve Deployment</Button>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
|
||||
let newProjName = '';
|
||||
let projectType: 'existing' | 'new' = 'existing';
|
||||
let newlyCreatedProject: Models.Project | null = null;
|
||||
|
||||
async function getProjects(orgId: string | null) {
|
||||
if (!orgId) {
|
||||
@@ -125,8 +126,9 @@
|
||||
});
|
||||
onExit();
|
||||
await invalidate(Dependencies.PROJECTS);
|
||||
const targetProject = newlyCreatedProject ?? currentSelectedProject;
|
||||
await goto(
|
||||
`${base}/project-${currentSelectedProject.region}-${currentSelectedProject.$id}/settings/migrations`
|
||||
`${base}/project-${targetProject.region ?? 'default'}-${targetProject.$id}/settings/migrations`
|
||||
);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
@@ -257,6 +259,7 @@
|
||||
} else {
|
||||
const project = await createNewProject();
|
||||
if (project !== null) {
|
||||
newlyCreatedProject = project;
|
||||
projectSdkInstance = sdk.forProject(
|
||||
project.region,
|
||||
project.$id
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
import {
|
||||
calculateTrialDay,
|
||||
checkForEnterpriseTrial,
|
||||
checkForMandate,
|
||||
checkForMarkedForDeletion,
|
||||
checkForMissingPaymentMethod,
|
||||
checkForNewDevUpgradePro,
|
||||
@@ -308,7 +307,6 @@
|
||||
if (org?.billingPlan !== BillingPlan.FREE) {
|
||||
await paymentExpired(org);
|
||||
await checkPaymentAuthorizationRequired(org);
|
||||
await checkForMandate(org);
|
||||
|
||||
if ($plansInfo.get(org.billingPlan)?.trialDays) {
|
||||
calculateTrialDay(org);
|
||||
@@ -334,8 +332,6 @@
|
||||
<CommandCenter />
|
||||
<Shell
|
||||
showSideNavigation={page.url.pathname !== '/' &&
|
||||
!page?.params.organization &&
|
||||
!page.url.pathname.includes(base + '/account') &&
|
||||
!page.url.pathname.includes(base + '/card') &&
|
||||
!page.url.pathname.includes(base + '/onboarding')}
|
||||
showHeader={!page.url.pathname.includes(base + '/onboarding/create-project')}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user