This commit is contained in:
Jesse Winton
2024-08-27 09:43:37 -04:00
parent c9f7fc75f1
commit 562058cf0e
319 changed files with 16360 additions and 15622 deletions

View File

@@ -1,30 +1,30 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
root: true,
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:svelte/recommended",
"prettier",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
extraFileExtensions: [".svelte"],
},
env: {
browser: true,
es2017: true,
node: true,
},
overrides: [
{
files: ["*.svelte"],
parser: "svelte-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
},
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
],
};

View File

@@ -1,23 +1,23 @@
name: Search Index
on:
push:
branches:
- main
push:
branches:
- main
jobs:
indexing:
runs-on: ubuntu-latest
name: Indexing
steps:
- uses: actions/checkout@v4
with:
repository: 'meilisearch/scrapix'
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
- run: yarn
- run: yarn start -c "$SCRAPIX_CONFIG"
env:
SCRAPIX_CONFIG: ${{ secrets.SCRAPIX_CONFIG }}
indexing:
runs-on: ubuntu-latest
name: Indexing
steps:
- uses: actions/checkout@v4
with:
repository: "meilisearch/scrapix"
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "20.x"
- run: yarn
- run: yarn start -c "$SCRAPIX_CONFIG"
env:
SCRAPIX_CONFIG: ${{ secrets.SCRAPIX_CONFIG }}

View File

@@ -1,79 +1,79 @@
name: Production deployment
on:
release:
types: [published]
release:
types: [published]
env:
TAG: ${{ github.event.release.tag_name }}
STACK_FILE: docker/production.yml
REPOSITORY: website
REGISTRY_USERNAME: christyjacob4
TAG: ${{ github.event.release.tag_name }}
STACK_FILE: docker/production.yml
REPOSITORY: website
REGISTRY_USERNAME: christyjacob4
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v2
build:
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Login to DockerHub
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ghcr.io/appwrite/website:${{ env.TAG }}
build-args: |
"PUBLIC_APPWRITE_ENDPOINT=${{ secrets.PUBLIC_APPWRITE_ENDPOINT }}"
"PUBLIC_APPWRITE_DASHBOARD=${{ secrets.PUBLIC_APPWRITE_DASHBOARD }}"
"PUBLIC_APPWRITE_PROJECT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_ID }}"
"PUBLIC_APPWRITE_DB_MAIN_ID=${{ vars.PUBLIC_APPWRITE_DB_MAIN_ID }}"
"PUBLIC_APPWRITE_COL_THREADS_ID=${{ vars.PUBLIC_APPWRITE_COL_THREADS_ID }}"
"PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ vars.PUBLIC_APPWRITE_COL_MESSAGES_ID }}"
"PUBLIC_APPWRITE_FN_TLDR_ID=${{ vars.PUBLIC_APPWRITE_FN_TLDR_ID }}"
"PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_INIT_ID }}"
"PUBLIC_GROWTH_ENDPOINT=${{ vars.PUBLIC_GROWTH_ENDPOINT }}"
"APPWRITE_DB_INIT_ID=${{ secrets.APPWRITE_DB_INIT_ID }}"
"APPWRITE_COL_INIT_ID=${{ secrets.APPWRITE_COL_INIT_ID }}"
"APPWRITE_API_KEY_INIT=${{ secrets.APPWRITE_API_KEY_INIT }}"
"GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ghcr.io/appwrite/website:${{ env.TAG }}
build-args: |
"PUBLIC_APPWRITE_ENDPOINT=${{ secrets.PUBLIC_APPWRITE_ENDPOINT }}"
"PUBLIC_APPWRITE_DASHBOARD=${{ secrets.PUBLIC_APPWRITE_DASHBOARD }}"
"PUBLIC_APPWRITE_PROJECT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_ID }}"
"PUBLIC_APPWRITE_DB_MAIN_ID=${{ vars.PUBLIC_APPWRITE_DB_MAIN_ID }}"
"PUBLIC_APPWRITE_COL_THREADS_ID=${{ vars.PUBLIC_APPWRITE_COL_THREADS_ID }}"
"PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ vars.PUBLIC_APPWRITE_COL_MESSAGES_ID }}"
"PUBLIC_APPWRITE_FN_TLDR_ID=${{ vars.PUBLIC_APPWRITE_FN_TLDR_ID }}"
"PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_INIT_ID }}"
"PUBLIC_GROWTH_ENDPOINT=${{ vars.PUBLIC_GROWTH_ENDPOINT }}"
"APPWRITE_DB_INIT_ID=${{ secrets.APPWRITE_DB_INIT_ID }}"
"APPWRITE_COL_INIT_ID=${{ secrets.APPWRITE_COL_INIT_ID }}"
"APPWRITE_API_KEY_INIT=${{ secrets.APPWRITE_API_KEY_INIT }}"
"GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Execute SSH commands
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRD_SSH_HOST }}
username: ${{ secrets.PRD_SSH_USERNAME }}
key: ${{ secrets.PRD_SSH_KEY }}
script: |
url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git"
if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then
echo "Clone failed because the folder ${{ env.REPOSITORY }} exists"
fi
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Execute SSH commands
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRD_SSH_HOST }}
username: ${{ secrets.PRD_SSH_USERNAME }}
key: ${{ secrets.PRD_SSH_KEY }}
script: |
url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git"
if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then
echo "Clone failed because the folder ${{ env.REPOSITORY }} exists"
fi
cd ${{ env.REPOSITORY }}
git reset --hard HEAD
git remote set-url origin $url
git fetch origin
git checkout ${{ env.TAG }}
cd ${{ env.REPOSITORY }}
git reset --hard HEAD
git remote set-url origin $url
git fetch origin
git checkout ${{ env.TAG }}
rm -f .env
echo "_APP_VERSION=${{ env.TAG }}" >> .env
echo "_APP_DOMAIN=${{ secrets.PRD_APP_DOMAIN }}" >> .env
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env
echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env
rm -f .env
echo "_APP_VERSION=${{ env.TAG }}" >> .env
echo "_APP_DOMAIN=${{ secrets.PRD_APP_DOMAIN }}" >> .env
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env
echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env
echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ env.REGISTRY_USERNAME }} --password-stdin
docker-compose -f ${{ env.STACK_FILE }} config
env $(cat .env | xargs) docker stack deploy --prune --resolve-image always --with-registry-auth -c ${{ env.STACK_FILE }} ${{ env.REPOSITORY }}
echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ env.REGISTRY_USERNAME }} --password-stdin
docker-compose -f ${{ env.STACK_FILE }} config
env $(cat .env | xargs) docker stack deploy --prune --resolve-image always --with-registry-auth -c ${{ env.STACK_FILE }} ${{ env.REPOSITORY }}

View File

@@ -1,81 +1,81 @@
name: Staging deployment
on:
workflow_dispatch:
push:
branches:
- main
workflow_dispatch:
push:
branches:
- main
env:
TAG: ${{ github.sha }}
STACK_FILE: docker/stage.yml
REPOSITORY: website
REGISTRY_USERNAME: christyjacob4
TAG: ${{ github.sha }}
STACK_FILE: docker/stage.yml
REPOSITORY: website
REGISTRY_USERNAME: christyjacob4
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v2
build:
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Login to DockerHub
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ghcr.io/appwrite/website:${{ env.TAG }}
build-args: |
"PUBLIC_APPWRITE_ENDPOINT=${{ secrets.PUBLIC_APPWRITE_ENDPOINT }}"
"PUBLIC_APPWRITE_DASHBOARD=${{ secrets.PUBLIC_APPWRITE_DASHBOARD }}"
"PUBLIC_APPWRITE_PROJECT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_ID }}"
"PUBLIC_APPWRITE_DB_MAIN_ID=${{ vars.PUBLIC_APPWRITE_DB_MAIN_ID }}"
"PUBLIC_APPWRITE_COL_THREADS_ID=${{ vars.PUBLIC_APPWRITE_COL_THREADS_ID }}"
"PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ vars.PUBLIC_APPWRITE_COL_MESSAGES_ID }}"
"PUBLIC_APPWRITE_FN_TLDR_ID=${{ vars.PUBLIC_APPWRITE_FN_TLDR_ID }}"
"PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_INIT_ID }}"
"PUBLIC_GROWTH_ENDPOINT=${{ vars.PUBLIC_GROWTH_ENDPOINT }}"
"APPWRITE_DB_INIT_ID=${{ secrets.APPWRITE_DB_INIT_ID }}"
"APPWRITE_COL_INIT_ID=${{ secrets.APPWRITE_COL_INIT_ID }}"
"APPWRITE_API_KEY_INIT=${{ secrets.APPWRITE_API_KEY_INIT }}"
"GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ghcr.io/appwrite/website:${{ env.TAG }}
build-args: |
"PUBLIC_APPWRITE_ENDPOINT=${{ secrets.PUBLIC_APPWRITE_ENDPOINT }}"
"PUBLIC_APPWRITE_DASHBOARD=${{ secrets.PUBLIC_APPWRITE_DASHBOARD }}"
"PUBLIC_APPWRITE_PROJECT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_ID }}"
"PUBLIC_APPWRITE_DB_MAIN_ID=${{ vars.PUBLIC_APPWRITE_DB_MAIN_ID }}"
"PUBLIC_APPWRITE_COL_THREADS_ID=${{ vars.PUBLIC_APPWRITE_COL_THREADS_ID }}"
"PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ vars.PUBLIC_APPWRITE_COL_MESSAGES_ID }}"
"PUBLIC_APPWRITE_FN_TLDR_ID=${{ vars.PUBLIC_APPWRITE_FN_TLDR_ID }}"
"PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_INIT_ID }}"
"PUBLIC_GROWTH_ENDPOINT=${{ vars.PUBLIC_GROWTH_ENDPOINT }}"
"APPWRITE_DB_INIT_ID=${{ secrets.APPWRITE_DB_INIT_ID }}"
"APPWRITE_COL_INIT_ID=${{ secrets.APPWRITE_COL_INIT_ID }}"
"APPWRITE_API_KEY_INIT=${{ secrets.APPWRITE_API_KEY_INIT }}"
"GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Execute SSH commands
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STG_SSH_HOST }}
username: ${{ secrets.STG_SSH_USERNAME }}
key: ${{ secrets.STG_SSH_KEY }}
script: |
url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git"
if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then
echo "Clone failed because the folder ${{ env.REPOSITORY }} exists"
fi
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Execute SSH commands
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STG_SSH_HOST }}
username: ${{ secrets.STG_SSH_USERNAME }}
key: ${{ secrets.STG_SSH_KEY }}
script: |
url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git"
if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then
echo "Clone failed because the folder ${{ env.REPOSITORY }} exists"
fi
cd ${{ env.REPOSITORY }}
git reset --hard HEAD
git remote set-url origin $url
git fetch origin
git checkout ${{ env.TAG }}
cd ${{ env.REPOSITORY }}
git reset --hard HEAD
git remote set-url origin $url
git fetch origin
git checkout ${{ env.TAG }}
rm -f .env
echo "_APP_VERSION=${{ env.TAG }}" >> .env
echo "_APP_DOMAIN=${{ secrets.STG_APP_DOMAIN }}" >> .env
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env
echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env
rm -f .env
echo "_APP_VERSION=${{ env.TAG }}" >> .env
echo "_APP_DOMAIN=${{ secrets.STG_APP_DOMAIN }}" >> .env
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env
echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env
echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ env.REGISTRY_USERNAME }} --password-stdin
docker-compose -f ${{ env.STACK_FILE }} config
env $(cat .env | xargs) docker stack deploy --prune --resolve-image always --with-registry-auth -c ${{ env.STACK_FILE }} ${{ env.REPOSITORY }}
echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ env.REGISTRY_USERNAME }} --password-stdin
docker-compose -f ${{ env.STACK_FILE }} config
env $(cat .env | xargs) docker stack deploy --prune --resolve-image always --with-registry-auth -c ${{ env.STACK_FILE }} ${{ env.REPOSITORY }}

View File

@@ -1,23 +1,23 @@
name: Mark stale issues
on:
schedule:
- cron: '0 0 * * *' # Midnight Runtime
schedule:
- cron: "0 0 * * *" # Midnight Runtime
jobs:
stale:
runs-on: ubuntu-latest
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue has been labeled as a 'question', indicating that it requires additional information from the requestor. It has been inactive for 7 days. If no further activity occurs, this issue will be closed in 14 days."
stale-issue-label: 'stale'
days-before-stale: 7
days-before-close: 14
remove-stale-when-updated: true
close-issue-message: 'This issue has been closed due to inactivity. If you still require assistance, please provide the requested information.'
close-issue-reason: 'not_planned'
operations-per-run: 100
only-labels: 'question'
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue has been labeled as a 'question', indicating that it requires additional information from the requestor. It has been inactive for 7 days. If no further activity occurs, this issue will be closed in 14 days."
stale-issue-label: "stale"
days-before-stale: 7
days-before-close: 14
remove-stale-when-updated: true
close-issue-message: "This issue has been closed due to inactivity. If you still require assistance, please provide the requested information."
close-issue-reason: "not_planned"
operations-per-run: 100
only-labels: "question"

View File

@@ -1,50 +1,50 @@
name: Tests
on:
pull_request_target:
branches: ['**']
pull_request_target:
branches: ["**"]
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install pnpm
run: corepack enable
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install pnpm
run: corepack enable
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Website
env:
NODE_OPTIONS: '--max_old_space_size=8192'
PUBLIC_APPWRITE_PROJECT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_ID }}
PUBLIC_APPWRITE_DB_MAIN_ID: ${{ secrets.PUBLIC_APPWRITE_DB_MAIN_ID }}
PUBLIC_APPWRITE_COL_THREADS_ID: ${{ secrets.PUBLIC_APPWRITE_COL_THREADS_ID }}
PUBLIC_APPWRITE_COL_MESSAGES_ID: ${{ secrets.PUBLIC_APPWRITE_COL_MESSAGES_ID }}
PUBLIC_APPWRITE_FN_TLDR_ID: ${{ secrets.PUBLIC_APPWRITE_FN_TLDR_ID }}
PUBLIC_APPWRITE_PROJECT_INIT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_INIT_ID }}
PUBLIC_GROWTH_ENDPOINT: ${{ secrets.PUBLIC_GROWTH_ENDPOINT }}
APPWRITE_DB_INIT_ID: ${{ secrets.APPWRITE_DB_INIT_ID }}
APPWRITE_COL_INIT_ID: ${{ secrets.APPWRITE_COL_INIT_ID }}
APPWRITE_API_KEY_INIT: ${{ secrets.APPWRITE_API_KEY_INIT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm run build
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Website
env:
NODE_OPTIONS: "--max_old_space_size=8192"
PUBLIC_APPWRITE_PROJECT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_ID }}
PUBLIC_APPWRITE_DB_MAIN_ID: ${{ secrets.PUBLIC_APPWRITE_DB_MAIN_ID }}
PUBLIC_APPWRITE_COL_THREADS_ID: ${{ secrets.PUBLIC_APPWRITE_COL_THREADS_ID }}
PUBLIC_APPWRITE_COL_MESSAGES_ID: ${{ secrets.PUBLIC_APPWRITE_COL_MESSAGES_ID }}
PUBLIC_APPWRITE_FN_TLDR_ID: ${{ secrets.PUBLIC_APPWRITE_FN_TLDR_ID }}
PUBLIC_APPWRITE_PROJECT_INIT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_INIT_ID }}
PUBLIC_GROWTH_ENDPOINT: ${{ secrets.PUBLIC_GROWTH_ENDPOINT }}
APPWRITE_DB_INIT_ID: ${{ secrets.APPWRITE_DB_INIT_ID }}
APPWRITE_COL_INIT_ID: ${{ secrets.APPWRITE_COL_INIT_ID }}
APPWRITE_API_KEY_INIT: ${{ secrets.APPWRITE_API_KEY_INIT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm run build

View File

@@ -5,5 +5,5 @@
# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
tasks:
- init: pnpm install && pnpm run build
command: pnpm run dev
- init: pnpm install && pnpm run build
command: pnpm run dev

View File

@@ -1,9 +1,9 @@
{
"useTabs": false,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
"useTabs": false,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@@ -49,9 +49,9 @@ Create ordered (numbered) and unordered (bulleted) lists using 1., \*, or -.
**Unordered List**:
```md
- Apple
- Banana
- Cherry
- Apple
- Banana
- Cherry
```
#### Links
@@ -130,19 +130,19 @@ Alternatively, use markdoc tables.
```md
{% table %}
- Heading 1
- Heading 2
- Heading 1
- Heading 2
---
- Row 1 Cell 1
- Row 1 Cell 2
- Row 1 Cell 1
- Row 1 Cell 2
---
- Row 2 Cell 1
- Row 2 cell 2
{% /table %}
- Row 2 Cell 1
- Row 2 cell 2
{% /table %}
```
#### Block Quotes

View File

@@ -60,11 +60,11 @@ doc-548-submit-a-pull-request-section-to-contribution-guide
When `TYPE` can be:
- **feat** - is a new feature
- **doc** - documentation only changes
- **cicd** - changes related to CI/CD system
- **fix** - a bug fix
- **refactor** - code change that neither fixes a bug nor adds a feature
- **feat** - is a new feature
- **doc** - documentation only changes
- **cicd** - changes related to CI/CD system
- **fix** - a bug fix
- **refactor** - code change that neither fixes a bug nor adds a feature
**All PRs must include a commit message with a description of the changes made!**

View File

@@ -10,8 +10,8 @@ The Appwrite Website repo features the main Appwrite website, including our [hom
The Appwrite Website has been built with the following frameworks:
- [Svelte](https://svelte.dev/)
- [SvelteKit](https://kit.svelte.dev/)
- [Svelte](https://svelte.dev/)
- [SvelteKit](https://kit.svelte.dev/)
## Development

View File

@@ -27,14 +27,14 @@ services:
build:
context: .
args:
- PUBLIC_APPWRITE_ENDPOINT=$PUBLIC_APPWRITE_ENDPOINT
- PUBLIC_APPWRITE_DASHBOARD=$PUBLIC_APPWRITE_DASHBOARD
- PUBLIC_APPWRITE_PROJECT_INIT_ID=$PUBLIC_APPWRITE_PROJECT_INIT_ID
- PUBLIC_APPWRITE_PROJECT_ID=$PUBLIC_APPWRITE_PROJECT_ID
- PUBLIC_APPWRITE_DB_MAIN_ID=$PUBLIC_APPWRITE_DB_MAIN_ID
- PUBLIC_APPWRITE_COL_THREADS_ID=$PUBLIC_APPWRITE_COL_THREADS_ID
- PUBLIC_APPWRITE_COL_MESSAGES_ID=$PUBLIC_APPWRITE_COL_MESSAGES_ID
- PUBLIC_APPWRITE_FN_TLDR_ID=$PUBLIC_APPWRITE_FN_TLDR_ID
- PUBLIC_APPWRITE_ENDPOINT=$PUBLIC_APPWRITE_ENDPOINT
- PUBLIC_APPWRITE_DASHBOARD=$PUBLIC_APPWRITE_DASHBOARD
- PUBLIC_APPWRITE_PROJECT_INIT_ID=$PUBLIC_APPWRITE_PROJECT_INIT_ID
- PUBLIC_APPWRITE_PROJECT_ID=$PUBLIC_APPWRITE_PROJECT_ID
- PUBLIC_APPWRITE_DB_MAIN_ID=$PUBLIC_APPWRITE_DB_MAIN_ID
- PUBLIC_APPWRITE_COL_THREADS_ID=$PUBLIC_APPWRITE_COL_THREADS_ID
- PUBLIC_APPWRITE_COL_MESSAGES_ID=$PUBLIC_APPWRITE_COL_MESSAGES_ID
- PUBLIC_APPWRITE_FN_TLDR_ID=$PUBLIC_APPWRITE_FN_TLDR_ID
restart: always
networks:
- homepage

View File

@@ -1,135 +1,135 @@
x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: '5'
max-size: '20m'
logging:
driver: "json-file"
options:
max-file: "5"
max-size: "20m"
x-update-config: &x-update-config
update_config:
order: stop-first
failure_action: rollback
parallelism: 1
delay: 10s
rollback_config:
failure_action: pause
monitor: 5s
parallelism: 2
order: stop-first
update_config:
order: stop-first
failure_action: rollback
parallelism: 1
delay: 10s
rollback_config:
failure_action: pause
monitor: 5s
parallelism: 2
order: stop-first
version: '3.8'
version: "3.8"
services:
traefik:
image: traefik:2.9
<<: *x-logging
command:
- --log.level=DEBUG
- --api.insecure=false
- --providers.docker=true
- --providers.docker.watch=true
- --providers.docker.swarmMode=true
- --providers.docker.exposedByDefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --certificatesresolvers.myresolver.acme.httpchallenge=true
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json
- --accesslog=true
ports:
- 80:80
- 443:443
volumes:
- /letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock
networks:
- cloud
deploy:
replicas: 3
<<: *x-update-config
placement:
max_replicas_per_node: 1
constraints:
- node.role == manager
preferences:
- spread: node.role == worker
labels:
- traefik.http.routers.traefik.middlewares=traefik-compress
- traefik.http.middlewares.traefik-compress.compress=true
traefik:
image: traefik:2.9
<<: *x-logging
command:
- --log.level=DEBUG
- --api.insecure=false
- --providers.docker=true
- --providers.docker.watch=true
- --providers.docker.swarmMode=true
- --providers.docker.exposedByDefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --certificatesresolvers.myresolver.acme.httpchallenge=true
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json
- --accesslog=true
ports:
- 80:80
- 443:443
volumes:
- /letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock
networks:
- cloud
deploy:
replicas: 3
<<: *x-update-config
placement:
max_replicas_per_node: 1
constraints:
- node.role == manager
preferences:
- spread: node.role == worker
labels:
- traefik.http.routers.traefik.middlewares=traefik-compress
- traefik.http.middlewares.traefik-compress.compress=true
server:
image: ghcr.io/appwrite/website:$_APP_VERSION
<<: *x-logging
networks:
- cloud
environment:
- PUBLIC_APPWRITE_PROJECT_INIT_ID
- PUBLIC_APPWRITE_PROJECT_ID
- PUBLIC_APPWRITE_DB_MAIN_ID
- PUBLIC_APPWRITE_COL_THREADS_ID
- PUBLIC_APPWRITE_COL_MESSAGES_ID
- PUBLIC_APPWRITE_FN_TLDR_ID
deploy:
<<: *x-update-config
mode: replicated
replicas: 8
placement:
max_replicas_per_node: 2
constraints:
- node.role == worker
preferences:
- spread: node.role == worker
labels:
- traefik.enable=true
- traefik.docker.lbswarm=true
- traefik.constraint-label-stack=appwrite
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
- traefik.http.middlewares.appwrite_middlewares.compress=true
#http
- traefik.http.routers.appwrite.entrypoints=web
- traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
- traefik.http.routers.appwrite.service=appwrite_service
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
# https
- traefik.http.routers.appwrite_secure.entrypoints=websecure
- traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
- traefik.http.routers.appwrite_secure.service=appwrite_service
- traefik.http.routers.appwrite_secure.tls=true
- traefik.http.routers.appwrite_secure.tls.certresolver=myresolver
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
server:
image: ghcr.io/appwrite/website:$_APP_VERSION
<<: *x-logging
networks:
- cloud
environment:
- PUBLIC_APPWRITE_PROJECT_INIT_ID
- PUBLIC_APPWRITE_PROJECT_ID
- PUBLIC_APPWRITE_DB_MAIN_ID
- PUBLIC_APPWRITE_COL_THREADS_ID
- PUBLIC_APPWRITE_COL_MESSAGES_ID
- PUBLIC_APPWRITE_FN_TLDR_ID
deploy:
<<: *x-update-config
mode: replicated
replicas: 8
placement:
max_replicas_per_node: 2
constraints:
- node.role == worker
preferences:
- spread: node.role == worker
labels:
- traefik.enable=true
- traefik.docker.lbswarm=true
- traefik.constraint-label-stack=appwrite
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
- traefik.http.middlewares.appwrite_middlewares.compress=true
#http
- traefik.http.routers.appwrite.entrypoints=web
- traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
- traefik.http.routers.appwrite.service=appwrite_service
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
# https
- traefik.http.routers.appwrite_secure.entrypoints=websecure
- traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
- traefik.http.routers.appwrite_secure.service=appwrite_service
- traefik.http.routers.appwrite_secure.tls=true
- traefik.http.routers.appwrite_secure.tls.certresolver=myresolver
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
janitor:
image: appwrite/docker-janitor
deploy:
mode: global
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TIME_BETWEEN_RUNS=3600
- UNUSED_TIME=6h
janitor:
image: appwrite/docker-janitor
deploy:
mode: global
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TIME_BETWEEN_RUNS=3600
- UNUSED_TIME=6h
sematext-agent:
image: sematext/agent:latest
environment:
REGION: EU
INFRA_TOKEN: $SEMATEXT_TOKEN
deploy:
mode: global
restart_policy:
condition: any
volumes:
- /:/hostfs:ro
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
- /sys:/host/sys:ro
- /dev:/hostfs/dev:ro
- /var/run:/var/run
- /sys/kernel/debug:/sys/kernel/debug
sematext-agent:
image: sematext/agent:latest
environment:
REGION: EU
INFRA_TOKEN: $SEMATEXT_TOKEN
deploy:
mode: global
restart_policy:
condition: any
volumes:
- /:/hostfs:ro
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
- /sys:/host/sys:ro
- /dev:/hostfs/dev:ro
- /var/run:/var/run
- /sys/kernel/debug:/sys/kernel/debug
networks:
cloud:
driver: overlay
cloud:
driver: overlay

View File

@@ -1,137 +1,137 @@
x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: '5'
max-size: '20m'
logging:
driver: "json-file"
options:
max-file: "5"
max-size: "20m"
x-update-config: &x-update-config
update_config:
order: stop-first
failure_action: rollback
parallelism: 1
delay: 10s
rollback_config:
failure_action: pause
monitor: 5s
parallelism: 2
order: stop-first
update_config:
order: stop-first
failure_action: rollback
parallelism: 1
delay: 10s
rollback_config:
failure_action: pause
monitor: 5s
parallelism: 2
order: stop-first
version: '3.8'
version: "3.8"
services:
traefik:
image: traefik:2.9
<<: *x-logging
command:
- --log.level=DEBUG
- --api.insecure=true
- --providers.docker=true
- --providers.docker.watch=true
- --providers.docker.swarmMode=true
- --providers.docker.exposedByDefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --certificatesresolvers.myresolver.acme.httpchallenge=true
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json
- --accesslog=true
ports:
- 80:80
- 443:443
- 8080:8080
volumes:
- /letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock
networks:
- cloud
deploy:
replicas: 3
<<: *x-update-config
placement:
max_replicas_per_node: 1
constraints:
- node.role == manager
preferences:
- spread: node.role == worker
labels:
- traefik.http.routers.traefik.middlewares=traefik-compress
- traefik.http.middlewares.traefik-compress.compress=true
traefik:
image: traefik:2.9
<<: *x-logging
command:
- --log.level=DEBUG
- --api.insecure=true
- --providers.docker=true
- --providers.docker.watch=true
- --providers.docker.swarmMode=true
- --providers.docker.exposedByDefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --certificatesresolvers.myresolver.acme.httpchallenge=true
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json
- --accesslog=true
ports:
- 80:80
- 443:443
- 8080:8080
volumes:
- /letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock
networks:
- cloud
deploy:
replicas: 3
<<: *x-update-config
placement:
max_replicas_per_node: 1
constraints:
- node.role == manager
preferences:
- spread: node.role == worker
labels:
- traefik.http.routers.traefik.middlewares=traefik-compress
- traefik.http.middlewares.traefik-compress.compress=true
server:
image: ghcr.io/appwrite/website:$_APP_VERSION
<<: *x-logging
networks:
- cloud
environment:
- PUBLIC_APPWRITE_PROJECT_INIT_ID
- PUBLIC_APPWRITE_PROJECT_ID
- PUBLIC_APPWRITE_DB_MAIN_ID
- PUBLIC_APPWRITE_COL_THREADS_ID
- PUBLIC_APPWRITE_COL_MESSAGES_ID
- PUBLIC_APPWRITE_FN_TLDR_ID
deploy:
<<: *x-update-config
mode: replicated
replicas: 8
placement:
max_replicas_per_node: 2
constraints:
- node.role == worker
preferences:
- spread: node.role == worker
labels:
- traefik.enable=true
- traefik.docker.lbswarm=true
- traefik.constraint-label-stack=appwrite
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
- traefik.http.middlewares.appwrite_middlewares.compress=true
#http
- traefik.http.routers.appwrite.entrypoints=web
- traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
- traefik.http.routers.appwrite.service=appwrite_service
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
# https
- traefik.http.routers.appwrite_secure.entrypoints=websecure
- traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
- traefik.http.routers.appwrite_secure.service=appwrite_service
- traefik.http.routers.appwrite_secure.tls=true
- traefik.http.routers.appwrite_secure.tls.certresolver=myresolver
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
server:
image: ghcr.io/appwrite/website:$_APP_VERSION
<<: *x-logging
networks:
- cloud
environment:
- PUBLIC_APPWRITE_PROJECT_INIT_ID
- PUBLIC_APPWRITE_PROJECT_ID
- PUBLIC_APPWRITE_DB_MAIN_ID
- PUBLIC_APPWRITE_COL_THREADS_ID
- PUBLIC_APPWRITE_COL_MESSAGES_ID
- PUBLIC_APPWRITE_FN_TLDR_ID
deploy:
<<: *x-update-config
mode: replicated
replicas: 8
placement:
max_replicas_per_node: 2
constraints:
- node.role == worker
preferences:
- spread: node.role == worker
labels:
- traefik.enable=true
- traefik.docker.lbswarm=true
- traefik.constraint-label-stack=appwrite
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
- traefik.http.middlewares.appwrite_middlewares.compress=true
#http
- traefik.http.routers.appwrite.entrypoints=web
- traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
- traefik.http.routers.appwrite.service=appwrite_service
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
# https
- traefik.http.routers.appwrite_secure.entrypoints=websecure
- traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
- traefik.http.routers.appwrite_secure.service=appwrite_service
- traefik.http.routers.appwrite_secure.tls=true
- traefik.http.routers.appwrite_secure.tls.certresolver=myresolver
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
janitor:
image: appwrite/docker-janitor
deploy:
mode: global
environment:
- TIME_BETWEEN_RUNS=600
- UNUSED_TIME=10m
- RUN_ON_STARTUP=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
janitor:
image: appwrite/docker-janitor
deploy:
mode: global
environment:
- TIME_BETWEEN_RUNS=600
- UNUSED_TIME=10m
- RUN_ON_STARTUP=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
sematext-agent:
image: sematext/agent:latest
environment:
REGION: EU
INFRA_TOKEN: $SEMATEXT_TOKEN
deploy:
mode: global
restart_policy:
condition: any
volumes:
- /:/hostfs:ro
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
- /sys:/host/sys:ro
- /dev:/hostfs/dev:ro
- /var/run:/var/run
- /sys/kernel/debug:/sys/kernel/debug
sematext-agent:
image: sematext/agent:latest
environment:
REGION: EU
INFRA_TOKEN: $SEMATEXT_TOKEN
deploy:
mode: global
restart_policy:
condition: any
volumes:
- /:/hostfs:ro
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
- /sys:/host/sys:ro
- /dev:/hostfs/dev:ro
- /var/run:/var/run
- /sys/kernel/debug:/sys/kernel/debug
networks:
cloud:
driver: overlay
cloud:
driver: overlay

View File

@@ -1,12 +1,12 @@
[
{
"id": "appwrite",
"path": "src/",
"schema": {
"path": ".svelte-kit/markdoc_schema.js",
"type": "esm",
"property": "default",
"watch": true
}
{
"id": "appwrite",
"path": "src/",
"schema": {
"path": ".svelte-kit/markdoc_schema.js",
"type": "esm",
"property": "default",
"watch": true
}
}
]

View File

@@ -1,94 +1,94 @@
{
"name": "appwrite-website",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"build": "node ./scripts/build.js",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i",
"dev": "vite dev",
"download-contributors": "node ./scripts/download-contributor-data.js",
"format": "prettier --plugin-search-dir . --write .",
"icons:build": "node ./src/icons/build.js",
"icons:generate": "node ./src/icons/optimize.js && node ./src/icons/build.js",
"icons:optimize": "node ./src/icons/optimize.js",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"preview": "vite preview",
"test": "npm run test:integration && npm run test:unit",
"test:integration": "playwright test",
"test:unit": "vitest",
"optimize": "node ./scripts/optimize-assets.js"
},
"packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a",
"dependencies": {
"@sentry/sveltekit": "^8.12.0",
"clsx": "^2.1.1",
"cva": "npm:class-variance-authority@^0.7.0",
"fuse.js": "^7.0.0",
"h3": "^1.12.0",
"sharp": "^0.33.4",
"tailwind-merge": "^2.4.0"
},
"prettier": {
"plugins": [
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss"
]
},
"devDependencies": {
"@appwrite.io/console": "^0.6.2",
"@appwrite.io/pink": "~0.26.0",
"@appwrite.io/pink-icons": "~0.26.0",
"@appwrite.io/repo": "github:appwrite/appwrite#1.6.x",
"@internationalized/date": "3.5.0",
"@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.74.4",
"@playwright/test": "^1.44.1",
"@sveltejs/adapter-node": "^4.0.1",
"@sveltejs/enhanced-img": "^0.1.9",
"@sveltejs/kit": "^2.5.17",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@tailwindcss/postcss": "4.0.0-alpha.17",
"@types/compression": "^1.7.5",
"@types/glob": "^8.1.0",
"@types/markdown-it": "^13.0.8",
"@types/morgan": "^1.9.9",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",
"date-fns": "^3.6.0",
"dequal": "^2.0.3",
"embla-carousel": "^8.1.5",
"embla-carousel-svelte": "^8.1.5",
"embla-carousel-wheel-gestures": "^8.0.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-svelte": "^2.40.0",
"highlight.js": "^11.9.0",
"markdown-it": "^14.1.0",
"meilisearch": "^0.37.0",
"motion": "^10.18.0",
"node-html-parser": "^6.1.13",
"openapi-types": "^12.1.3",
"oslllo-svg-fixer": "^3.0.0",
"postcss": "^8.4.39",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.6.5",
"remeda": "^2.10.0",
"sass": "^1.77.6",
"svelte": "^4.2.18",
"svelte-check": "^3.8.1",
"svelte-markdoc-preprocess": "^2.0.0",
"svelte-markdown": "^0.4.1",
"svgtofont": "^4.2.1",
"tailwindcss": "4.0.0-alpha.17",
"tslib": "^2.6.3",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vite-plugin-dynamic-import": "^1.5.0",
"vite-plugin-image-optimizer": "^1.1.8",
"vitest": "^1.6.0"
}
"name": "appwrite-website",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"build": "node ./scripts/build.js",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i",
"dev": "vite dev",
"download-contributors": "node ./scripts/download-contributor-data.js",
"format": "prettier --plugin-search-dir . --write .",
"icons:build": "node ./src/icons/build.js",
"icons:generate": "node ./src/icons/optimize.js && node ./src/icons/build.js",
"icons:optimize": "node ./src/icons/optimize.js",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"preview": "vite preview",
"test": "npm run test:integration && npm run test:unit",
"test:integration": "playwright test",
"test:unit": "vitest",
"optimize": "node ./scripts/optimize-assets.js"
},
"packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a",
"dependencies": {
"@sentry/sveltekit": "^8.12.0",
"clsx": "^2.1.1",
"cva": "npm:class-variance-authority@^0.7.0",
"fuse.js": "^7.0.0",
"h3": "^1.12.0",
"sharp": "^0.33.4",
"tailwind-merge": "^2.4.0"
},
"prettier": {
"plugins": [
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss"
]
},
"devDependencies": {
"@appwrite.io/console": "^0.6.2",
"@appwrite.io/pink": "~0.26.0",
"@appwrite.io/pink-icons": "~0.26.0",
"@appwrite.io/repo": "github:appwrite/appwrite#1.6.x",
"@internationalized/date": "3.5.0",
"@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.74.4",
"@playwright/test": "^1.44.1",
"@sveltejs/adapter-node": "^4.0.1",
"@sveltejs/enhanced-img": "^0.1.9",
"@sveltejs/kit": "^2.5.17",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@tailwindcss/postcss": "4.0.0-alpha.17",
"@types/compression": "^1.7.5",
"@types/glob": "^8.1.0",
"@types/markdown-it": "^13.0.8",
"@types/morgan": "^1.9.9",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",
"date-fns": "^3.6.0",
"dequal": "^2.0.3",
"embla-carousel": "^8.1.5",
"embla-carousel-svelte": "^8.1.5",
"embla-carousel-wheel-gestures": "^8.0.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-svelte": "^2.40.0",
"highlight.js": "^11.9.0",
"markdown-it": "^14.1.0",
"meilisearch": "^0.37.0",
"motion": "^10.18.0",
"node-html-parser": "^6.1.13",
"openapi-types": "^12.1.3",
"oslllo-svg-fixer": "^3.0.0",
"postcss": "^8.4.39",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.6.5",
"remeda": "^2.10.0",
"sass": "^1.77.6",
"svelte": "^4.2.18",
"svelte-check": "^3.8.1",
"svelte-markdoc-preprocess": "^2.0.0",
"svelte-markdown": "^0.4.1",
"svgtofont": "^4.2.1",
"tailwindcss": "4.0.0-alpha.17",
"tslib": "^2.6.3",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vite-plugin-dynamic-import": "^1.5.0",
"vite-plugin-image-optimizer": "^1.1.8",
"vitest": "^1.6.0"
}
}

View File

@@ -1,12 +1,12 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import type { PlaywrightTestConfig } from "@playwright/test";
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
webServer: {
command: "npm run build && npm run preview",
port: 4173,
},
testDir: "tests",
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
};
export default config;

View File

@@ -1,5 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {}
}
plugins: {
"@tailwindcss/postcss": {},
},
};

View File

@@ -1,9 +1,9 @@
import { build } from 'vite';
import { downloadContributors } from './download-contributor-data.js';
import { build } from "vite";
import { downloadContributors } from "./download-contributor-data.js";
async function main() {
await downloadContributors();
await build();
await downloadContributors();
await build();
}
main();

View File

@@ -1,100 +1,108 @@
import fs from 'fs';
import fs from "fs";
const perPage = 100;
const outputFile = `./src/lib/contributors.ts`;
const headers = process.env.GITHUB_TOKEN
? {
Authorization: `token ${process.env.GITHUB_TOKEN}`
}
: {};
? {
Authorization: `token ${process.env.GITHUB_TOKEN}`,
}
: {};
console.log(`using github token: ${!!process.env.GITHUB_TOKEN}`);
async function fetchRepositories() {
let page = 1;
let repositoriesData = [];
let hasMoreData = true;
let page = 1;
let repositoriesData = [];
let hasMoreData = true;
while (hasMoreData) {
console.log(`Fetching page ${page} of repositories...`);
const url = `https://api.github.com/orgs/appwrite/repos?page=${page}&per_page=${perPage}`;
while (hasMoreData) {
console.log(`Fetching page ${page} of repositories...`);
const url = `https://api.github.com/orgs/appwrite/repos?page=${page}&per_page=${perPage}`;
const response = await fetch(url, {
headers
});
const response = await fetch(url, {
headers,
});
if (!response.ok) {
console.error(`Failed to fetch data from ${url}`);
break;
}
const data = await response.json();
if (data.length === 0) {
hasMoreData = false;
} else {
repositoriesData = repositoriesData.concat(data);
page++;
}
console.log(`Fetched ${data.length} repositories. Total: ${repositoriesData.length}...\n`);
if (!response.ok) {
console.error(`Failed to fetch data from ${url}`);
break;
}
return repositoriesData.map((repo) => repo.full_name);
const data = await response.json();
if (data.length === 0) {
hasMoreData = false;
} else {
repositoriesData = repositoriesData.concat(data);
page++;
}
console.log(
`Fetched ${data.length} repositories. Total: ${repositoriesData.length}...\n`,
);
}
return repositoriesData.map((repo) => repo.full_name);
}
async function fetchContributors(apiUrl) {
let page = 1;
let contributorsData = [];
let hasMoreData = true;
let page = 1;
let contributorsData = [];
let hasMoreData = true;
while (hasMoreData) {
console.log(`Fetching page ${page} of contributors...`);
const url = `${apiUrl}?page=${page}&per_page=${perPage}`;
const response = await fetch(url, {
headers
});
while (hasMoreData) {
console.log(`Fetching page ${page} of contributors...`);
const url = `${apiUrl}?page=${page}&per_page=${perPage}`;
const response = await fetch(url, {
headers,
});
if (!response.ok) {
console.error(`Failed to fetch data from ${url}`, await response.text());
break;
}
const data = await response.json();
if (data.length === 0) {
hasMoreData = false;
} else {
contributorsData = contributorsData.concat(data.map((c) => c.login));
page++;
}
console.log(`Fetched ${data.length} contributors. Total: ${contributorsData.length}...\n`);
if (!response.ok) {
console.error(`Failed to fetch data from ${url}`, await response.text());
break;
}
return contributorsData;
const data = await response.json();
if (data.length === 0) {
hasMoreData = false;
} else {
contributorsData = contributorsData.concat(data.map((c) => c.login));
page++;
}
console.log(
`Fetched ${data.length} contributors. Total: ${contributorsData.length}...\n`,
);
}
return contributorsData;
}
export async function downloadContributors() {
const contributors = new Set();
const contributors = new Set();
for (const repo of ['appwrite/appwrite', 'appwrite/console', 'appwrite/sdk-generator']) {
console.log(`Fetching contributors for ${repo}...`);
const url = `https://api.github.com/repos/${repo}/contributors`;
const data = await fetchContributors(url);
for (const repo of [
"appwrite/appwrite",
"appwrite/console",
"appwrite/sdk-generator",
]) {
console.log(`Fetching contributors for ${repo}...`);
const url = `https://api.github.com/repos/${repo}/contributors`;
const data = await fetchContributors(url);
data.forEach((contributor) => contributors.add(contributor));
}
data.forEach((contributor) => contributors.add(contributor));
}
const contributorsArray = Array.from(contributors);
const contributorsString = JSON.stringify(contributorsArray, null, 4);
const contributorsFile = `export const contributors = ${contributorsString};`;
const contributorsArray = Array.from(contributors);
const contributorsString = JSON.stringify(contributorsArray, null, 4);
const contributorsFile = `export const contributors = ${contributorsString};`;
const currentContributors = fs.readFileSync(outputFile, 'utf8');
if (currentContributors.length >= contributorsFile.length) {
console.log('No new contributors found. Exiting...');
return;
}
const currentContributors = fs.readFileSync(outputFile, "utf8");
if (currentContributors.length >= contributorsFile.length) {
console.log("No new contributors found. Exiting...");
return;
}
fs.writeFileSync(outputFile, contributorsFile);
fs.writeFileSync(outputFile, contributorsFile);
}

View File

@@ -1,11 +1,11 @@
import { readdirSync, statSync } from 'fs';
import { join, relative } from 'path';
import sharp from 'sharp';
import { fileURLToPath } from 'url';
import { readdirSync, statSync } from "fs";
import { join, relative } from "path";
import sharp from "sharp";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const root_dir = join(__dirname, '../static');
const exceptions = ['assets/'];
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const root_dir = join(__dirname, "../static");
const exceptions = ["assets/"];
/**
* @type {{
@@ -17,78 +17,79 @@ const exceptions = ['assets/'];
* }}
*/
const config = {
jpeg: {
quality: 100
},
webp: {
lossless: true
},
png: {
quality: 100
},
gif: {
quality: 100
},
avif: {
lossless: true
}
jpeg: {
quality: 100,
},
webp: {
lossless: true,
},
png: {
quality: 100,
},
gif: {
quality: 100,
},
avif: {
lossless: true,
},
};
/** @type {sharp.ResizeOptions} */
const resize_config = {
width: 1280,
height: 1280,
fit: sharp.fit.inside,
withoutEnlargement: true
width: 1280,
height: 1280,
fit: sharp.fit.inside,
withoutEnlargement: true,
};
function* walk_directory(dir) {
const files = readdirSync(dir);
const files = readdirSync(dir);
for (const file of files) {
const pathToFile = join(dir, file);
const isDirectory = statSync(pathToFile).isDirectory();
if (isDirectory) {
yield* walk_directory(pathToFile);
} else {
yield pathToFile;
}
for (const file of files) {
const pathToFile = join(dir, file);
const isDirectory = statSync(pathToFile).isDirectory();
if (isDirectory) {
yield* walk_directory(pathToFile);
} else {
yield pathToFile;
}
}
}
function is_image(file) {
const extension = file.split('.').pop().toLowerCase();
const extension = file.split(".").pop().toLowerCase();
return Object.keys(config).includes(extension);
return Object.keys(config).includes(extension);
}
function get_relative_path(file) {
return relative(root_dir, file);
return relative(root_dir, file);
}
async function main() {
for (const file of walk_directory(join(__dirname, '../static'))) {
const relative_path = get_relative_path(file);
const is_animated = file.endsWith('.gif');
if (!is_image(file)) continue;
if (exceptions.some((exception) => relative_path.startsWith(exception))) continue;
for (const file of walk_directory(join(__dirname, "../static"))) {
const relative_path = get_relative_path(file);
const is_animated = file.endsWith(".gif");
if (!is_image(file)) continue;
if (exceptions.some((exception) => relative_path.startsWith(exception)))
continue;
const image = sharp(file, {
animated: is_animated
});
const size_before = (await image.toBuffer()).length;
const meta = await image.metadata();
const buffer = await image[meta.format](config[meta.format])
.resize(resize_config)
.toBuffer();
const size_after = buffer.length;
const image = sharp(file, {
animated: is_animated,
});
const size_before = (await image.toBuffer()).length;
const meta = await image.metadata();
const buffer = await image[meta.format](config[meta.format])
.resize(resize_config)
.toBuffer();
const size_after = buffer.length;
if (size_after >= size_before) continue;
if (size_after >= size_before) continue;
console.log(relative_path);
console.log(relative_path);
await sharp(buffer).toFile(file);
}
await sharp(buffer).toFile(file);
}
}
await main();

View File

@@ -1,17 +1,17 @@
import { createApp, fromNodeMiddleware, toNodeListener } from 'h3';
import { createServer } from 'node:http';
import { handler } from '../build/handler.js';
import { sitemap } from './sitemap.js';
import { createApp, fromNodeMiddleware, toNodeListener } from "h3";
import { createServer } from "node:http";
import { handler } from "../build/handler.js";
import { sitemap } from "./sitemap.js";
async function main() {
const port = process.env.PORT || 3000;
const app = createApp();
app.use('/sitemap.xml', await sitemap());
app.use(fromNodeMiddleware(handler));
const server = createServer(toNodeListener(app)).listen(port);
server.addListener('listening', () => {
console.log(`Listening on http://0.0.0.0:${port}`);
});
const port = process.env.PORT || 3000;
const app = createApp();
app.use("/sitemap.xml", await sitemap());
app.use(fromNodeMiddleware(handler));
const server = createServer(toNodeListener(app)).listen(port);
server.addListener("listening", () => {
console.log(`Listening on http://0.0.0.0:${port}`);
});
}
main();

View File

@@ -1,43 +1,45 @@
import { createRequire } from 'node:module';
import { defineEventHandler, setResponseHeader } from 'h3';
import { createRequire } from "node:module";
import { defineEventHandler, setResponseHeader } from "h3";
/**
* @returns {Promise<import('h3').EventHandler>}
*/
export async function sitemap() {
console.info('Preparing Sitemap...');
const manifest = await import('../build/server/manifest.js');
const prerendered = manifest.prerendered;
const file_route_extensions = ['.json', '.xml'];
const routes = [...prerendered, ...collectThreads()].filter(
(route) => !file_route_extensions.some((ext) => route.endsWith(ext))
);
console.info(`Sitemap loaded with ${routes.length} routes!`);
console.info("Preparing Sitemap...");
const manifest = await import("../build/server/manifest.js");
const prerendered = manifest.prerendered;
const file_route_extensions = [".json", ".xml"];
const routes = [...prerendered, ...collectThreads()].filter(
(route) => !file_route_extensions.some((ext) => route.endsWith(ext)),
);
console.info(`Sitemap loaded with ${routes.length} routes!`);
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${routes
.map(
(route) => `<url>
.map(
(route) => `<url>
<loc>https://appwrite.io${route}</loc>
</url>
`
)
.join('')}
`,
)
.join("")}
</urlset>`;
return defineEventHandler((event) => {
setResponseHeader(event, 'Content-Type', 'application/xml');
return defineEventHandler((event) => {
setResponseHeader(event, "Content-Type", "application/xml");
return sitemap;
});
return sitemap;
});
}
/**
* @returns {string[]}
*/
function collectThreads() {
const threads = createRequire(import.meta.url)('../build/prerendered/threads/data.json');
const threads = createRequire(import.meta.url)(
"../build/prerendered/threads/data.json",
);
return threads.map((id) => `/threads/${id}`);
return threads.map((id) => `/threads/${id}`);
}

14
src/app.d.ts vendored
View File

@@ -1,14 +1,14 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
interface PageData {
changelogEntries: number;
}
// interface Platform {}
namespace App {
// interface Error {}
// interface Locals {}
interface PageData {
changelogEntries: number;
}
// interface Platform {}
}
}
export {};

View File

@@ -1,30 +1,30 @@
import { dev } from '$app/environment';
import { SENTRY_DSN } from '$lib/constants';
import { handleErrorWithSentry, replayIntegration } from '@sentry/sveltekit';
import * as Sentry from '@sentry/sveltekit';
import { dev } from "$app/environment";
import { SENTRY_DSN } from "$lib/constants";
import { handleErrorWithSentry, replayIntegration } from "@sentry/sveltekit";
import * as Sentry from "@sentry/sveltekit";
Sentry.init({
enabled: !dev,
dsn: SENTRY_DSN,
allowUrls: [/appwrite\.io/],
tracesSampleRate: 1.0,
enabled: !dev,
dsn: SENTRY_DSN,
allowUrls: [/appwrite\.io/],
tracesSampleRate: 1.0,
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0,
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0,
// If the entire session is not sampled, use the below sample rate to sample
// sessions when an error occurs.
replaysOnErrorSampleRate: 1.0,
// If the entire session is not sampled, use the below sample rate to sample
// sessions when an error occurs.
replaysOnErrorSampleRate: 1.0,
// If you don't want to use Session Replay, just remove the line below:
integrations: [
replayIntegration({
maskAllInputs: true,
maskAllText: false,
blockAllMedia: false
})
]
// If you don't want to use Session Replay, just remove the line below:
integrations: [
replayIntegration({
maskAllInputs: true,
maskAllText: false,
blockAllMedia: false,
}),
],
});
// If you have a custom error handler, pass it to `handleErrorWithSentry`

View File

@@ -1,39 +1,46 @@
import * as Sentry from '@sentry/sveltekit';
import type { Handle } from '@sveltejs/kit';
import redirects from './redirects.json';
import { sequence } from '@sveltejs/kit/hooks';
import { BANNER_KEY, SENTRY_DSN } from '$lib/constants';
import { dev } from '$app/environment';
import * as Sentry from "@sentry/sveltekit";
import type { Handle } from "@sveltejs/kit";
import redirects from "./redirects.json";
import { sequence } from "@sveltejs/kit/hooks";
import { BANNER_KEY, SENTRY_DSN } from "$lib/constants";
import { dev } from "$app/environment";
Sentry.init({
enabled: !dev,
dsn: SENTRY_DSN,
tracesSampleRate: 1,
allowUrls: [/appwrite\.io/]
enabled: !dev,
dsn: SENTRY_DSN,
tracesSampleRate: 1,
allowUrls: [/appwrite\.io/],
});
const redirectMap = new Map(redirects.map(({ link, redirect }) => [link, redirect]));
const redirectMap = new Map(
redirects.map(({ link, redirect }) => [link, redirect]),
);
const redirecter: Handle = async ({ event, resolve }) => {
const currentPath = event.url.pathname;
if (redirectMap.has(currentPath)) {
return new Response(null, {
status: 308,
headers: {
location: redirectMap.get(currentPath) ?? ''
}
});
}
const currentPath = event.url.pathname;
if (redirectMap.has(currentPath)) {
return new Response(null, {
status: 308,
headers: {
location: redirectMap.get(currentPath) ?? "",
},
});
}
return await resolve(event);
return await resolve(event);
};
const bannerRewriter: Handle = async ({ event, resolve }) => {
const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('%aw_banner_key%', BANNER_KEY)
});
return response;
const response = await resolve(event, {
transformPageChunk: ({ html }) =>
html.replace("%aw_banner_key%", BANNER_KEY),
});
return response;
};
export const handle = sequence(Sentry.sentryHandle(), redirecter, bannerRewriter);
export const handle = sequence(
Sentry.sentryHandle(),
redirecter,
bannerRewriter,
);
export const handleError = Sentry.handleErrorWithSentry();

View File

@@ -1,3 +1,3 @@
import { generateIcons } from './scripts.js';
import { generateIcons } from "./scripts.js";
generateIcons();

View File

@@ -1,3 +1,3 @@
import { optimizeSVG } from './scripts.js';
import { optimizeSVG } from "./scripts.js";
optimizeSVG();

View File

@@ -1,71 +1,178 @@
@font-face {
font-family: "web-icon";
font-display: swap;
src: url('web-icon.eot'); /* IE9*/
src: url('web-icon.eot#iefix') format('embedded-opentype'), /* IE6-IE8 */
url("web-icon.woff2") format("woff2"),
font-family: "web-icon";
font-display: swap;
src: url("web-icon.eot"); /* IE9*/
src:
url("web-icon.eot#iefix") format("embedded-opentype"),
/* IE6-IE8 */ url("web-icon.woff2") format("woff2"),
url("web-icon.woff") format("woff"),
url('web-icon.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('web-icon.svg#web-icon') format('svg'); /* iOS 4.1- */
url("web-icon.ttf") format("truetype"),
/* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url("web-icon.svg#web-icon") format("svg"); /* iOS 4.1- */
}
[class^="web-icon-"], [class*=" web-icon-"] {
font-family: 'web-icon' !important;
font-size: 20px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
[class^="web-icon-"],
[class*=" web-icon-"] {
font-family: "web-icon" !important;
font-size: 20px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.web-icon-apple:before { content: "\ea01"; }
.web-icon-appwrite:before { content: "\ea02"; }
.web-icon-arrow-down:before { content: "\ea03"; }
.web-icon-arrow-ext-link:before { content: "\ea04"; }
.web-icon-arrow-left:before { content: "\ea05"; }
.web-icon-arrow-right:before { content: "\ea06"; }
.web-icon-arrow-up:before { content: "\ea07"; }
.web-icon-calendar:before { content: "\ea08"; }
.web-icon-check:before { content: "\ea09"; }
.web-icon-chevron-down:before { content: "\ea0a"; }
.web-icon-chevron-left:before { content: "\ea0b"; }
.web-icon-chevron-right:before { content: "\ea0c"; }
.web-icon-chevron-up:before { content: "\ea0d"; }
.web-icon-close:before { content: "\ea0e"; }
.web-icon-command:before { content: "\ea0f"; }
.web-icon-copy:before { content: "\ea10"; }
.web-icon-daily-dev:before { content: "\ea11"; }
.web-icon-dark:before { content: "\ea12"; }
.web-icon-discord:before { content: "\ea13"; }
.web-icon-divider-vertical:before { content: "\ea14"; }
.web-icon-download:before { content: "\ea15"; }
.web-icon-ext-link:before { content: "\ea16"; }
.web-icon-firebase:before { content: "\ea17"; }
.web-icon-github:before { content: "\ea18"; }
.web-icon-google:before { content: "\ea19"; }
.web-icon-hamburger-menu:before { content: "\ea1a"; }
.web-icon-light:before { content: "\ea1b"; }
.web-icon-linkedin:before { content: "\ea1c"; }
.web-icon-location:before { content: "\ea1d"; }
.web-icon-logout-left:before { content: "\ea1e"; }
.web-icon-logout-right:before { content: "\ea1f"; }
.web-icon-mailgun:before { content: "\ea20"; }
.web-icon-message:before { content: "\ea21"; }
.web-icon-microsoft:before { content: "\ea22"; }
.web-icon-minus:before { content: "\ea23"; }
.web-icon-nuxt:before { content: "\ea24"; }
.web-icon-platform:before { content: "\ea25"; }
.web-icon-play:before { content: "\ea26"; }
.web-icon-plus:before { content: "\ea27"; }
.web-icon-product-hunt:before { content: "\ea28"; }
.web-icon-refine:before { content: "\ea29"; }
.web-icon-rest:before { content: "\ea2a"; }
.web-icon-search:before { content: "\ea2b"; }
.web-icon-sendgrid:before { content: "\ea2c"; }
.web-icon-star:before { content: "\ea2d"; }
.web-icon-system:before { content: "\ea2e"; }
.web-icon-textmagic:before { content: "\ea2f"; }
.web-icon-twitter:before { content: "\ea30"; }
.web-icon-vue:before { content: "\ea31"; }
.web-icon-x:before { content: "\ea32"; }
.web-icon-ycombinator:before { content: "\ea33"; }
.web-icon-youtube:before { content: "\ea34"; }
.web-icon-apple:before {
content: "\ea01";
}
.web-icon-appwrite:before {
content: "\ea02";
}
.web-icon-arrow-down:before {
content: "\ea03";
}
.web-icon-arrow-ext-link:before {
content: "\ea04";
}
.web-icon-arrow-left:before {
content: "\ea05";
}
.web-icon-arrow-right:before {
content: "\ea06";
}
.web-icon-arrow-up:before {
content: "\ea07";
}
.web-icon-calendar:before {
content: "\ea08";
}
.web-icon-check:before {
content: "\ea09";
}
.web-icon-chevron-down:before {
content: "\ea0a";
}
.web-icon-chevron-left:before {
content: "\ea0b";
}
.web-icon-chevron-right:before {
content: "\ea0c";
}
.web-icon-chevron-up:before {
content: "\ea0d";
}
.web-icon-close:before {
content: "\ea0e";
}
.web-icon-command:before {
content: "\ea0f";
}
.web-icon-copy:before {
content: "\ea10";
}
.web-icon-daily-dev:before {
content: "\ea11";
}
.web-icon-dark:before {
content: "\ea12";
}
.web-icon-discord:before {
content: "\ea13";
}
.web-icon-divider-vertical:before {
content: "\ea14";
}
.web-icon-download:before {
content: "\ea15";
}
.web-icon-ext-link:before {
content: "\ea16";
}
.web-icon-firebase:before {
content: "\ea17";
}
.web-icon-github:before {
content: "\ea18";
}
.web-icon-google:before {
content: "\ea19";
}
.web-icon-hamburger-menu:before {
content: "\ea1a";
}
.web-icon-light:before {
content: "\ea1b";
}
.web-icon-linkedin:before {
content: "\ea1c";
}
.web-icon-location:before {
content: "\ea1d";
}
.web-icon-logout-left:before {
content: "\ea1e";
}
.web-icon-logout-right:before {
content: "\ea1f";
}
.web-icon-mailgun:before {
content: "\ea20";
}
.web-icon-message:before {
content: "\ea21";
}
.web-icon-microsoft:before {
content: "\ea22";
}
.web-icon-minus:before {
content: "\ea23";
}
.web-icon-nuxt:before {
content: "\ea24";
}
.web-icon-platform:before {
content: "\ea25";
}
.web-icon-play:before {
content: "\ea26";
}
.web-icon-plus:before {
content: "\ea27";
}
.web-icon-product-hunt:before {
content: "\ea28";
}
.web-icon-refine:before {
content: "\ea29";
}
.web-icon-rest:before {
content: "\ea2a";
}
.web-icon-search:before {
content: "\ea2b";
}
.web-icon-sendgrid:before {
content: "\ea2c";
}
.web-icon-star:before {
content: "\ea2d";
}
.web-icon-system:before {
content: "\ea2e";
}
.web-icon-textmagic:before {
content: "\ea2f";
}
.web-icon-twitter:before {
content: "\ea30";
}
.web-icon-vue:before {
content: "\ea31";
}
.web-icon-x:before {
content: "\ea32";
}
.web-icon-ycombinator:before {
content: "\ea33";
}
.web-icon-youtube:before {
content: "\ea34";
}

View File

@@ -1,40 +1,40 @@
// @ts-expect-error missing types
import SVGFixer from 'oslllo-svg-fixer';
import svgtofont from 'svgtofont';
import { resolve } from 'path';
import SVGFixer from "oslllo-svg-fixer";
import svgtofont from "svgtofont";
import { resolve } from "path";
const src = resolve(process.cwd(), 'src/icons/svg');
const optimized = resolve(process.cwd(), 'src/icons/optimized');
const dist = resolve(process.cwd(), 'src/icons/output');
const src = resolve(process.cwd(), "src/icons/svg");
const optimized = resolve(process.cwd(), "src/icons/optimized");
const dist = resolve(process.cwd(), "src/icons/output");
export const optimizeSVG = async () => {
const fixer = new SVGFixer(src, optimized, {
showProgressBar: true
});
const fixer = new SVGFixer(src, optimized, {
showProgressBar: true,
});
await fixer.fix();
await fixer.fix();
};
export const generateIcons = async () => {
await svgtofont({
classNamePrefix: 'web-icon',
src: optimized,
dist: dist,
fontName: 'web-icon',
styleTemplates: resolve(process.cwd(), 'src/icons/templates'),
css: {
fontSize: '20px'
},
outSVGReact: false,
svgicons2svgfont: {
centerHorizontally: true,
centerVertically: true,
fixedWidth: true,
fontHeight: 1000,
normalize: true,
descent: 200
},
emptyDist: true,
generateInfoData: true
});
await svgtofont({
classNamePrefix: "web-icon",
src: optimized,
dist: dist,
fontName: "web-icon",
styleTemplates: resolve(process.cwd(), "src/icons/templates"),
css: {
fontSize: "20px",
},
outSVGReact: false,
svgicons2svgfont: {
centerHorizontally: true,
centerVertically: true,
fixedWidth: true,
fontHeight: 1000,
normalize: true,
descent: 200,
},
emptyDist: true,
generateInfoData: true,
});
};

View File

@@ -1,20 +1,20 @@
<script lang="ts">
export let src: string;
export let alt = '';
export let controls = true;
export let autoplay = false;
let className = '';
export { className as class };
export let src: string;
export let alt = "";
export let controls = true;
export let autoplay = false;
let className = "";
export { className as class };
const videoExtensions = ['mp4', 'webm', 'ogg'] as const;
$: isVideo = videoExtensions.some((ext) => src.endsWith(ext));
const videoExtensions = ["mp4", "webm", "ogg"] as const;
$: isVideo = videoExtensions.some((ext) => src.endsWith(ext));
</script>
{#if isVideo}
<!-- svelte-ignore a11y-media-has-caption-->
<video {src} class={className} {controls} {autoplay}>
<slot />
</video>
<!-- svelte-ignore a11y-media-has-caption-->
<video {src} class={className} {controls} {autoplay}>
<slot />
</video>
{:else}
<img loading="lazy" {src} {alt} class={className} />
<img loading="lazy" {src} {alt} class={className} />
{/if}

View File

@@ -1,14 +1,14 @@
<script lang="ts">
import { melt } from '@melt-ui/svelte';
import { getTabsContext } from './index.svelte';
import { melt } from "@melt-ui/svelte";
import { getTabsContext } from "./index.svelte";
export let tab: string;
export let tab: string;
const {
elements: { content }
} = getTabsContext();
const {
elements: { content },
} = getTabsContext();
</script>
<div use:melt={$content(tab)}>
<slot />
<slot />
</div>

View File

@@ -1,45 +1,45 @@
<script lang="ts" context="module">
const CTX_KEY = 'tabs';
const CTX_KEY = "tabs";
type Context = Tabs & {
tabs: readonly string[];
};
type Context = Tabs & {
tabs: readonly string[];
};
const setTabsContext = (ctx: Context) => {
setContext(CTX_KEY, ctx);
};
const setTabsContext = (ctx: Context) => {
setContext(CTX_KEY, ctx);
};
export const getTabsContext = () => {
return getContext<Context>(CTX_KEY);
};
export const getTabsContext = () => {
return getContext<Context>(CTX_KEY);
};
</script>
<script lang="ts">
import { createTabs, melt, type Tabs } from '@melt-ui/svelte';
import { getContext, setContext } from 'svelte';
import List from './List.svelte';
import Content from './Content.svelte';
import { createTabs, melt, type Tabs } from "@melt-ui/svelte";
import { getContext, setContext } from "svelte";
import List from "./List.svelte";
import Content from "./Content.svelte";
export let tabs: Context['tabs'];
export let tab = tabs[0];
export let tabs: Context["tabs"];
export let tab = tabs[0];
const ctx = createTabs({
defaultValue: tab,
onValueChange({ next }) {
tab = next;
return next;
}
});
$: value.set(tab);
const ctx = createTabs({
defaultValue: tab,
onValueChange({ next }) {
tab = next;
return next;
},
});
$: value.set(tab);
setTabsContext({ ...ctx, tabs });
setTabsContext({ ...ctx, tabs });
const {
elements: { root },
states: { value }
} = ctx;
const {
elements: { root },
states: { value },
} = ctx;
</script>
<div use:melt={$root}>
<slot TabsList={List} TabContent={Content} />
<slot TabsList={List} TabContent={Content} />
</div>

View File

@@ -1,2 +1,2 @@
export { default as Tabs } from './Tabs/index.svelte';
export { default as Media } from './Media.svelte';
export { default as Tabs } from "./Tabs/index.svelte";
export { default as Media } from "./Media.svelte";

View File

@@ -1,15 +1,18 @@
export function clickOutside(node: HTMLElement, callback: (e: MouseEvent) => void) {
const handleClick = (event: MouseEvent) => {
if (node && !node.contains(event.target as Node)) {
callback(event);
}
};
export function clickOutside(
node: HTMLElement,
callback: (e: MouseEvent) => void,
) {
const handleClick = (event: MouseEvent) => {
if (node && !node.contains(event.target as Node)) {
callback(event);
}
};
document.addEventListener('click', handleClick, true);
document.addEventListener("click", handleClick, true);
return {
destroy() {
document.removeEventListener('click', handleClick, true);
}
};
return {
destroy() {
document.removeEventListener("click", handleClick, true);
},
};
}

View File

@@ -1,6 +1,6 @@
export const highlight = (node: HTMLElement, text: string[]) => {
text.forEach((word) => {
const regex = new RegExp(`(${word})`, 'gi');
node.innerHTML = node.innerHTML.replace(regex, '<mark>$1</mark>');
});
text.forEach((word) => {
const regex = new RegExp(`(${word})`, "gi");
node.innerHTML = node.innerHTML.replace(regex, "<mark>$1</mark>");
});
};

View File

@@ -1,3 +1,3 @@
export * from './portal';
export * from './rect';
export * from './visible';
export * from "./portal";
export * from "./rect";
export * from "./visible";

View File

@@ -1,55 +1,58 @@
import { tick } from 'svelte';
import type { Action } from 'svelte/action';
import { tick } from "svelte";
import type { Action } from "svelte/action";
export type PortalConfig =
| { target?: string | HTMLElement | undefined; prepend?: boolean }
| undefined;
| { target?: string | HTMLElement | undefined; prepend?: boolean }
| undefined;
const defaults: PortalConfig = {
target: 'body',
prepend: false
target: "body",
prepend: false,
};
export const portal: Action<HTMLElement, PortalConfig> = (el, config?: PortalConfig) => {
let targetEl;
export const portal: Action<HTMLElement, PortalConfig> = (
el,
config?: PortalConfig,
) => {
let targetEl;
async function update(newConfig: PortalConfig) {
const { target, prepend } = { ...defaults, ...newConfig };
async function update(newConfig: PortalConfig) {
const { target, prepend } = { ...defaults, ...newConfig };
if (typeof target === 'string') {
targetEl = document.querySelector(target);
if (targetEl === null) {
await tick();
targetEl = document.querySelector(target);
}
if (targetEl === null) {
throw new Error(`No element found matching css selector: "${target}"`);
}
} else if (target instanceof HTMLElement) {
targetEl = target;
} else {
throw new TypeError(
`Unknown portal target type: ${
target === null ? 'null' : typeof target
}. Allowed types: string (CSS selector) or HTMLElement.`
);
}
el.dataset.portal = '';
if (prepend) {
targetEl.prepend(el);
} else {
targetEl.appendChild(el);
}
el.hidden = false;
if (typeof target === "string") {
targetEl = document.querySelector(target);
if (targetEl === null) {
await tick();
targetEl = document.querySelector(target);
}
if (targetEl === null) {
throw new Error(`No element found matching css selector: "${target}"`);
}
} else if (target instanceof HTMLElement) {
targetEl = target;
} else {
throw new TypeError(
`Unknown portal target type: ${
target === null ? "null" : typeof target
}. Allowed types: string (CSS selector) or HTMLElement.`,
);
}
function destroy() {
el.remove();
el.dataset.portal = "";
if (prepend) {
targetEl.prepend(el);
} else {
targetEl.appendChild(el);
}
el.hidden = false;
}
update(config ?? {});
return {
update,
destroy
};
function destroy() {
el.remove();
}
update(config ?? {});
return {
update,
destroy,
};
};

View File

@@ -1,28 +1,28 @@
import type { Action } from 'svelte/action';
import type { Writable } from 'svelte/store';
import type { Action } from "svelte/action";
import type { Writable } from "svelte/store";
type Args = Writable<DOMRect | null>;
export const rect: Action<HTMLElement, Args> = (node, store) => {
let observer: ResizeObserver | null = null;
const update = (store: Args) => {
observer?.disconnect();
let observer: ResizeObserver | null = null;
const update = (store: Args) => {
observer?.disconnect();
store.set(node.getBoundingClientRect());
store.set(node.getBoundingClientRect());
// Observe resize
observer = new ResizeObserver(() => {
store.set(node.getBoundingClientRect());
});
observer.observe(node);
};
// Observe resize
observer = new ResizeObserver(() => {
store.set(node.getBoundingClientRect());
});
observer.observe(node);
};
update(store);
update(store);
return {
update,
destroy() {
// no-op
}
};
return {
update,
destroy() {
// no-op
},
};
};

View File

@@ -1,16 +1,16 @@
export const scrollToTop = (node: HTMLElement) => {
const handleClick = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
const handleClick = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
};
node.addEventListener('click', handleClick);
node.addEventListener("click", handleClick);
return {
destroy() {
node.removeEventListener('click', handleClick);
}
};
return {
destroy() {
node.removeEventListener("click", handleClick);
},
};
};

View File

@@ -1,58 +1,58 @@
import { isVisible } from '$lib/utils/isVisible';
import type { Action } from 'svelte/action';
import { isVisible } from "$lib/utils/isVisible";
import type { Action } from "svelte/action";
type Args =
| {
top?: number;
bottom?: number;
left?: number;
right?: number;
}
| undefined;
| {
top?: number;
bottom?: number;
left?: number;
right?: number;
}
| undefined;
export const visible: Action<
HTMLElement,
Args,
{ 'on:visible': (e: CustomEvent<boolean>) => void }
HTMLElement,
Args,
{ "on:visible": (e: CustomEvent<boolean>) => void }
> = (node, args) => {
let visible = false;
let visible = false;
const createVisibilityHandler = (newArgs: Args) => {
const argsWithDefaults = {
top: 0,
bottom: window.innerHeight,
left: 0,
right: window.innerWidth,
...newArgs
};
return () => {
visible = isVisible(node, argsWithDefaults);
node.dispatchEvent(new CustomEvent('visible', { detail: visible }));
};
const createVisibilityHandler = (newArgs: Args) => {
const argsWithDefaults = {
top: 0,
bottom: window.innerHeight,
left: 0,
right: window.innerWidth,
...newArgs,
};
let handleVisibility = createVisibilityHandler(args);
handleVisibility();
function destroy() {
window.removeEventListener('scroll', handleVisibility);
window.removeEventListener('resize', handleVisibility);
}
function update(args: Args) {
destroy();
handleVisibility = createVisibilityHandler(args);
window.addEventListener('scroll', handleVisibility);
window.addEventListener('resize', handleVisibility);
}
update(args);
return {
update,
destroy() {
window.removeEventListener('scroll', handleVisibility);
window.removeEventListener('resize', handleVisibility);
}
return () => {
visible = isVisible(node, argsWithDefaults);
node.dispatchEvent(new CustomEvent("visible", { detail: visible }));
};
};
let handleVisibility = createVisibilityHandler(args);
handleVisibility();
function destroy() {
window.removeEventListener("scroll", handleVisibility);
window.removeEventListener("resize", handleVisibility);
}
function update(args: Args) {
destroy();
handleVisibility = createVisibilityHandler(args);
window.addEventListener("scroll", handleVisibility);
window.addEventListener("resize", handleVisibility);
}
update(args);
return {
update,
destroy() {
window.removeEventListener("scroll", handleVisibility);
window.removeEventListener("resize", handleVisibility);
},
};
};

View File

@@ -1,34 +1,34 @@
<script lang="ts">
import { rect } from '$lib/actions';
import { writable } from 'svelte/store';
import { rect } from "$lib/actions";
import { writable } from "svelte/store";
const bodyRect = writable<DOMRect | null>(null);
const bodyRect = writable<DOMRect | null>(null);
</script>
<div class="relative">
<div
class="true-body"
style:width={`${$bodyRect?.width ?? 0}px`}
style:height={`${$bodyRect?.height ?? 0}px`}
/>
<div class="body" use:rect={bodyRect}>
<slot />
</div>
<div
class="true-body"
style:width={`${$bodyRect?.width ?? 0}px`}
style:height={`${$bodyRect?.height ?? 0}px`}
/>
<div class="body" use:rect={bodyRect}>
<slot />
</div>
</div>
<style lang="scss">
.relative {
position: relative;
overflow: hidden;
}
.relative {
position: relative;
overflow: hidden;
}
.body {
position: absolute;
left: 0;
top: 0;
}
.body {
position: absolute;
left: 0;
top: 0;
}
.true-body {
transition: 0.2s ease;
}
.true-body {
transition: 0.2s ease;
}
</style>

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import '$scss/hljs.css';
import "$scss/hljs.css";
import { getCodeHtml } from '$lib/utils/code';
import { getCodeHtml } from "$lib/utils/code";
export let content: string;
$: codeHtml = getCodeHtml({ content, language: 'js' });
export let content: string;
$: codeHtml = getCodeHtml({ content, language: "js" });
</script>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->

View File

@@ -1,96 +1,100 @@
<script>
import AutoBox from '../AutoBox.svelte';
import Code from './Code.svelte';
import AutoBox from "../AutoBox.svelte";
import Code from "./Code.svelte";
</script>
<div class="code-console">
<div class="header">
<div class="ellipse" />
<div class="ellipse-2" />
<div class="ellipse-3" />
</div>
<div class="block">
<AutoBox>
<slot {Code} />
</AutoBox>
</div>
<div id="code-bottom" />
<div class="header">
<div class="ellipse" />
<div class="ellipse-2" />
<div class="ellipse-3" />
</div>
<div class="block">
<AutoBox>
<slot {Code} />
</AutoBox>
</div>
<div id="code-bottom" />
</div>
<style lang="scss">
@use '$scss/abstract/mixins/border-gradient' as gradients;
@use "$scss/abstract/mixins/border-gradient" as gradients;
.code-console {
@include gradients.border-gradient;
--p-radius: 16px;
.code-console {
@include gradients.border-gradient;
--p-radius: 16px;
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
background-color: hsl(var(--web-color-card));
border-radius: var(--p-radius);
--m-border-radius: var(--p-radius);
--m-border-gradient-before: linear-gradient(
180deg,
rgba(255, 255, 255, 0.12) 0%,
rgba(255, 255, 255, 0) 125.11%
);
backdrop-filter: blur(8px);
background-color: hsl(var(--web-color-card));
border-radius: var(--p-radius);
--m-border-radius: var(--p-radius);
--m-border-gradient-before: linear-gradient(
180deg,
rgba(255, 255, 255, 0.12) 0%,
rgba(255, 255, 255, 0) 125.11%
);
backdrop-filter: blur(8px);
min-width: 330px;
width: fit-content;
padding: 0 0.25rem 0.25rem;
min-width: 330px;
width: fit-content;
padding: 0 0.25rem 0.25rem;
}
.block {
width: 100%;
flex-grow: 1;
text-align: left;
border-radius: 12px;
background: linear-gradient(
129deg,
rgba(0, 0, 0, 0.48) 22.38%,
rgba(0, 0, 0, 0) 136.5%
);
padding: 20px;
position: relative;
}
.header {
position: relative;
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.75rem;
.ellipse {
background-color: #ec6a5e;
width: 8px;
height: 8px;
top: 12px;
border-radius: 4px;
}
.block {
width: 100%;
flex-grow: 1;
text-align: left;
.ellipse-2 {
background-color: #f5bf4f;
border-radius: 12px;
background: linear-gradient(129deg, rgba(0, 0, 0, 0.48) 22.38%, rgba(0, 0, 0, 0) 136.5%);
padding: 20px;
position: relative;
width: 8px;
height: 8px;
top: 12px;
border-radius: 4px;
}
.header {
position: relative;
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.75rem;
.ellipse-3 {
background-color: #61c554;
.ellipse {
background-color: #ec6a5e;
width: 8px;
height: 8px;
top: 12px;
border-radius: 4px;
}
.ellipse-2 {
background-color: #f5bf4f;
width: 8px;
height: 8px;
top: 12px;
border-radius: 4px;
}
.ellipse-3 {
background-color: #61c554;
width: 8px;
height: 8px;
top: 12px;
border-radius: 4px;
}
width: 8px;
height: 8px;
top: 12px;
border-radius: 4px;
}
}
.code-console :global(code) {
white-space: pre;
tab-size: 4;
}
.code-console :global(code) {
white-space: pre;
tab-size: 4;
}
</style>

View File

@@ -1,9 +1,9 @@
<script lang="ts">
export let id: string | undefined = undefined;
export let id: string | undefined = undefined;
</script>
<div class="phone" {id}>
<div class="inner">
<slot />
</div>
<div class="inner">
<slot />
</div>
</div>

View File

@@ -1,55 +1,55 @@
<script lang="ts">
import AutoHeight from '../AutoBox.svelte';
import AutoHeight from "../AutoBox.svelte";
</script>
<div class="anim-box">
<div class="top"><slot name="top" /></div>
<div class="content">
<AutoHeight>
<slot />
</AutoHeight>
</div>
<div class="top"><slot name="top" /></div>
<div class="content">
<AutoHeight>
<slot />
</AutoHeight>
</div>
</div>
<style lang="scss">
@use '$scss/abstract/mixins/border-gradient' as gradients;
@use "$scss/abstract/mixins/border-gradient" as gradients;
.anim-box {
@include gradients.border-gradient;
--m-border-radius: 1rem;
--m-border-gradient-before: linear-gradient(
180deg,
rgba(255, 255, 255, 0.12) 0%,
rgba(255, 255, 255, 0) 125.11%
);
border-radius: var(--m-border-radius);
background: hsl(var(--web-color-card));
backdrop-filter: blur(8px);
.anim-box {
@include gradients.border-gradient;
--m-border-radius: 1rem;
--m-border-gradient-before: linear-gradient(
180deg,
rgba(255, 255, 255, 0.12) 0%,
rgba(255, 255, 255, 0) 125.11%
);
border-radius: var(--m-border-radius);
background: hsl(var(--web-color-card));
backdrop-filter: blur(8px);
padding: 0.5rem;
padding-block-start: 0;
padding: 0.5rem;
padding-block-start: 0;
text-align: left;
text-align: left;
.top {
color: var(--greyscale-50, #ededf0);
font-family: Aeonik Pro;
font-size: 1.25rem;
font-style: normal;
font-weight: 400;
line-height: 2rem; /* 160% */
letter-spacing: -0.0125rem;
.top {
color: var(--greyscale-50, #ededf0);
font-family: Aeonik Pro;
font-size: 1.25rem;
font-style: normal;
font-weight: 400;
line-height: 2rem; /* 160% */
letter-spacing: -0.0125rem;
padding: 1rem;
text-align: left;
}
.content {
border-radius: 0.75rem;
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(30px);
position: relative;
}
padding: 1rem;
text-align: left;
}
.content {
border-radius: 0.75rem;
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(30px);
position: relative;
}
}
</style>

View File

@@ -1,61 +1,61 @@
<script lang="ts">
import { createCheckbox, melt } from '@melt-ui/svelte';
import { createCheckbox, melt } from "@melt-ui/svelte";
export let checked = false;
export let checked = false;
const {
elements: { root },
states: { checked: localChecked },
helpers: { isChecked }
} = createCheckbox({
onCheckedChange({ next }) {
if (typeof next === 'boolean') {
checked = next;
}
return next;
}
});
const {
elements: { root },
states: { checked: localChecked },
helpers: { isChecked },
} = createCheckbox({
onCheckedChange({ next }) {
if (typeof next === "boolean") {
checked = next;
}
return next;
},
});
$: localChecked.set(checked);
$: localChecked.set(checked);
</script>
<div class="wrapper">
<button use:melt={$root} class="anim-checkbox">
{#if $isChecked}
<span class="web-icon-check" />
{/if}
</button>
<button use:melt={$root} class="anim-checkbox">
{#if $isChecked}
<span class="web-icon-check" />
{/if}
</button>
</div>
<style lang="scss">
.wrapper {
display: grid;
place-items: center;
.wrapper {
display: grid;
place-items: center;
}
.anim-checkbox {
width: 1rem;
height: 1rem;
flex-shrink: 0;
border-radius: 0.125rem;
border: 1.5px solid var(--greyscale-500, #818186);
position: relative;
&:global(.anim-checkbox[data-state="checked"]) {
background-color: #7c67fe;
border-color: #7c67fe;
}
}
.anim-checkbox {
width: 1rem;
height: 1rem;
flex-shrink: 0;
[class*="icon-"] {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border-radius: 0.125rem;
border: 1.5px solid var(--greyscale-500, #818186);
position: relative;
&:global(.anim-checkbox[data-state='checked']) {
background-color: #7c67fe;
border-color: #7c67fe;
}
}
[class*='icon-'] {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 1rem;
}
color: white;
font-size: 1rem;
}
</style>

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import Code from '$lib/animations/CodeWindow/Code.svelte';
import { authController } from '.';
import Code from "$lib/animations/CodeWindow/Code.svelte";
import { authController } from ".";
const { state } = authController;
const { state } = authController;
$: content = `
$: content = `
const result = account.create(
ID.unique(),
'${$state.email}',

View File

@@ -1,81 +1,81 @@
<script lang="ts">
import { Switch } from '$lib/components';
import { objectKeys } from '$lib/utils/object';
import { authController } from '.';
import { Switch } from "$lib/components";
import { objectKeys } from "$lib/utils/object";
import { authController } from ".";
const { state } = authController;
const { state } = authController;
const getIcon = (provider: string) => {
return `web-icon-${provider.toLowerCase()}`;
};
const getIcon = (provider: string) => {
return `web-icon-${provider.toLowerCase()}`;
};
</script>
<div class="auth-controls">
{#each objectKeys($state.controls) as provider, i}
{@const isLast = i === objectKeys($state.controls).length - 1}
<div>
<span class={getIcon(provider)} />
<span>{provider}</span>
<Switch bind:checked={$state.controls[provider]} />
</div>
{#if !isLast}
<div class="sep" />
{/if}
{/each}
{#each objectKeys($state.controls) as provider, i}
{@const isLast = i === objectKeys($state.controls).length - 1}
<div>
<span class={getIcon(provider)} />
<span>{provider}</span>
<Switch bind:checked={$state.controls[provider]} />
</div>
{#if !isLast}
<div class="sep" />
{/if}
{/each}
</div>
<style lang="scss">
.auth-controls {
display: flex;
flex-direction: column;
.auth-controls {
display: flex;
flex-direction: column;
padding: 0.75rem;
padding: 0.75rem;
width: 12.5rem;
width: 12.5rem;
> div {
display: flex;
align-items: center;
> div {
display: flex;
align-items: center;
> :nth-child(2) {
margin-left: 0.75rem;
color: hsl(var(--web-color-white));
> :nth-child(2) {
margin-left: 0.75rem;
color: hsl(var(--web-color-white));
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 500;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.00394rem;
}
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 500;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.00394rem;
}
> :global(:nth-child(3)) {
margin-left: auto;
}
}
.sep {
width: 100%;
height: 1px;
background-color: rgba(255, 255, 255, 0.12);
margin-block: 0.5rem;
}
[class*='icon-'] {
--size: 2rem;
font-size: var(--size);
width: var(--size);
height: var(--size);
color: hsl(var(--web-color-greayscale-50));
position: relative;
&::before {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
> :global(:nth-child(3)) {
margin-left: auto;
}
}
.sep {
width: 100%;
height: 1px;
background-color: rgba(255, 255, 255, 0.12);
margin-block: 0.5rem;
}
[class*="icon-"] {
--size: 2rem;
font-size: var(--size);
width: var(--size);
height: var(--size);
color: hsl(var(--web-color-greayscale-50));
position: relative;
&::before {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
}
</style>

View File

@@ -1,95 +1,110 @@
import Box from './box.svelte';
import Code from './code.svelte';
import Controls from './controls.svelte';
import Phone from './phone.svelte';
import Box from "./box.svelte";
import Code from "./code.svelte";
import Controls from "./controls.svelte";
import Phone from "./phone.svelte";
export const Auth = {
Phone,
Box,
Code,
Controls
Phone,
Box,
Code,
Controls,
};
import { safeAnimate, sleep, write } from '$lib/animations';
import { createResettable } from '$lib/utils/resettable';
import { getElSelector } from '../Products.svelte';
import { safeAnimate, sleep, write } from "$lib/animations";
import { createResettable } from "$lib/utils/resettable";
import { getElSelector } from "../Products.svelte";
type State = {
email: string;
password: string;
name: string;
email: string;
password: string;
name: string;
showControls: boolean;
submitted: boolean;
controls: {
GitHub: boolean;
Google: boolean;
Apple: boolean;
Microsoft: boolean;
};
showControls: boolean;
submitted: boolean;
controls: {
GitHub: boolean;
Google: boolean;
Apple: boolean;
Microsoft: boolean;
};
};
const state = createResettable<State>({
email: '',
password: '',
name: "Walter O'Brian",
showControls: false,
submitted: false,
controls: {
GitHub: true,
Google: false,
Apple: false,
Microsoft: false
}
email: "",
password: "",
name: "Walter O'Brian",
showControls: false,
submitted: false,
controls: {
GitHub: true,
Google: false,
Apple: false,
Microsoft: false,
},
});
const emailToSet = 'walterobrian@example.com';
const passwordToSet = 'password';
const emailToSet = "walterobrian@example.com";
const passwordToSet = "password";
const execute = async () => {
const phone = getElSelector('phone');
const box = getElSelector('box');
const code = getElSelector('code');
const controls = getElSelector('controls');
const phone = getElSelector("phone");
const box = getElSelector("box");
const code = getElSelector("code");
const controls = getElSelector("controls");
// Reset
const { update } = state.reset();
// Reset
const { update } = state.reset();
await Promise.all([
safeAnimate(box, { x: 310, y: 140, opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(code, { x: 200, y: 460, opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(phone, { x: 0, y: 0 }, { duration: 0.5 })?.finished,
safeAnimate(controls, { x: 420, y: 0, opacity: 0 }, { duration: 0.5 })?.finished
]);
await Promise.all([
safeAnimate(box, { x: 310, y: 140, opacity: 0 }, { duration: 0.5 })
?.finished,
safeAnimate(code, { x: 200, y: 460, opacity: 0 }, { duration: 0.5 })
?.finished,
safeAnimate(phone, { x: 0, y: 0 }, { duration: 0.5 })?.finished,
safeAnimate(controls, { x: 420, y: 0, opacity: 0 }, { duration: 0.5 })
?.finished,
]);
// Start
await safeAnimate(box, { y: [48, 140], opacity: 1 }, { duration: 0.25, delay: 0.25 })?.finished;
// Start
await safeAnimate(
box,
{ y: [48, 140], opacity: 1 },
{ duration: 0.25, delay: 0.25 },
)?.finished;
await sleep(50);
await sleep(50);
await write(emailToSet, (v) => update((p) => ({ ...p, email: v })), 300);
await sleep(50);
await write(emailToSet, (v) => update((p) => ({ ...p, email: v })), 300);
await sleep(50);
await write(passwordToSet, (v) => update((p) => ({ ...p, password: v })), 300);
await sleep(50);
await write(
passwordToSet,
(v) => update((p) => ({ ...p, password: v })),
300,
);
await sleep(50);
await safeAnimate(
code,
{ x: [200, 200], y: [460 + 16, 460], opacity: [0, 1] },
{ duration: 0.25 }
)?.finished;
await safeAnimate(
code,
{ x: [200, 200], y: [460 + 16, 460], opacity: [0, 1] },
{ duration: 0.25 },
)?.finished;
await sleep(350);
await sleep(350);
update((p) => ({ ...p, submitted: true }));
update((p) => ({ ...p, submitted: true }));
await sleep(1000);
await sleep(1000);
update((p) => ({ ...p, showControls: true }));
safeAnimate(controls, { x: [420, 420], y: [16, 0], opacity: 1 }, { duration: 0.5 });
update((p) => ({ ...p, showControls: true }));
safeAnimate(
controls,
{ x: [420, 420], y: [16, 0], opacity: 1 },
{ duration: 0.5 },
);
};
export const authController = {
execute,
state
execute,
state,
};

View File

@@ -1,220 +1,229 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { authController } from '.';
import { objectKeys } from '$lib/utils/object';
import { flip } from '$lib/utils/flip';
import { fade } from "svelte/transition";
import { authController } from ".";
import { objectKeys } from "$lib/utils/object";
import { flip } from "$lib/utils/flip";
const { state } = authController;
const { state } = authController;
$: controlsEnabled = $state.showControls && Object.values($state.controls).some(Boolean);
$: controlsEnabled =
$state.showControls && Object.values($state.controls).some(Boolean);
</script>
<div data-theme-ignore class="inner-phone light">
<p class="title">Create an Account</p>
<p class="subtitle">Please enter your details</p>
<div class="inputs">
<fieldset>
<label for="name">Your Name</label>
<input type="name" id="name" placeholder="Enter your name" bind:value={$state.name} />
</fieldset>
<fieldset>
<label for="email">Your Email</label>
<input
type="email"
id="email"
placeholder="Enter your email"
bind:value={$state.email}
/>
</fieldset>
<fieldset>
<label for="password">Create Password</label>
<input
type="password"
id="password"
placeholder="Enter Password"
bind:value={$state.password}
/>
</fieldset>
<p class="title">Create an Account</p>
<p class="subtitle">Please enter your details</p>
<div class="inputs">
<fieldset>
<label for="name">Your Name</label>
<input
type="name"
id="name"
placeholder="Enter your name"
bind:value={$state.name}
/>
</fieldset>
<fieldset>
<label for="email">Your Email</label>
<input
type="email"
id="email"
placeholder="Enter your email"
bind:value={$state.email}
/>
</fieldset>
<fieldset>
<label for="password">Create Password</label>
<input
type="password"
id="password"
placeholder="Enter Password"
bind:value={$state.password}
/>
</fieldset>
</div>
<button class="sign-up">Sign Up</button>
{#if controlsEnabled}
<span class="with-sep" transition:fade={{ duration: 100 }}
>or sign up with</span
>
<div class="oauth-btns" transition:fade={{ duration: 100 }}>
{#each objectKeys($state.controls).filter((p) => $state.controls[p]) as provider (provider)}
<button
class="oauth"
transition:fade={{ duration: 100 }}
animate:flip={{ duration: 250 }}
>
<div class="inner">
<span class="web-icon-{provider.toLowerCase()}" />
<span>{provider}</span>
</div>
</button>
{/each}
</div>
<button class="sign-up">Sign Up</button>
{#if controlsEnabled}
<span class="with-sep" transition:fade={{ duration: 100 }}>or sign up with</span>
<div class="oauth-btns" transition:fade={{ duration: 100 }}>
{#each objectKeys($state.controls).filter((p) => $state.controls[p]) as provider (provider)}
<button
class="oauth"
transition:fade={{ duration: 100 }}
animate:flip={{ duration: 250 }}
>
<div class="inner">
<span class="web-icon-{provider.toLowerCase()}" />
<span>{provider}</span>
</div>
</button>
{/each}
</div>
{/if}
{/if}
</div>
<style lang="scss">
.inner-phone {
padding-block: 3rem;
padding-inline: 1rem;
.inner-phone {
padding-block: 3rem;
padding-inline: 1rem;
color: rgba(67, 67, 71, 1);
text-align: left;
color: rgba(67, 67, 71, 1);
text-align: left;
.title {
color: #434347;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px; /* 137.5% */
letter-spacing: -0.224px;
}
.subtitle {
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.196px;
}
.inputs {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-block-start: 1.5rem;
fieldset {
display: flex;
flex-direction: column;
gap: 0.3125rem;
width: 100%;
label {
color: var(--color-greyscale-700, #56565c);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: -0.168px;
}
input {
all: unset;
display: flex;
padding: 8px 12px;
align-items: flex-start;
align-self: stretch;
border-radius: 8px;
border: 1px solid #d8d8db;
color: #434347;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: -0.168px;
}
}
}
.sign-up {
padding: 0.375rem 0.75rem;
text-align: center;
width: 100%;
margin-block-start: 1.25rem;
border-radius: 0.5rem;
background: var(--appwrite-purple, #7c67fe);
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.06);
color: var(--color-bw-white, #fff);
text-align: center;
/* Responsive/SubBody-500 */
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 22px; /* 157.143% */
letter-spacing: -0.07px;
}
.with-sep {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 166.667% */
letter-spacing: -0.0105rem;
color: hsl(var(--web-color-greyscale-500));
margin-block-start: 0.75rem;
&::before,
&::after {
content: '';
height: 1px;
flex-grow: 1;
background-color: hsl(var(--web-color-greyscale-200));
}
}
.oauth-btns {
--gap: 0.5rem;
display: flex;
flex-wrap: wrap;
gap: var(--gap);
margin-block-start: 0.75rem;
}
.oauth {
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.5rem;
border: 1px solid #d9d9d9;
color: hsl(var(--web-color-greyscale-750));
text-align: center;
/* Responsive/Caption-500 */
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 500;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.01575rem;
flex: 1 1 calc(50% - var(--gap));
padding-block: 0.375rem;
position: relative;
height: 2.125rem;
.inner {
position: absolute;
left: 50%;
top: 50%;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
transform: translate(-50%, -50%) scale(var(--inverse-sx, 1), var(--inverse-sy, 1));
}
}
.title {
color: #434347;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px; /* 137.5% */
letter-spacing: -0.224px;
}
.subtitle {
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.196px;
}
.inputs {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-block-start: 1.5rem;
fieldset {
display: flex;
flex-direction: column;
gap: 0.3125rem;
width: 100%;
label {
color: var(--color-greyscale-700, #56565c);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: -0.168px;
}
input {
all: unset;
display: flex;
padding: 8px 12px;
align-items: flex-start;
align-self: stretch;
border-radius: 8px;
border: 1px solid #d8d8db;
color: #434347;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
letter-spacing: -0.168px;
}
}
}
.sign-up {
padding: 0.375rem 0.75rem;
text-align: center;
width: 100%;
margin-block-start: 1.25rem;
border-radius: 0.5rem;
background: var(--appwrite-purple, #7c67fe);
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.06);
color: var(--color-bw-white, #fff);
text-align: center;
/* Responsive/SubBody-500 */
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 22px; /* 157.143% */
letter-spacing: -0.07px;
}
.with-sep {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 166.667% */
letter-spacing: -0.0105rem;
color: hsl(var(--web-color-greyscale-500));
margin-block-start: 0.75rem;
&::before,
&::after {
content: "";
height: 1px;
flex-grow: 1;
background-color: hsl(var(--web-color-greyscale-200));
}
}
.oauth-btns {
--gap: 0.5rem;
display: flex;
flex-wrap: wrap;
gap: var(--gap);
margin-block-start: 0.75rem;
}
.oauth {
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.5rem;
border: 1px solid #d9d9d9;
color: hsl(var(--web-color-greyscale-750));
text-align: center;
/* Responsive/Caption-500 */
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 500;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.01575rem;
flex: 1 1 calc(50% - var(--gap));
padding-block: 0.375rem;
position: relative;
height: 2.125rem;
.inner {
position: absolute;
left: 50%;
top: 50%;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
transform: translate(-50%, -50%)
scale(var(--inverse-sx, 1), var(--inverse-sy, 1));
}
}
}
</style>

View File

@@ -1,54 +1,58 @@
<script lang="ts">
import { slide } from 'svelte/transition';
import { databasesController } from '.';
import { flip } from '$lib/utils/flip';
import { slide } from "svelte/transition";
import { databasesController } from ".";
import { flip } from "$lib/utils/flip";
const { state } = databasesController;
const { state } = databasesController;
</script>
<div class="pseudo-table">
<div class="header">
<span class="web-eyebrow">Document ID</span>
<span class="web-eyebrow">Task</span>
<div class="header">
<span class="web-eyebrow">Document ID</span>
<span class="web-eyebrow">Task</span>
</div>
{#each $state.tasks.slice(0, $state.tableSlice) as task (task.id)}
<div
class="row"
transition:slide={{ duration: 150 }}
animate:flip={{ duration: 150 }}
>
<div class="copy-button">
<span class="web-icon-copy" />
<span>{task.id}</span>
</div>
<span class="truncated">{task.title}</span>
</div>
{#each $state.tasks.slice(0, $state.tableSlice) as task (task.id)}
<div class="row" transition:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
<div class="copy-button">
<span class="web-icon-copy" />
<span>{task.id}</span>
</div>
<span class="truncated">{task.title}</span>
</div>
{/each}
{/each}
</div>
<style lang="scss">
.copy-button {
display: flex;
padding: 0.25rem 0.5rem;
align-items: center;
gap: 0.375rem;
.copy-button {
display: flex;
padding: 0.25rem 0.5rem;
align-items: center;
gap: 0.375rem;
border-radius: 62.4375rem;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(2.6666667461395264px);
border-radius: 62.4375rem;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(2.6666667461395264px);
[class*='icon-'] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-600));
}
span:not([class*='icon-']) {
color: var(--greyscale-400, #adadb1);
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 142.857% */
overflow: hidden;
text-overflow: ellipsis;
}
[class*="icon-"] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-600));
}
span:not([class*="icon-"]) {
color: var(--greyscale-400, #adadb1);
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 142.857% */
overflow: hidden;
text-overflow: ellipsis;
}
}
</style>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import Code from '$lib/animations/CodeWindow/Code.svelte';
import Code from "$lib/animations/CodeWindow/Code.svelte";
let content = `
let content = `
const result = databases.createDocument(
'Your-tasks',
tasks,

View File

@@ -1,94 +1,95 @@
import Box from './box.svelte';
import Code from './code.svelte';
import Phone from './phone.svelte';
import Box from "./box.svelte";
import Code from "./code.svelte";
import Phone from "./phone.svelte";
import { safeAnimate, sleep } from '$lib/animations';
import { createResettable } from '$lib/utils/resettable';
import { getElSelector } from '../Products.svelte';
import { safeAnimate, sleep } from "$lib/animations";
import { createResettable } from "$lib/utils/resettable";
import { getElSelector } from "../Products.svelte";
type Task = {
id: string;
title: string;
checked: boolean;
id: string;
title: string;
checked: boolean;
};
type State = {
tasks: Task[];
tableSlice: number;
tasks: Task[];
tableSlice: number;
};
const state = createResettable<State>({
tasks: [
{
id: '3397fecdedb13397fecdedb1',
title: 'Research user needs',
checked: true
}
],
tableSlice: 1
tasks: [
{
id: "3397fecdedb13397fecdedb1",
title: "Research user needs",
checked: true,
},
],
tableSlice: 1,
});
const execute = async () => {
const phone = getElSelector('phone');
const box = getElSelector('box');
const code = getElSelector('code');
const { update } = state.reset();
const phone = getElSelector("phone");
const box = getElSelector("box");
const code = getElSelector("code");
const { update } = state.reset();
await Promise.all([
safeAnimate(phone, { x: 390, y: 0 }, { duration: 0.5 })?.finished,
safeAnimate(box, { x: 0, y: 32, opacity: 1 }, { duration: 0.5 })?.finished,
safeAnimate(code, { x: 80, y: 320, opacity: 1 }, { duration: 0.5 })?.finished
]);
await Promise.all([
safeAnimate(phone, { x: 390, y: 0 }, { duration: 0.5 })?.finished,
safeAnimate(box, { x: 0, y: 32, opacity: 1 }, { duration: 0.5 })?.finished,
safeAnimate(code, { x: 80, y: 320, opacity: 1 }, { duration: 0.5 })
?.finished,
]);
await sleep(250);
await sleep(250);
update((p) => ({
...p,
tasks: [
...p.tasks,
{
id: '3397fecdedb13397fecdedb2',
title: 'Create wireframes',
checked: false
}
]
}));
await sleep(250);
update((p) => ({
...p,
tasks: [
...p.tasks,
{
id: "3397fecdedb13397fecdedb2",
title: "Create wireframes",
checked: false,
},
],
}));
await sleep(250);
update((p) => ({
...p,
tableSlice: p.tableSlice + 1
}));
update((p) => ({
...p,
tableSlice: p.tableSlice + 1,
}));
await sleep(250);
await sleep(250);
update((p) => ({
...p,
tasks: [
...p.tasks,
{
id: '3397fecdedb13397fecdedb3',
title: 'Create visual design',
checked: false
}
]
}));
update((p) => ({
...p,
tasks: [
...p.tasks,
{
id: "3397fecdedb13397fecdedb3",
title: "Create visual design",
checked: false,
},
],
}));
await sleep(250);
await sleep(250);
update((p) => ({
...p,
tableSlice: p.tableSlice + 1
}));
update((p) => ({
...p,
tableSlice: p.tableSlice + 1,
}));
};
export const databasesController = {
execute,
state
execute,
state,
};
export const Databases = {
Phone,
Box,
Code
Phone,
Box,
Code,
};

View File

@@ -1,127 +1,131 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { databasesController } from '.';
import TaskCheckbox from '../TaskCheckbox.svelte';
import { fly } from "svelte/transition";
import { databasesController } from ".";
import TaskCheckbox from "../TaskCheckbox.svelte";
const { state } = databasesController;
const { state } = databasesController;
</script>
<div data-theme-ignore class="inner-phone light">
<div class="header">
<p class="title">Your tasks</p>
<span class="icon-menu" aria-label="menu" />
</div>
<div class="header">
<p class="title">Your tasks</p>
<span class="icon-menu" aria-label="menu" />
</div>
<div class="date">Today</div>
<div class="tasks">
{#each $state.tasks as task (task.id)}
<div class="task" data-checked={task.checked ? '' : undefined} in:fly={{ x: -16 }}>
<TaskCheckbox bind:checked={task.checked} />
<span class="title">{task.title}</span>
</div>
{/each}
</div>
<div class="date">Today</div>
<div class="tasks">
{#each $state.tasks as task (task.id)}
<div
class="task"
data-checked={task.checked ? "" : undefined}
in:fly={{ x: -16 }}
>
<TaskCheckbox bind:checked={task.checked} />
<span class="title">{task.title}</span>
</div>
{/each}
</div>
<button class="add-btn">
<span class="web-icon-plus" />
</button>
<button class="add-btn">
<span class="web-icon-plus" />
</button>
</div>
<style lang="scss">
.inner-phone {
padding-block: 3rem;
padding-inline: 1rem;
.inner-phone {
padding-block: 3rem;
padding-inline: 1rem;
color: rgba(67, 67, 71, 1);
text-align: left;
color: rgba(67, 67, 71, 1);
text-align: left;
position: relative;
height: 100%;
position: relative;
height: 100%;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 1rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 1rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
[class*='icon-'] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-500));
}
}
.date {
margin-block-start: 3rem;
color: hsl(var(--web-color-greyscale-600));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 500;
line-height: 1.25rem; /* 166.667% */
}
.tasks {
margin-block-start: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
.task {
display: flex;
align-items: center;
gap: 0.75rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--web-color-greyscale-50));
background: hsl(var(--web-color-white));
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
padding-block: 0.55rem;
padding-inline: 0.88rem;
/* Responsive/SubBody-400 */
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.00394rem;
transition: opacity 200ms ease;
&[data-checked] {
opacity: 0.6;
}
}
}
.add-btn {
position: absolute;
right: 1rem;
bottom: 2.5rem;
width: 2.5rem;
height: 2.5rem;
flex-shrink: 0;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
background-color: rgba(124, 103, 254, 1);
color: rgba(237, 237, 240, 1);
font-size: 1.5rem;
display: grid;
place-items: center;
border-radius: 100%;
}
[class*="icon-"] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-500));
}
}
.date {
margin-block-start: 3rem;
color: hsl(var(--web-color-greyscale-600));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 500;
line-height: 1.25rem; /* 166.667% */
}
.tasks {
margin-block-start: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
.task {
display: flex;
align-items: center;
gap: 0.75rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--web-color-greyscale-50));
background: hsl(var(--web-color-white));
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
padding-block: 0.55rem;
padding-inline: 0.88rem;
/* Responsive/SubBody-400 */
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.00394rem;
transition: opacity 200ms ease;
&[data-checked] {
opacity: 0.6;
}
}
}
.add-btn {
position: absolute;
right: 1rem;
bottom: 2.5rem;
width: 2.5rem;
height: 2.5rem;
flex-shrink: 0;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
background-color: rgba(124, 103, 254, 1);
color: rgba(237, 237, 240, 1);
font-size: 1.5rem;
display: grid;
place-items: center;
border-radius: 100%;
}
}
</style>

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { portal } from '$lib/actions';
import Code from '$lib/animations/CodeWindow/Code.svelte';
import { fade } from 'svelte/transition';
import { functionsController } from '.';
import { portal } from "$lib/actions";
import Code from "$lib/animations/CodeWindow/Code.svelte";
import { fade } from "svelte/transition";
import { functionsController } from ".";
let content = `
let content = `
const userId = req.headers['user-id'];
if (req.path === '/subscribe') {
@@ -18,39 +18,39 @@ if (req.path === '/webhook') {
return res.json({ success: true });`.trim();
const { state } = functionsController;
const { state } = functionsController;
</script>
<Code {content} />
<div use:portal={{ target: '#code-bottom' }} class="bottom">
{#if $state.submit !== 'idle'}
<span class="web-icon-github" in:fade />
{/if}
{#if $state.submit === 'loading'}
<span in:fade>Pushing to GitHub...</span>
<div class="loader is-small" in:fade />
{:else if $state.submit === 'success'}
<span>Deployed to Appwrite Cloud</span>
<span class="web-icon-check" />
{/if}
<div use:portal={{ target: "#code-bottom" }} class="bottom">
{#if $state.submit !== "idle"}
<span class="web-icon-github" in:fade />
{/if}
{#if $state.submit === "loading"}
<span in:fade>Pushing to GitHub...</span>
<div class="loader is-small" in:fade />
{:else if $state.submit === "success"}
<span>Deployed to Appwrite Cloud</span>
<span class="web-icon-check" />
{/if}
</div>
<style lang="scss">
.bottom {
display: flex;
align-items: center;
gap: 0.5rem;
.bottom {
display: flex;
align-items: center;
gap: 0.5rem;
height: 3rem;
padding-inline: 1rem;
height: 3rem;
padding-inline: 1rem;
color: var(--color-bw-white, #fff);
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 150%; /* 1.3125rem */
letter-spacing: -0.00875rem;
}
color: var(--color-bw-white, #fff);
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 150%; /* 1.3125rem */
letter-spacing: -0.00875rem;
}
</style>

View File

@@ -1,58 +1,61 @@
import Code from './code.svelte';
import Phone from './phone.svelte';
import Code from "./code.svelte";
import Phone from "./phone.svelte";
import { safeAnimate, sleep } from '$lib/animations';
import { createResettable } from '$lib/utils/resettable';
import { getElSelector } from '../Products.svelte';
import { safeAnimate, sleep } from "$lib/animations";
import { createResettable } from "$lib/utils/resettable";
import { getElSelector } from "../Products.svelte";
type State = {
submit: 'idle' | 'loading' | 'success';
submit: "idle" | "loading" | "success";
};
const state = createResettable<State>({
submit: 'idle'
submit: "idle",
});
const execute = async () => {
const phone = getElSelector('phone');
const box = getElSelector('box');
const code = getElSelector('code');
const phone = getElSelector("phone");
const box = getElSelector("box");
const code = getElSelector("code");
const { update } = state.reset();
const { update } = state.reset();
await Promise.all([
safeAnimate(phone, { x: 430, y: 0, width: '275px' }, { duration: 0.5 })?.finished,
safeAnimate(code, { x: 0, y: 200, opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished
]);
await Promise.all([
safeAnimate(phone, { x: 430, y: 0, width: "275px" }, { duration: 0.5 })
?.finished,
safeAnimate(code, { x: 0, y: 200, opacity: 0 }, { duration: 0.5 })
?.finished,
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
]);
await sleep(250);
await sleep(250);
await safeAnimate(code, { zIndex: 0 }, { duration: 0 })?.finished;
await safeAnimate(code, { y: [200 - 16, 200], opacity: 1 }, { duration: 0.5 })?.finished;
await safeAnimate(code, { zIndex: 0 }, { duration: 0 })?.finished;
await safeAnimate(code, { y: [200 - 16, 200], opacity: 1 }, { duration: 0.5 })
?.finished;
await sleep(250);
await sleep(250);
update((p) => ({
...p,
submit: 'loading'
}));
update((p) => ({
...p,
submit: "loading",
}));
await sleep(1500);
await sleep(1500);
update((p) => ({
...p,
submit: 'success'
}));
update((p) => ({
...p,
submit: "success",
}));
};
export const functionsController = {
execute,
state
execute,
state,
};
export const Functions = {
Phone,
Phone,
Code
Code,
};

View File

@@ -1,325 +1,325 @@
<script lang="ts">
import { flip } from '$lib/utils/flip';
import { crossfade, scale, slide } from 'svelte/transition';
import { functionsController } from '.';
import { flip } from "$lib/utils/flip";
import { crossfade, scale, slide } from "svelte/transition";
import { functionsController } from ".";
const { state } = functionsController;
const { state } = functionsController;
type Method = {
icon: string;
label: string;
};
type Method = {
icon: string;
label: string;
};
$: methods = [
$state.submit === 'success' && {
icon: '/images/animations/stripe.png',
label: 'Stripe'
},
{
icon: '/images/animations/credit-card.svg',
label: 'Card'
},
{
icon: '/images/animations/paypal.svg',
label: 'PayPal'
},
{
icon: '/images/animations/apple.svg',
label: 'Apple'
}
].filter(Boolean) as Method[];
$: methods = [
$state.submit === "success" && {
icon: "/images/animations/stripe.png",
label: "Stripe",
},
{
icon: "/images/animations/credit-card.svg",
label: "Card",
},
{
icon: "/images/animations/paypal.svg",
label: "PayPal",
},
{
icon: "/images/animations/apple.svg",
label: "Apple",
},
].filter(Boolean) as Method[];
</script>
<div data-theme-ignore class="inner-phone light">
<div class="header">
<p class="title">Upgrade plan</p>
<span class="icon-menu" aria-label="menu" />
</div>
<div class="header">
<p class="title">Upgrade plan</p>
<span class="icon-menu" aria-label="menu" />
</div>
<div class="plan">
<p class="title">Premium plan</p>
<div class="subscription">
<p class="price">$20</p>
<p class="period">/month</p>
</div>
<ul>
<li>Premium plan</li>
<li>Premium plan</li>
<li>Premium plan</li>
</ul>
<div class="plan">
<p class="title">Premium plan</p>
<div class="subscription">
<p class="price">$20</p>
<p class="period">/month</p>
</div>
<ul class="methods">
{#each methods as method, i (method.label)}
<li
in:scale={{ delay: 150 }}
animate:flip={{ duration: 500 }}
data-active={i == 0 ? '' : undefined}
>
<img src={method.icon} alt="" />
<p>{method.label}</p>
</li>
{/each}
<ul>
<li>Premium plan</li>
<li>Premium plan</li>
<li>Premium plan</li>
</ul>
</div>
{#if $state.submit !== 'success'}
<div class="form">
<p>Card information</p>
<div class="bordered">
<div>
<p>placeholder</p>
<img src="/images/animations/visa.png" alt="" />
<img src="/images/animations/mastercard.png" alt="" />
</div>
<div>
<p>MM/YY</p>
<p>CVV</p>
</div>
</div>
<ul class="methods">
{#each methods as method, i (method.label)}
<li
in:scale={{ delay: 150 }}
animate:flip={{ duration: 500 }}
data-active={i == 0 ? "" : undefined}
>
<img src={method.icon} alt="" />
<p>{method.label}</p>
</li>
{/each}
</ul>
{#if $state.submit !== "success"}
<div class="form">
<p>Card information</p>
<div class="bordered">
<div>
<p>placeholder</p>
<img src="/images/animations/visa.png" alt="" />
<img src="/images/animations/mastercard.png" alt="" />
</div>
{/if}
<div>
<p>MM/YY</p>
<p>CVV</p>
</div>
</div>
</div>
{/if}
<button>
Pay $20.00
{#if $state.submit === 'success'}
<span in:slide={{ axis: 'x' }}>on Stripe</span>
{/if}
</button>
<button>
Pay $20.00
{#if $state.submit === "success"}
<span in:slide={{ axis: "x" }}>on Stripe</span>
{/if}
</button>
</div>
<style lang="scss">
.inner-phone {
padding-block: 3rem 1.5rem;
padding-inline: 1rem;
.inner-phone {
padding-block: 3rem 1.5rem;
padding-inline: 1rem;
color: rgba(67, 67, 71, 1);
text-align: left;
color: rgba(67, 67, 71, 1);
text-align: left;
position: relative;
height: 100%;
overflow: visible;
position: relative;
height: 100%;
overflow: visible;
display: flex;
flex-direction: column;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.title {
color: #434347;
font-family: Inter;
font-size: 1rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
[class*="icon-"] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-500));
}
}
.plan {
display: flex;
padding: 0.75rem 1rem;
flex-direction: column;
justify-content: center;
align-items: flex-start;
border-radius: 0.75rem;
background: rgba(237, 237, 240, 0.5);
margin-block-start: 1rem;
.title {
color: var(--color-greyscale-700, #56565c);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 700;
line-height: 1.25rem; /* 166.667% */
}
.subscription {
display: flex;
align-items: baseline;
margin-block-start: 0.15rem;
.price {
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
font-family: Inter;
font-size: 1.125rem;
font-style: normal;
font-weight: 700;
line-height: 1.25rem; /* 111.111% */
}
.period {
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem;
}
}
ul {
display: flex;
flex-direction: column;
gap: 0.125rem;
margin-block-start: 0.75rem;
.header {
display: flex;
justify-content: space-between;
align-items: center;
li {
color: var(--color-greyscale-500, #818186);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 166.667% */
.title {
color: #434347;
font-family: Inter;
font-size: 1rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
padding-inline-start: 1.5rem;
[class*='icon-'] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-500));
}
}
.plan {
display: flex;
padding: 0.75rem 1rem;
flex-direction: column;
justify-content: center;
align-items: flex-start;
border-radius: 0.75rem;
background: rgba(237, 237, 240, 0.5);
margin-block-start: 1rem;
.title {
color: var(--color-greyscale-700, #56565c);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 700;
line-height: 1.25rem; /* 166.667% */
}
.subscription {
display: flex;
align-items: baseline;
margin-block-start: 0.15rem;
.price {
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
font-family: Inter;
font-size: 1.125rem;
font-style: normal;
font-weight: 700;
line-height: 1.25rem; /* 111.111% */
}
.period {
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem;
}
}
ul {
display: flex;
flex-direction: column;
gap: 0.125rem;
margin-block-start: 0.75rem;
li {
color: var(--color-greyscale-500, #818186);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 166.667% */
padding-inline-start: 1.5rem;
position: relative;
&::before {
content: '';
display: block;
position: absolute;
left: 0;
width: 1rem;
height: 1rem;
top: 50%;
transform: translateY(-50%);
background-image: url('/images/animations/check-circle.svg');
}
}
}
}
.methods {
margin-block-start: 1.25rem;
display: flex;
gap: 0.75rem;
overflow: hidden;
margin-inline: -1rem;
padding-inline: 1rem;
li {
flex-shrink: 0;
display: flex;
width: 5.5rem;
padding: 0.75rem 0.75rem 0.625rem 0.75rem;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 0.125rem;
border-radius: 0.75rem;
border: 1px solid var(--greyscale-50, #ededf0);
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 166.667% */
&[data-active] {
border-color: var(--appwrite-purple, #7c67fe);
}
}
}
.form {
margin-block-start: 1.25rem;
> p {
color: var(--dark-neutrals-150, #373b4d);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 150%; /* 1.125rem */
}
.bordered {
margin-block-start: 0.25rem;
border-radius: 0.5rem;
border: 1px solid #ededf0;
background: var(--color-bw-white, #fff);
> div:first-child {
display: flex;
gap: 0.25rem;
padding: 0.65rem 0.75rem;
border-bottom: 1px solid #ededf0;
> :nth-child(2) {
margin-inline-start: auto;
}
}
> div:nth-child(2) {
display: flex;
> p {
padding: 0.65rem 0.75rem;
}
> p:first-child {
flex-grow: 3;
}
> p:last-child {
flex-grow: 1;
border-inline-start: 1px solid #ededf0;
}
}
p {
color: var(--light-neutrals-50, #c4c6d7);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 150%; /* 1.125rem */
}
}
}
> button {
display: flex;
text-align: center;
padding: 0.5rem 1rem;
justify-content: center;
align-items: center;
gap: 0.25rem;
border-radius: 0.5rem;
background: var(--appwrite-purple, #7c67fe);
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.06);
color: var(--color-bw-white, #fff);
text-align: center;
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 500;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.01225rem;
margin-block-start: auto;
white-space: nowrap;
position: relative;
&::before {
content: "";
display: block;
position: absolute;
left: 0;
width: 1rem;
height: 1rem;
top: 50%;
transform: translateY(-50%);
background-image: url("/images/animations/check-circle.svg");
}
}
}
}
.methods {
margin-block-start: 1.25rem;
display: flex;
gap: 0.75rem;
overflow: hidden;
margin-inline: -1rem;
padding-inline: 1rem;
li {
flex-shrink: 0;
display: flex;
width: 5.5rem;
padding: 0.75rem 0.75rem 0.625rem 0.75rem;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 0.125rem;
border-radius: 0.75rem;
border: 1px solid var(--greyscale-50, #ededf0);
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 166.667% */
&[data-active] {
border-color: var(--appwrite-purple, #7c67fe);
}
}
}
.form {
margin-block-start: 1.25rem;
> p {
color: var(--dark-neutrals-150, #373b4d);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 150%; /* 1.125rem */
}
.bordered {
margin-block-start: 0.25rem;
border-radius: 0.5rem;
border: 1px solid #ededf0;
background: var(--color-bw-white, #fff);
> div:first-child {
display: flex;
gap: 0.25rem;
padding: 0.65rem 0.75rem;
border-bottom: 1px solid #ededf0;
> :nth-child(2) {
margin-inline-start: auto;
}
}
> div:nth-child(2) {
display: flex;
> p {
padding: 0.65rem 0.75rem;
}
> p:first-child {
flex-grow: 3;
}
> p:last-child {
flex-grow: 1;
border-inline-start: 1px solid #ededf0;
}
}
p {
color: var(--light-neutrals-50, #c4c6d7);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 150%; /* 1.125rem */
}
}
}
> button {
display: flex;
text-align: center;
padding: 0.5rem 1rem;
justify-content: center;
align-items: center;
gap: 0.25rem;
border-radius: 0.5rem;
background: var(--appwrite-purple, #7c67fe);
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.06);
color: var(--color-bw-white, #fff);
text-align: center;
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 500;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.01225rem;
margin-block-start: auto;
white-space: nowrap;
}
}
</style>

View File

@@ -1,116 +1,120 @@
<script lang="ts">
import { fade, slide } from 'svelte/transition';
import { messagingController } from '.';
import { flip } from '$lib/utils/flip';
import { fade, slide } from "svelte/transition";
import { messagingController } from ".";
import { flip } from "$lib/utils/flip";
const { state } = messagingController;
const { state } = messagingController;
</script>
<div class="pseudo-table">
<div class="header">
<span class="web-eyebrow">Message ID</span>
<span class="web-eyebrow">Type</span>
<span class="web-eyebrow" style:text-align="center">Status</span>
</div>
{#each $state.messages.slice(0, $state.tableSlice) as task (task.id)}
<div class="row" transition:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
<div class="copy-button">
<span class="web-icon-copy" />
<span>{task.id}</span>
</div>
<div class="icon-button">
<div class="icon">
<img src={task.icon} alt="" width="16" height="16" />
</div>
<span class="truncated">{task.type}</span>
</div>
<div class="status-indicator">
{#if task.status === 'sending'}
<div class="loader is-small" in:fade />
{:else}
<span class="web-icon-check" />
{/if}
</div>
<div class="header">
<span class="web-eyebrow">Message ID</span>
<span class="web-eyebrow">Type</span>
<span class="web-eyebrow" style:text-align="center">Status</span>
</div>
{#each $state.messages.slice(0, $state.tableSlice) as task (task.id)}
<div
class="row"
transition:slide={{ duration: 150 }}
animate:flip={{ duration: 150 }}
>
<div class="copy-button">
<span class="web-icon-copy" />
<span>{task.id}</span>
</div>
<div class="icon-button">
<div class="icon">
<img src={task.icon} alt="" width="16" height="16" />
</div>
{/each}
<span class="truncated">{task.type}</span>
</div>
<div class="status-indicator">
{#if task.status === "sending"}
<div class="loader is-small" in:fade />
{:else}
<span class="web-icon-check" />
{/if}
</div>
</div>
{/each}
</div>
<style lang="scss">
.header,
.row {
grid-template-columns: 7rem 1fr 1fr !important;
gap: 1.5rem 3rem;
.header,
.row {
grid-template-columns: 7rem 1fr 1fr !important;
gap: 1.5rem 3rem;
}
.copy-button {
display: flex;
padding: 0.25rem 0.5rem;
align-items: center;
justify-content: space-between;
gap: 0.375rem;
border-radius: 62.4375rem;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(2.6666667461395264px);
[class*="icon-"] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-600));
}
.copy-button {
display: flex;
padding: 0.25rem 0.5rem;
align-items: center;
justify-content: space-between;
gap: 0.375rem;
span:not([class*="icon-"]) {
color: var(--greyscale-400, #adadb1);
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 142.857% */
border-radius: 62.4375rem;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(2.6666667461395264px);
[class*='icon-'] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-600));
}
span:not([class*='icon-']) {
color: var(--greyscale-400, #adadb1);
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 142.857% */
overflow: hidden;
text-overflow: ellipsis;
}
overflow: hidden;
text-overflow: ellipsis;
}
}
.icon-button {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
.icon-button {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
.icon {
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(2.6666667461395264px);
border-radius: 100%;
height: 2rem;
width: 2rem;
display: flex;
align-items: center;
justify-content: center;
}
span:not([class*='icon-']) {
color: var(--greyscale-400, #adadb1);
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 142.857% */
overflow: hidden;
text-overflow: ellipsis;
}
.icon {
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(2.6666667461395264px);
border-radius: 100%;
height: 2rem;
width: 2rem;
display: flex;
align-items: center;
justify-content: center;
}
span:not([class*="icon-"]) {
color: var(--greyscale-400, #adadb1);
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.25rem; /* 142.857% */
.status-indicator {
display: flex;
align-items: center;
justify-content: center;
[class*='icon-'] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-600));
}
overflow: hidden;
text-overflow: ellipsis;
}
}
.status-indicator {
display: flex;
align-items: center;
justify-content: center;
[class*="icon-"] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-600));
}
}
</style>

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { messagingController } from '.';
import Code from '$lib/animations/CodeWindow/Code.svelte';
import { messagingController } from ".";
import Code from "$lib/animations/CodeWindow/Code.svelte";
const { state } = messagingController;
const { state } = messagingController;
$: content = `
$: content = `
await messaging.createPush(
ID.unique(),
'${$state.heading}',

View File

@@ -1,164 +1,173 @@
import Box from './box.svelte';
import Code from './code.svelte';
import Phone from './phone.svelte';
import Box from "./box.svelte";
import Code from "./code.svelte";
import Phone from "./phone.svelte";
import { safeAnimate, sleep, write } from '$lib/animations';
import { createResettable } from '$lib/utils/resettable';
import { getElSelector } from '../Products.svelte';
import { safeAnimate, sleep, write } from "$lib/animations";
import { createResettable } from "$lib/utils/resettable";
import { getElSelector } from "../Products.svelte";
type Task = {
id: string;
title: string;
checked: boolean;
id: string;
title: string;
checked: boolean;
};
type Message = {
id: string;
type: string;
icon: string;
status: 'sending' | 'sent';
id: string;
type: string;
icon: string;
status: "sending" | "sent";
};
type State = {
heading: string;
message: string;
tasks: Task[];
messages: Message[];
tableSlice: number;
submit: 'loading' | 'success';
heading: string;
message: string;
tasks: Task[];
messages: Message[];
tableSlice: number;
submit: "loading" | "success";
};
const state = createResettable<State>({
heading: '',
message: '',
tasks: [
{
id: '3397fecdedb13397fecdedb1',
title: 'Research user needs',
checked: true
}
],
messages: [
{
id: '...3397fecdedb1',
type: 'SMS',
icon: './images/icons/illustrated/dark/sms.svg',
status: 'sent'
},
{
id: '...2224gabjger4',
type: 'Email',
icon: './images/icons/illustrated/dark/email.svg',
status: 'sent'
}
],
tableSlice: 2,
submit: 'loading'
heading: "",
message: "",
tasks: [
{
id: "3397fecdedb13397fecdedb1",
title: "Research user needs",
checked: true,
},
],
messages: [
{
id: "...3397fecdedb1",
type: "SMS",
icon: "./images/icons/illustrated/dark/sms.svg",
status: "sent",
},
{
id: "...2224gabjger4",
type: "Email",
icon: "./images/icons/illustrated/dark/email.svg",
status: "sent",
},
],
tableSlice: 2,
submit: "loading",
});
const execute = async () => {
const phone = getElSelector('phone');
const box = getElSelector('box');
const code = getElSelector('code');
const { update } = state.reset();
const phone = getElSelector("phone");
const box = getElSelector("box");
const code = getElSelector("code");
const { update } = state.reset();
await Promise.all([
safeAnimate(phone, { x: 365, y: 0, width: '275px' }, { duration: 0.5 })?.finished,
safeAnimate(code, { x: 80, y: 325, opacity: 0, zIndex: 100 }, { duration: 0.5 })?.finished,
safeAnimate(box, { x: 0, y: 32, opacity: 1 }, { duration: 0.5, delay: 1 })?.finished
]);
await Promise.all([
safeAnimate(phone, { x: 365, y: 0, width: "275px" }, { duration: 0.5 })
?.finished,
safeAnimate(
code,
{ x: 80, y: 325, opacity: 0, zIndex: 100 },
{ duration: 0.5 },
)?.finished,
safeAnimate(box, { x: 0, y: 32, opacity: 1 }, { duration: 0.5, delay: 1 })
?.finished,
]);
await sleep(250);
update((p) => ({
...p,
tasks: [
...p.tasks,
{
id: "3397fecdedb13397fecdedb2",
title: "Create wireframes",
checked: false,
},
],
}));
await sleep(250);
update((p) => ({
...p,
tableSlice: p.tableSlice + 1,
}));
await sleep(250);
update((p) => ({
...p,
tasks: [
...p.tasks,
{
id: "3397fecdedb13397fecdedb3",
title: "Create visual design",
checked: false,
},
],
}));
await sleep(250);
update((p) => ({
...p,
tableSlice: p.tableSlice + 1,
}));
await sleep(250);
safeAnimate(code, { opacity: 1 }, { duration: 0.5 })?.finished,
await sleep(250);
await write(
"New task assigned to you",
(v) => {
state.update((n) => ({ ...n, heading: v }));
},
300,
);
update((p) => ({
...p,
tasks: [
...p.tasks,
{
id: '3397fecdedb13397fecdedb2',
title: 'Create wireframes',
checked: false
}
]
}));
await sleep(250);
await write(
"You were assigned a new task in your board. Tap to check it out.",
(v) => {
state.update((n) => ({ ...n, message: v }));
},
300,
);
update((p) => ({
...p,
tableSlice: p.tableSlice + 1
}));
await sleep(250);
await sleep(250);
update((p) => ({
...p,
messages: [
...p.messages,
{
id: "...5689fdoerre2",
type: "Push",
icon: "./images/icons/illustrated/dark/push.svg",
status: "sending",
},
],
}));
update((p) => ({
...p,
tasks: [
...p.tasks,
{
id: '3397fecdedb13397fecdedb3',
title: 'Create visual design',
checked: false
}
]
}));
await sleep(1250);
await sleep(250);
update((p) => ({
...p,
tableSlice: p.tableSlice + 1
}));
await sleep(250);
safeAnimate(code, { opacity: 1 }, { duration: 0.5 })?.finished, await sleep(250);
await write(
'New task assigned to you',
(v) => {
state.update((n) => ({ ...n, heading: v }));
},
300
);
await write(
'You were assigned a new task in your board. Tap to check it out.',
(v) => {
state.update((n) => ({ ...n, message: v }));
},
300
);
await sleep(250);
update((p) => ({
...p,
messages: [
...p.messages,
{
id: '...5689fdoerre2',
type: 'Push',
icon: './images/icons/illustrated/dark/push.svg',
status: 'sending'
}
]
}));
await sleep(1250);
update((p) => ({
...p,
submit: 'success',
messages: p.messages.map((m) => (m.id === '...5689fdoerre2' ? { ...m, status: 'sent' } : m))
}));
update((p) => ({
...p,
submit: "success",
messages: p.messages.map((m) =>
m.id === "...5689fdoerre2" ? { ...m, status: "sent" } : m,
),
}));
};
export const messagingController = {
execute,
state
execute,
state,
};
export const Messaging = {
Phone,
Box,
Code
Phone,
Box,
Code,
};

View File

@@ -1,205 +1,211 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { messagingController } from '.';
import TaskCheckbox from '../TaskCheckbox.svelte';
import { fly } from "svelte/transition";
import { messagingController } from ".";
import TaskCheckbox from "../TaskCheckbox.svelte";
const { state } = messagingController;
const { state } = messagingController;
</script>
{#if $state.submit === 'success'}
<div class="push-notification" in:fly={{ y: -20 }}>
<div class="icon" />
<div class="content">
<div class="header">
<h3 class="title">New task assigned to you</h3>
<span class="time">now</span>
</div>
<p class="message">You were assigned a new task in your board. Tap to check it out.</p>
</div>
{#if $state.submit === "success"}
<div class="push-notification" in:fly={{ y: -20 }}>
<div class="icon" />
<div class="content">
<div class="header">
<h3 class="title">New task assigned to you</h3>
<span class="time">now</span>
</div>
<p class="message">
You were assigned a new task in your board. Tap to check it out.
</p>
</div>
</div>
{/if}
<div data-theme-ignore class="inner-phone light">
<div class="header">
<p class="title">Your tasks</p>
<span class="icon-menu" aria-label="menu" />
</div>
<div class="header">
<p class="title">Your tasks</p>
<span class="icon-menu" aria-label="menu" />
</div>
<div class="date">Today</div>
<div class="tasks">
{#each $state.tasks as task (task.id)}
<div class="task" data-checked={task.checked ? '' : undefined} in:fly={{ x: -16 }}>
<TaskCheckbox bind:checked={task.checked} />
<span class="title">{task.title}</span>
</div>
{/each}
</div>
<button class="add-btn">
<span class="web-icon-plus" />
</button>
<div class="date">Today</div>
<div class="tasks">
{#each $state.tasks as task (task.id)}
<div
class="task"
data-checked={task.checked ? "" : undefined}
in:fly={{ x: -16 }}
>
<TaskCheckbox bind:checked={task.checked} />
<span class="title">{task.title}</span>
</div>
{/each}
</div>
<button class="add-btn">
<span class="web-icon-plus" />
</button>
</div>
<style lang="scss">
.push-notification {
position: absolute;
.push-notification {
position: absolute;
display: flex;
justify-content: space-between;
align-items: center;
top: 20px;
padding: 0.5rem;
margin: 0 auto;
width: 125%;
height: 60px;
gap: 0.75rem;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
border-radius: 20px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(8px);
box-shadow: 3px -8px 32px 0px rgba(0, 0, 0, 0.24);
.icon {
height: 38px;
width: 38px;
flex-shrink: 0;
border-radius: 10px;
background-image: linear-gradient(180deg, #7c67fe, #4a3e98);
}
.header {
display: flex;
justify-content: space-between;
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 600;
line-height: 1rem; /* 137.5% */
letter-spacing: -0.014rem;
}
.time {
color: var(--color-greyscale-700, #56565c);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1rem; /* 137.5% */
letter-spacing: -0.014rem;
}
}
.message {
color: var(--color-greyscale-700, #56565c);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1rem; /* 137.5% */
letter-spacing: -0.014rem;
}
}
.inner-phone {
padding-block: 3rem 1.5rem;
padding-inline: 1rem;
color: rgba(67, 67, 71, 1);
text-align: left;
position: relative;
height: 100%;
overflow: visible;
display: flex;
flex-direction: column;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 1rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
[class*="icon-"] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-500));
}
}
.date {
margin-block-start: 3rem;
color: hsl(var(--web-color-greyscale-600));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 500;
line-height: 1.25rem; /* 166.667% */
}
.tasks {
margin-block-start: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
.task {
display: flex;
justify-content: space-between;
align-items: center;
top: 20px;
padding: 0.5rem;
margin: 0 auto;
width: 125%;
height: 60px;
gap: 0.75rem;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
border-radius: 20px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(8px);
box-shadow: 3px -8px 32px 0px rgba(0, 0, 0, 0.24);
.icon {
height: 38px;
width: 38px;
flex-shrink: 0;
border-radius: 10px;
background-image: linear-gradient(180deg, #7c67fe, #4a3e98);
}
.header {
display: flex;
justify-content: space-between;
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 600;
line-height: 1rem; /* 137.5% */
letter-spacing: -0.014rem;
}
.time {
color: var(--color-greyscale-700, #56565c);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1rem; /* 137.5% */
letter-spacing: -0.014rem;
}
}
.message {
color: var(--color-greyscale-700, #56565c);
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 400;
line-height: 1rem; /* 137.5% */
letter-spacing: -0.014rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--web-color-greyscale-50));
background: hsl(var(--web-color-white));
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
padding-block: 0.55rem;
padding-inline: 0.88rem;
/* Responsive/SubBody-400 */
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.00394rem;
transition: opacity 200ms ease;
&[data-checked] {
opacity: 0.6;
}
}
}
.inner-phone {
padding-block: 3rem 1.5rem;
padding-inline: 1rem;
.add-btn {
position: absolute;
right: 1rem;
bottom: 2.5rem;
color: rgba(67, 67, 71, 1);
text-align: left;
width: 2.5rem;
height: 2.5rem;
flex-shrink: 0;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
background-color: rgba(124, 103, 254, 1);
color: rgba(237, 237, 240, 1);
font-size: 1.5rem;
position: relative;
height: 100%;
overflow: visible;
display: flex;
flex-direction: column;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 1rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
[class*='icon-'] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-500));
}
}
.date {
margin-block-start: 3rem;
color: hsl(var(--web-color-greyscale-600));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 500;
line-height: 1.25rem; /* 166.667% */
}
.tasks {
margin-block-start: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
.task {
display: flex;
align-items: center;
gap: 0.75rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--web-color-greyscale-50));
background: hsl(var(--web-color-white));
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
padding-block: 0.55rem;
padding-inline: 0.88rem;
/* Responsive/SubBody-400 */
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.00394rem;
transition: opacity 200ms ease;
&[data-checked] {
opacity: 0.6;
}
}
}
.add-btn {
position: absolute;
right: 1rem;
bottom: 2.5rem;
width: 2.5rem;
height: 2.5rem;
flex-shrink: 0;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
background-color: rgba(124, 103, 254, 1);
color: rgba(237, 237, 240, 1);
font-size: 1.5rem;
display: grid;
place-items: center;
border-radius: 100%;
}
display: grid;
place-items: center;
border-radius: 100%;
}
}
</style>

View File

@@ -1,7 +1,7 @@
import { safeAnimate, sleep } from '$lib/animations';
import { createResettable } from '$lib/utils/resettable';
import { animate } from 'motion';
import { getElSelector } from '../Products.svelte';
import { safeAnimate, sleep } from "$lib/animations";
import { createResettable } from "$lib/utils/resettable";
import { animate } from "motion";
import { getElSelector } from "../Products.svelte";
const requests = createResettable(0);
const databases = createResettable(0);
@@ -12,42 +12,54 @@ const executions = createResettable(0);
const realtime = createResettable(0);
const execute = async () => {
const phone = getElSelector('phone');
const pd = getElSelector('pd');
const phone = getElSelector("phone");
const pd = getElSelector("pd");
const graphBox = getElSelector('graph-box');
const graphBox = getElSelector("graph-box");
const boxesAndStates = [
{ box: getElSelector('post-auth'), state: authentication.reset() },
{ box: getElSelector('post-storage'), state: storage.reset() },
{ box: getElSelector('post-bandwidth'), state: bandwidth.reset() },
{ box: getElSelector('post-functions'), state: executions.reset() },
{ box: getElSelector('post-databases'), state: databases.reset() },
{ box: getElSelector('post-realtime'), state: realtime.reset() },
{ box: getElSelector('post-requests'), state: requests.reset() }
];
const boxesAndStates = [
{ box: getElSelector("post-auth"), state: authentication.reset() },
{ box: getElSelector("post-storage"), state: storage.reset() },
{ box: getElSelector("post-bandwidth"), state: bandwidth.reset() },
{ box: getElSelector("post-functions"), state: executions.reset() },
{ box: getElSelector("post-databases"), state: databases.reset() },
{ box: getElSelector("post-realtime"), state: realtime.reset() },
{ box: getElSelector("post-requests"), state: requests.reset() },
];
await Promise.all([
safeAnimate(pd, { opacity: 0, y: -16 }, { duration: 0.5 })?.finished,
safeAnimate(graphBox, { opacity: 0, visibility: 'hidden' }, { duration: 0.5 })?.finished,
safeAnimate(phone, { x: '-50%', width: '660px' }, { duration: 1, delay: 0.5 })?.finished
]);
await Promise.all([
safeAnimate(pd, { opacity: 0, y: -16 }, { duration: 0.5 })?.finished,
safeAnimate(
graphBox,
{ opacity: 0, visibility: "hidden" },
{ duration: 0.5 },
)?.finished,
safeAnimate(
phone,
{ x: "-50%", width: "660px" },
{ duration: 1, delay: 0.5 },
)?.finished,
]);
boxesAndStates.forEach(({ box, state }, i) => {
safeAnimate(box, { opacity: 1, y: [1200, 0] }, { duration: 0.5, delay: i * 0.1 })?.finished;
animate(state.set, { duration: 2, delay: (i + 1) * 0.25 });
});
boxesAndStates.forEach(({ box, state }, i) => {
safeAnimate(
box,
{ opacity: 1, y: [1200, 0] },
{ duration: 0.5, delay: i * 0.1 },
)?.finished;
animate(state.set, { duration: 2, delay: (i + 1) * 0.25 });
});
};
export const postController = {
execute,
state: {
requests,
databases,
authentication,
storage,
bandwidth,
executions,
realtime
}
execute,
state: {
requests,
databases,
authentication,
storage,
bandwidth,
executions,
realtime,
},
};

View File

@@ -1,238 +1,286 @@
import Phone from './phone.svelte';
import Phone from "./phone.svelte";
import { safeAnimate, sleep } from '$lib/animations';
import { createResettable } from '$lib/utils/resettable';
import { getElSelector } from '../Products.svelte';
import { animate } from 'motion';
import { safeAnimate, sleep } from "$lib/animations";
import { createResettable } from "$lib/utils/resettable";
import { getElSelector } from "../Products.svelte";
import { animate } from "motion";
type Task = {
title: string;
tags: string[];
images?: string[];
title: string;
tags: string[];
images?: string[];
};
type User = {
name: string;
color: string;
name: string;
color: string;
};
type State = {
tasks: {
todo: Task[];
doing: Task[];
done: Task[];
};
users: User[];
tasks: {
todo: Task[];
doing: Task[];
done: Task[];
};
users: User[];
};
const state = createResettable<State>({
tasks: {
todo: [
{
title: 'Edit images for website',
tags: ['design', 'content'],
images: ['./images/animations/storage-2.png', './images/animations/storage-3.png']
}
tasks: {
todo: [
{
title: "Edit images for website",
tags: ["design", "content"],
images: [
"./images/animations/storage-2.png",
"./images/animations/storage-3.png",
],
doing: [
{
title: 'Handoff meet',
tags: ['design', 'dev']
}
],
done: []
},
users: []
},
],
doing: [
{
title: "Handoff meet",
tags: ["design", "dev"],
},
],
done: [],
},
users: [],
});
export const connectionsProg = createResettable(0);
const addUser = (update: typeof state.update, user: User) => {
update((p) => ({
...p,
users: [...p.users, user]
}));
update((p) => ({
...p,
users: [...p.users, user],
}));
};
const addTask = (update: typeof state.update, group: keyof State['tasks'], task: Task) => {
update((p) => ({
...p,
tasks: {
...p.tasks,
[group]: [task, ...p.tasks[group]]
}
}));
const addTask = (
update: typeof state.update,
group: keyof State["tasks"],
task: Task,
) => {
update((p) => ({
...p,
tasks: {
...p.tasks,
[group]: [task, ...p.tasks[group]],
},
}));
};
const execute = async () => {
const phone = getElSelector('phone');
const code = getElSelector('code');
const box = getElSelector('box');
const phone = getElSelector("phone");
const code = getElSelector("code");
const box = getElSelector("box");
const walter = getElSelector('user-Walter');
const aditya = getElSelector('user-Aditya');
const sara = getElSelector('user-Sara');
const walter = getElSelector("user-Walter");
const aditya = getElSelector("user-Aditya");
const sara = getElSelector("user-Sara");
const addTodo = getElSelector('add-todo');
const addDoing = getElSelector('add-doing');
const addDone = getElSelector('add-done');
const addTodo = getElSelector("add-todo");
const addDoing = getElSelector("add-doing");
const addDone = getElSelector("add-done");
const graphBox = getElSelector('graph-box');
const graphBox = getElSelector("graph-box");
const pd = getElSelector('pd');
const pd = getElSelector("pd");
const { update } = state.reset();
const { set: setConn } = connectionsProg.reset();
const { update } = state.reset();
const { set: setConn } = connectionsProg.reset();
await Promise.all([
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(phone, { x: 0, y: 0, width: "660px" }, { duration: 0.5 })
?.finished,
safeAnimate(code, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(
graphBox,
{ opacity: 0, x: 0, y: 0, visibility: "visible" },
{ duration: 0 },
)?.finished,
safeAnimate(pd, { opacity: 1, y: 0 }, { duration: 0.5 })?.finished,
]);
// Graphbox
sleep(1250).then(async () => {
await safeAnimate(graphBox, { opacity: 1 }, { duration: 0.5 })?.finished;
animate(
(y) => {
setConn(y);
},
{ duration: 2.5, easing: "ease-in" },
);
});
// Walter
sleep(500).then(async () => {
addUser(update, { name: "Walter", color: "#fd366e" });
await sleep(500);
await safeAnimate(walter, { x: -200, y: -100, scale: 1 }, { duration: 0.5 })
?.finished;
await Promise.all([
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(phone, { x: 0, y: 0, width: '660px' }, { duration: 0.5 })?.finished,
safeAnimate(code, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(graphBox, { opacity: 0, x: 0, y: 0, visibility: 'visible' }, { duration: 0 })
?.finished,
safeAnimate(pd, { opacity: 1, y: 0 }, { duration: 0.5 })?.finished
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addTodo, { scale: [1, 0.9, 1] }, { duration: 0.25 })
?.finished,
]);
// Graphbox
sleep(1250).then(async () => {
await safeAnimate(graphBox, { opacity: 1 }, { duration: 0.5 })?.finished;
animate(
(y) => {
setConn(y);
},
{ duration: 2.5, easing: 'ease-in' }
);
addTask(update, "todo", {
title: "Handoff meet",
tags: ["design", "dev"],
});
// Walter
sleep(500).then(async () => {
addUser(update, { name: 'Walter', color: '#fd366e' });
await sleep(500);
await safeAnimate(walter, { x: -200, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
await Promise.all([
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addTodo, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
]);
await safeAnimate(
walter,
{ scale: 1, x: -180, y: -160 },
{ duration: 0.75, delay: 0.5 },
)?.finished;
addTask(update, 'todo', {
title: 'Handoff meet',
tags: ['design', 'dev']
});
await sleep(500);
await safeAnimate(walter, { scale: 1, x: -180, y: -160 }, { duration: 0.75, delay: 0.5 })
?.finished;
await safeAnimate(walter, { x: 210, y: -100, scale: 1 }, { duration: 0.5 })
?.finished;
await sleep(500);
await Promise.all([
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })
?.finished,
]);
await safeAnimate(walter, { x: 210, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
await Promise.all([
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
]);
addTask(update, 'done', {
title: 'Create migrations script',
tags: ['Dev']
});
safeAnimate(walter, { scale: 1, x: 230, y: -20 }, { duration: 0.75, delay: 0.5 });
await sleep(750);
await safeAnimate(walter, { x: -10, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
await Promise.all([
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDoing, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
]);
addTask(update, 'doing', {
title: 'Configure blog SEO',
tags: ['dev', 'content']
});
await safeAnimate(walter, { scale: 1, x: -70, y: 80 }, { duration: 0.75, delay: 0.25 });
addTask(update, "done", {
title: "Create migrations script",
tags: ["Dev"],
});
// Aditya
sleep(1500).then(async () => {
addUser(update, { name: 'Aditya', color: 'rgba(124, 103, 254, 1)' });
await sleep(500);
await safeAnimate(aditya, { x: 200, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
await Promise.all([
safeAnimate(aditya, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
]);
safeAnimate(
walter,
{ scale: 1, x: 230, y: -20 },
{ duration: 0.75, delay: 0.5 },
);
addTask(update, 'done', {
title: 'Write up briefing',
tags: ['dev-rel']
});
await sleep(750);
await safeAnimate(aditya, { scale: 1, x: 180, y: 60 }, { duration: 0.75, delay: 0.5 })
?.finished;
await safeAnimate(walter, { x: -10, y: -100, scale: 1 }, { duration: 0.5 })
?.finished;
await sleep(750);
await Promise.all([
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDoing, { scale: [1, 0.9, 1] }, { duration: 0.25 })
?.finished,
]);
await safeAnimate(aditya, { x: -210, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
await Promise.all([
safeAnimate(aditya, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addTodo, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
]);
addTask(update, 'todo', {
title: 'Review branding blog post',
tags: ['dev-rel']
});
await safeAnimate(aditya, { scale: 1, x: 70, y: -220 }, { duration: 0.75, delay: 0.5 })
?.finished;
addTask(update, "doing", {
title: "Configure blog SEO",
tags: ["dev", "content"],
});
// Sara
sleep(2500).then(async () => {
addUser(update, { name: 'Sara', color: 'rgba(103, 163, 254, 1)' });
await sleep(500);
await safeAnimate(sara, { x: 0, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
await Promise.all([
safeAnimate(sara, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDoing, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
]);
await safeAnimate(
walter,
{ scale: 1, x: -70, y: 80 },
{ duration: 0.75, delay: 0.25 },
);
});
addTask(update, 'doing', {
title: 'Prepare design system presentation',
tags: ['design']
});
// Aditya
sleep(1500).then(async () => {
addUser(update, { name: "Aditya", color: "rgba(124, 103, 254, 1)" });
await sleep(500);
await safeAnimate(aditya, { x: 200, y: -100, scale: 1 }, { duration: 0.5 })
?.finished;
await Promise.all([
safeAnimate(aditya, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })
?.finished,
]);
await safeAnimate(sara, { scale: 1, y: 60, x: -50 }, { duration: 0.75, delay: 0.5 })
?.finished;
await sleep(250);
await safeAnimate(sara, { x: 200, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
await Promise.all([
safeAnimate(sara, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
]);
addTask(update, 'done', {
title: 'QA branding animations',
tags: ['Dev']
});
await safeAnimate(sara, { scale: 1, x: 180, y: 60 }, { duration: 0.75, delay: 0.5 })
?.finished;
addTask(update, "done", {
title: "Write up briefing",
tags: ["dev-rel"],
});
await safeAnimate(
aditya,
{ scale: 1, x: 180, y: 60 },
{ duration: 0.75, delay: 0.5 },
)?.finished;
await sleep(750);
await safeAnimate(aditya, { x: -210, y: -100, scale: 1 }, { duration: 0.5 })
?.finished;
await Promise.all([
safeAnimate(aditya, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addTodo, { scale: [1, 0.9, 1] }, { duration: 0.25 })
?.finished,
]);
addTask(update, "todo", {
title: "Review branding blog post",
tags: ["dev-rel"],
});
await safeAnimate(
aditya,
{ scale: 1, x: 70, y: -220 },
{ duration: 0.75, delay: 0.5 },
)?.finished;
});
// Sara
sleep(2500).then(async () => {
addUser(update, { name: "Sara", color: "rgba(103, 163, 254, 1)" });
await sleep(500);
await safeAnimate(sara, { x: 0, y: -100, scale: 1 }, { duration: 0.5 })
?.finished;
await Promise.all([
safeAnimate(sara, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDoing, { scale: [1, 0.9, 1] }, { duration: 0.25 })
?.finished,
]);
addTask(update, "doing", {
title: "Prepare design system presentation",
tags: ["design"],
});
await safeAnimate(
sara,
{ scale: 1, y: 60, x: -50 },
{ duration: 0.75, delay: 0.5 },
)?.finished;
await sleep(250);
await safeAnimate(sara, { x: 200, y: -100, scale: 1 }, { duration: 0.5 })
?.finished;
await Promise.all([
safeAnimate(sara, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })
?.finished,
]);
addTask(update, "done", {
title: "QA branding animations",
tags: ["Dev"],
});
await safeAnimate(
sara,
{ scale: 1, x: 180, y: 60 },
{ duration: 0.75, delay: 0.5 },
)?.finished;
});
};
export const realtimeController = {
execute,
state
execute,
state,
};
export const Realtime = {
Phone
Phone,
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +1,43 @@
<script lang="ts">
import { slide } from 'svelte/transition';
import { storageController } from '.';
import { flip } from '$lib/utils/flip';
import { slide } from "svelte/transition";
import { storageController } from ".";
import { flip } from "$lib/utils/flip";
const { state } = storageController;
const { state } = storageController;
</script>
<div class="pseudo-table">
<div class="header">
<span class="web-eyebrow">Filename</span>
<span class="web-eyebrow">Type</span>
<span class="web-eyebrow">Size</span>
<div class="header">
<span class="web-eyebrow">Filename</span>
<span class="web-eyebrow">Type</span>
<span class="web-eyebrow">Size</span>
</div>
{#each $state.files as file (file.src)}
<div
class="row"
in:slide={{ duration: 150 }}
animate:flip={{ duration: 150 }}
>
<div class="img-wrapper">
<img src={file.src} alt="" />
<span>{file.filename}</span>
</div>
<span class="truncated">{file.type}</span>
<span class="truncated">{file.size}</span>
</div>
{#each $state.files as file (file.src)}
<div class="row" in:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
<div class="img-wrapper">
<img src={file.src} alt="" />
<span>{file.filename}</span>
</div>
<span class="truncated">{file.type}</span>
<span class="truncated">{file.size}</span>
</div>
{/each}
{/each}
</div>
<style lang="scss">
.header,
.row {
grid-template-columns: 7rem 1fr 1fr !important;
gap: 1.5rem 3rem;
}
.header,
.row {
grid-template-columns: 7rem 1fr 1fr !important;
gap: 1.5rem 3rem;
}
.img-wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
}
.img-wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
}
</style>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import Code from '$lib/animations/CodeWindow/Code.svelte';
import Code from "$lib/animations/CodeWindow/Code.svelte";
let content = `
let content = `
const result = storage.createFile(
'my-bucket',
ID.unique(),

View File

@@ -1,137 +1,146 @@
import Box from './box.svelte';
import Code from './code.svelte';
import Phone from './phone.svelte';
import Box from "./box.svelte";
import Code from "./code.svelte";
import Phone from "./phone.svelte";
import { safeAnimate, sleep } from '$lib/animations';
import { createResettable } from '$lib/utils/resettable';
import { getElSelector } from '../Products.svelte';
import { safeAnimate, sleep } from "$lib/animations";
import { createResettable } from "$lib/utils/resettable";
import { getElSelector } from "../Products.svelte";
type File = {
src: string;
filename: string;
type: string;
size: string;
src: string;
filename: string;
type: string;
size: string;
};
type State = {
files: File[];
files: File[];
};
const state = createResettable<State>({
files: []
files: [],
});
const execute = async () => {
const phone = getElSelector('phone');
const box = getElSelector('box');
const code = getElSelector('code');
const overlay = getElSelector('overlay');
const drawer = getElSelector('drawer');
const upload = getElSelector('upload');
const uploadBtn = getElSelector('upload-btn');
const uploadImg = getElSelector('upload-img');
const uploadLoading = getElSelector('upload-loading');
const uploadText = getElSelector('upload-text');
const phone = getElSelector("phone");
const box = getElSelector("box");
const code = getElSelector("code");
const overlay = getElSelector("overlay");
const drawer = getElSelector("drawer");
const upload = getElSelector("upload");
const uploadBtn = getElSelector("upload-btn");
const uploadImg = getElSelector("upload-img");
const uploadLoading = getElSelector("upload-loading");
const uploadText = getElSelector("upload-text");
const { update } = state.reset();
const { update } = state.reset();
await Promise.all([
safeAnimate(phone, { x: 0, y: 0 }, { duration: 0.5 })?.finished,
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(code, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(uploadLoading, { opacity: 0 }, { duration: 0 })?.finished
]);
await Promise.all([
safeAnimate(phone, { x: 0, y: 0 }, { duration: 0.5 })?.finished,
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(code, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(uploadLoading, { opacity: 0 }, { duration: 0 })?.finished,
]);
await safeAnimate(code, { zIndex: 20 }, { duration: 0 })?.finished;
await safeAnimate(code, { zIndex: 20 }, { duration: 0 })?.finished;
update((p) => ({
...p,
files: [
...p.files,
{
src: '/images/animations/storage-1.png',
filename: 'Profile.png',
type: 'image/png',
size: '362.6 KB'
}
]
}));
update((p) => ({
...p,
files: [
...p.files,
{
src: "/images/animations/storage-1.png",
filename: "Profile.png",
type: "image/png",
size: "362.6 KB",
},
],
}));
await sleep(250);
await sleep(250);
await Promise.all([
safeAnimate(overlay, { opacity: 1 }, { duration: 0.25 })?.finished,
safeAnimate(drawer, { y: [128, 0], opacity: 1 }, { duration: 0.5 })?.finished
]);
await Promise.all([
safeAnimate(overlay, { opacity: 1 }, { duration: 0.25 })?.finished,
safeAnimate(drawer, { y: [128, 0], opacity: 1 }, { duration: 0.5 })
?.finished,
]);
await sleep(250);
await sleep(250);
await safeAnimate(uploadBtn, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished;
await safeAnimate(uploadBtn, { scale: [1, 0.9, 1] }, { duration: 0.25 })
?.finished;
await safeAnimate(code, { x: 300, y: 32 }, { duration: 0 })?.finished;
await safeAnimate(code, { x: 300, y: 32 }, { duration: 0 })?.finished;
await Promise.all([
safeAnimate(code, { y: [32 - 16, 32], opacity: 1 }, { duration: 0.5 })?.finished,
safeAnimate(upload, { y: [-16, 0], opacity: 1 }, { duration: 0.5 })?.finished
]);
await Promise.all([
safeAnimate(code, { y: [32 - 16, 32], opacity: 1 }, { duration: 0.5 })
?.finished,
safeAnimate(upload, { y: [-16, 0], opacity: 1 }, { duration: 0.5 })
?.finished,
]);
await sleep(250);
await sleep(250);
await safeAnimate(box, { x: 300, y: 300 }, { duration: 0 })?.finished;
await safeAnimate(box, { x: 300, y: 300 }, { duration: 0 })?.finished;
await Promise.all([
safeAnimate(uploadImg, { x: [64, 48], y: [80, 64], opacity: 1 }, { duration: 0.5 })
?.finished,
safeAnimate(box, { y: [300 - 16, 300], opacity: 1 }, { duration: 1 })?.finished
]);
await Promise.all([
safeAnimate(
uploadImg,
{ x: [64, 48], y: [80, 64], opacity: 1 },
{ duration: 0.5 },
)?.finished,
safeAnimate(box, { y: [300 - 16, 300], opacity: 1 }, { duration: 1 })
?.finished,
]);
await sleep(250);
await sleep(250);
await Promise.all([
safeAnimate(uploadText, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(uploadLoading, { opacity: 1 }, { duration: 0.5 })?.finished,
safeAnimate(uploadImg, { opacity: 0, y: 64 + 8 }, { duration: 0.5 })?.finished
]);
await sleep(250);
await Promise.all([
safeAnimate(uploadText, { opacity: 0 }, { duration: 0.5 })?.finished,
safeAnimate(uploadLoading, { opacity: 1 }, { duration: 0.5 })?.finished,
safeAnimate(uploadImg, { opacity: 0, y: 64 + 8 }, { duration: 0.5 })
?.finished,
]);
await sleep(250);
await safeAnimate(upload, { opacity: 0, y: 48 }, { duration: 0.5 })?.finished;
await safeAnimate(upload, { opacity: 0, y: 48 }, { duration: 0.5 })?.finished;
update((p) => ({
...p,
files: [
...p.files,
{
src: '/images/animations/storage-2.png',
filename: 'Vector.svg',
type: 'vector/svg',
size: '1.5 KB'
}
]
}));
update((p) => ({
...p,
files: [
...p.files,
{
src: "/images/animations/storage-2.png",
filename: "Vector.svg",
type: "vector/svg",
size: "1.5 KB",
},
],
}));
await sleep(250);
await sleep(250);
update((p) => ({
...p,
files: [
...p.files,
{
src: '/images/animations/storage-3.png',
filename: 'img2.webp',
type: 'image/webp',
size: '3.2 MB'
}
]
}));
update((p) => ({
...p,
files: [
...p.files,
{
src: "/images/animations/storage-3.png",
filename: "img2.webp",
type: "image/webp",
size: "3.2 MB",
},
],
}));
};
export const storageController = {
execute,
state
execute,
state,
};
export const Storage = {
Phone,
Box,
Code
Phone,
Box,
Code,
};

View File

@@ -1,284 +1,288 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { storageController } from '.';
import { elId } from '../Products.svelte';
import TaskCheckbox from '../TaskCheckbox.svelte';
import { databasesController } from '../databases';
import { fly } from "svelte/transition";
import { storageController } from ".";
import { elId } from "../Products.svelte";
import TaskCheckbox from "../TaskCheckbox.svelte";
import { databasesController } from "../databases";
const { state: dbState } = databasesController;
const fixedTasks = $dbState.tasks;
const { state: dbState } = databasesController;
const fixedTasks = $dbState.tasks;
const { state } = storageController;
const { state } = storageController;
</script>
<div data-theme-ignore class="inner-phone light">
<div class="header">
<p class="title">Your tasks</p>
<span class="icon-menu" aria-label="menu" />
</div>
<div class="header">
<p class="title">Your tasks</p>
<span class="icon-menu" aria-label="menu" />
</div>
<div class="date">Today</div>
<div class="tasks">
{#each fixedTasks as task (task.id)}
<div class="task" data-checked={task.checked ? '' : undefined} in:fly={{ x: -16 }}>
<TaskCheckbox bind:checked={task.checked} />
<span class="title">{task.title}</span>
</div>
<div class="date">Today</div>
<div class="tasks">
{#each fixedTasks as task (task.id)}
<div
class="task"
data-checked={task.checked ? "" : undefined}
in:fly={{ x: -16 }}
>
<TaskCheckbox bind:checked={task.checked} />
<span class="title">{task.title}</span>
</div>
{/each}
</div>
<button class="add-btn">
<span class="web-icon-plus" />
</button>
<div class="overlay" id="overlay-{$elId}">
<div class="drawer" id="drawer-{$elId}">
<p class="title">Edit images for website</p>
<p class="subtitle">Edit the attached images to use in the website</p>
<div class="upload" id="upload-btn-{$elId}">Upload media...</div>
<div class="images">
{#each $state.files.slice(1) as file}
<img src={file.src} alt="" transition:fly={{ x: 16 }} />
{/each}
</div>
</div>
<button class="add-btn">
<span class="web-icon-plus" />
</button>
<div class="overlay" id="overlay-{$elId}">
<div class="drawer" id="drawer-{$elId}">
<p class="title">Edit images for website</p>
<p class="subtitle">Edit the attached images to use in the website</p>
<div class="upload" id="upload-btn-{$elId}">Upload media...</div>
<div class="images">
{#each $state.files.slice(1) as file}
<img src={file.src} alt="" transition:fly={{ x: 16 }} />
{/each}
</div>
</div>
</div>
</div>
</div>
<div class="upload-media" id="upload-{$elId}">
<p class="title">Upload media</p>
<div class="drop-zone">
<span id="upload-text-{$elId}"> Drop media here </span>
<div class="loading-overlay" id="upload-loading-{$elId}">
<div class="loader" />
</div>
<p class="title">Upload media</p>
<div class="drop-zone">
<span id="upload-text-{$elId}"> Drop media here </span>
<div class="loading-overlay" id="upload-loading-{$elId}">
<div class="loader" />
</div>
<img id="upload-img-{$elId}" src="/images/animations/storage-2.png" alt="" />
</div>
<img id="upload-img-{$elId}" src="/images/animations/storage-2.png" alt="" />
</div>
<style lang="scss">
.inner-phone {
padding-block: 3rem;
padding-inline: 1rem;
.inner-phone {
padding-block: 3rem;
padding-inline: 1rem;
color: rgba(67, 67, 71, 1);
text-align: left;
color: rgba(67, 67, 71, 1);
text-align: left;
position: relative;
height: 100%;
overflow: visible;
position: relative;
height: 100%;
overflow: visible;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 1rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 1rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
[class*='icon-'] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-500));
}
}
.date {
margin-block-start: 3rem;
color: hsl(var(--web-color-greyscale-600));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 500;
line-height: 1.25rem; /* 166.667% */
}
.tasks {
margin-block-start: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
.task {
display: flex;
align-items: center;
gap: 0.75rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--web-color-greyscale-50));
background: hsl(var(--web-color-white));
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
padding-block: 0.55rem;
padding-inline: 0.88rem;
/* Responsive/SubBody-400 */
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.00394rem;
transition: opacity 200ms ease;
&[data-checked] {
opacity: 0.6;
}
}
}
.add-btn {
position: absolute;
right: 1rem;
bottom: 2.5rem;
width: 2.5rem;
height: 2.5rem;
flex-shrink: 0;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
background-color: rgba(124, 103, 254, 1);
color: rgba(237, 237, 240, 1);
font-size: 1.5rem;
display: grid;
place-items: center;
border-radius: 100%;
}
[class*="icon-"] {
font-size: 1.25rem;
color: hsl(var(--web-color-greyscale-500));
}
}
.overlay {
opacity: 0;
.date {
margin-block-start: 3rem;
color: hsl(var(--web-color-greyscale-600));
font-family: Inter;
font-size: 0.75rem;
font-style: normal;
font-weight: 500;
line-height: 1.25rem; /* 166.667% */
}
.tasks {
margin-block-start: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
.task {
display: flex;
align-items: center;
gap: 0.75rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--web-color-greyscale-50));
background: hsl(var(--web-color-white));
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
padding-block: 0.55rem;
padding-inline: 0.88rem;
/* Responsive/SubBody-400 */
font-family: Inter;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.375rem; /* 157.143% */
letter-spacing: -0.00394rem;
transition: opacity 200ms ease;
&[data-checked] {
opacity: 0.6;
}
}
}
.add-btn {
position: absolute;
right: 1rem;
bottom: 2.5rem;
width: 2.5rem;
height: 2.5rem;
flex-shrink: 0;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
background-color: rgba(124, 103, 254, 1);
color: rgba(237, 237, 240, 1);
font-size: 1.5rem;
display: grid;
place-items: center;
border-radius: 100%;
}
}
.overlay {
opacity: 0;
position: absolute;
inset: 0;
overflow: hidden;
background: rgba(0, 0, 0, 0.32);
border-radius: 2rem;
.drawer {
position: absolute;
bottom: 0;
height: 60%;
opacity: 0;
background-color: white;
border-radius: 0.88463rem 0.88463rem 2rem 2rem;
padding: 1rem;
.title {
color: #434347;
font-family: Inter;
font-size: 0.88463rem;
font-style: normal;
font-weight: 600;
line-height: 1.21638rem; /* 137.5% */
letter-spacing: -0.01238rem;
}
.subtitle {
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
font-family: Inter;
font-size: 0.77406rem;
font-style: normal;
font-weight: 400;
line-height: 1.10575rem; /* 142.857% */
letter-spacing: -0.01081rem;
margin-block-start: 0.2rem;
}
.upload {
display: flex;
padding: 0.44231rem 0.66344rem;
justify-content: center;
align-items: center;
align-self: stretch;
border-radius: 0.66344rem;
border: 1px dashed #d9d9d9;
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
font-family: Inter;
font-size: 0.77406rem;
font-style: normal;
font-weight: 400;
line-height: 1.10575rem; /* 142.857% */
letter-spacing: -0.01081rem;
margin-block-start: 2rem;
}
}
}
.upload-media {
position: absolute;
right: calc(0% - 24px);
bottom: 8rem;
background-color: white;
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
border-radius: 1rem;
padding: 0.75rem;
opacity: 0;
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 0.85rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
.drop-zone {
display: grid;
place-items: center;
border: 0.885px dashed #d9d9d9;
border-radius: 0.5rem;
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
padding: 2rem 1.25rem;
margin-block-start: 0.5rem;
font-size: 0.65rem;
font-family: Inter;
position: relative;
overflow: hidden;
.loading-overlay {
position: absolute;
inset: 0;
overflow: hidden;
background: rgba(0, 0, 0, 0.32);
border-radius: 2rem;
.drawer {
position: absolute;
bottom: 0;
height: 60%;
opacity: 0;
background-color: white;
border-radius: 0.88463rem 0.88463rem 2rem 2rem;
padding: 1rem;
.title {
color: #434347;
font-family: Inter;
font-size: 0.88463rem;
font-style: normal;
font-weight: 600;
line-height: 1.21638rem; /* 137.5% */
letter-spacing: -0.01238rem;
}
.subtitle {
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
font-family: Inter;
font-size: 0.77406rem;
font-style: normal;
font-weight: 400;
line-height: 1.10575rem; /* 142.857% */
letter-spacing: -0.01081rem;
margin-block-start: 0.2rem;
}
.upload {
display: flex;
padding: 0.44231rem 0.66344rem;
justify-content: center;
align-items: center;
align-self: stretch;
border-radius: 0.66344rem;
border: 1px dashed #d9d9d9;
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
font-family: Inter;
font-size: 0.77406rem;
font-style: normal;
font-weight: 400;
line-height: 1.10575rem; /* 142.857% */
letter-spacing: -0.01081rem;
margin-block-start: 2rem;
}
}
}
.upload-media {
position: absolute;
right: calc(0% - 24px);
bottom: 8rem;
background-color: white;
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
border-radius: 1rem;
padding: 0.75rem;
opacity: 0;
z-index: 100;
.title {
color: var(--color-greyscale-800, #2d2d31);
font-family: Inter;
font-size: 0.85rem;
font-style: normal;
font-weight: 600;
line-height: 1.375rem; /* 137.5% */
letter-spacing: -0.014rem;
}
.drop-zone {
display: grid;
place-items: center;
border: 0.885px dashed #d9d9d9;
border-radius: 0.5rem;
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
padding: 2rem 1.25rem;
margin-block-start: 0.5rem;
font-size: 0.65rem;
font-family: Inter;
position: relative;
overflow: hidden;
.loading-overlay {
position: absolute;
inset: 0;
opacity: 0;
z-index: 100;
display: grid;
place-items: center;
}
}
img {
position: absolute;
left: 0;
top: 0;
opacity: 0;
}
display: grid;
place-items: center;
}
}
.images {
display: flex;
margin-block-start: 0.5rem;
gap: 0.5rem;
img {
position: absolute;
left: 0;
top: 0;
opacity: 0;
}
}
.images {
display: flex;
margin-block-start: 0.5rem;
gap: 0.5rem;
}
</style>

View File

@@ -1,186 +1,198 @@
import type { Action } from 'svelte/action';
import type { Action } from "svelte/action";
import {
animate as motionAnimate,
type ElementOrSelector,
type MotionKeyframesDefinition,
type AnimationOptionsWithOverrides,
animate
} from 'motion';
animate as motionAnimate,
type ElementOrSelector,
type MotionKeyframesDefinition,
type AnimationOptionsWithOverrides,
animate,
} from "motion";
export function animation(
elementOrSelector: ElementOrSelector,
keyframes: MotionKeyframesDefinition,
options?: AnimationOptionsWithOverrides
elementOrSelector: ElementOrSelector,
keyframes: MotionKeyframesDefinition,
options?: AnimationOptionsWithOverrides,
) {
const play = () => {
const played = motionAnimate(elementOrSelector, keyframes, options);
return played;
};
const play = () => {
const played = motionAnimate(elementOrSelector, keyframes, options);
return played;
};
const reverse = () => {
const reversedKeyframes = Object.fromEntries(
Object.entries(keyframes).map(([key, keyframe]) => {
return [key, Array.isArray(keyframe) ? [...keyframe].reverse() : keyframe];
})
) as typeof keyframes;
const reversed = motionAnimate(elementOrSelector, reversedKeyframes, options);
return reversed;
};
const reverse = () => {
const reversedKeyframes = Object.fromEntries(
Object.entries(keyframes).map(([key, keyframe]) => {
return [
key,
Array.isArray(keyframe) ? [...keyframe].reverse() : keyframe,
];
}),
) as typeof keyframes;
const reversed = motionAnimate(
elementOrSelector,
reversedKeyframes,
options,
);
return reversed;
};
return {
play,
reverse
};
return {
play,
reverse,
};
}
export type Animation = ReturnType<typeof animation>;
export const safeAnimate = (
elementOrSelector: ElementOrSelector,
keyframes: MotionKeyframesDefinition,
options?: AnimationOptionsWithOverrides
elementOrSelector: ElementOrSelector,
keyframes: MotionKeyframesDefinition,
options?: AnimationOptionsWithOverrides,
) => {
try {
return animate(elementOrSelector, keyframes, options);
} catch {
// do nothing lol
}
try {
return animate(elementOrSelector, keyframes, options);
} catch {
// do nothing lol
}
};
type Unsubscriber = () => void;
type PreviousScroll = 'before' | 'after' | undefined;
type PreviousScroll = "before" | "after" | undefined;
type ScrollCallbackState = {
previous?: PreviousScroll;
unsubscribe?: Unsubscriber;
executedCount: number;
previous?: PreviousScroll;
unsubscribe?: Unsubscriber;
executedCount: number;
};
export type ScrollCallback = {
percentage: number;
whenAfter?: (args: Omit<ScrollCallbackState, 'unsubscribe'>) => Unsubscriber | void;
percentage: number;
whenAfter?: (
args: Omit<ScrollCallbackState, "unsubscribe">,
) => Unsubscriber | void;
};
export function createScrollHandler(callbacks: ScrollCallback[]) {
const states: ScrollCallbackState[] = callbacks.map(() => ({ executedCount: 0 }));
const states: ScrollCallbackState[] = callbacks.map(() => ({
executedCount: 0,
}));
const handler = function (scrollPercentage: number) {
callbacks.forEach((callback, i) => {
const { percentage, whenAfter } = callback;
const { previous, unsubscribe, executedCount } = states[i];
const handler = function (scrollPercentage: number) {
callbacks.forEach((callback, i) => {
const { percentage, whenAfter } = callback;
const { previous, unsubscribe, executedCount } = states[i];
if (scrollPercentage >= percentage && previous !== 'after') {
// Execute whenAfter
states[i].unsubscribe = whenAfter?.({ previous, executedCount }) ?? undefined;
states[i].previous = 'after';
if (whenAfter) {
states[i].executedCount++;
}
} else if (scrollPercentage < percentage && previous === 'after') {
unsubscribe?.();
states[i].unsubscribe = undefined;
states[i].previous = 'before';
}
});
};
if (scrollPercentage >= percentage && previous !== "after") {
// Execute whenAfter
states[i].unsubscribe =
whenAfter?.({ previous, executedCount }) ?? undefined;
states[i].previous = "after";
if (whenAfter) {
states[i].executedCount++;
}
} else if (scrollPercentage < percentage && previous === "after") {
unsubscribe?.();
states[i].unsubscribe = undefined;
states[i].previous = "before";
}
});
};
handler.reset = () => {
states.forEach((state) => {
// state.unsubscribe?.();
state.unsubscribe = undefined;
state.previous = undefined;
state.executedCount = 0;
});
};
handler.reset = () => {
states.forEach((state) => {
// state.unsubscribe?.();
state.unsubscribe = undefined;
state.previous = undefined;
state.executedCount = 0;
});
};
return handler;
return handler;
}
export type ScrollInfo = {
percentage: number;
traversed: number;
remaning: number;
percentage: number;
traversed: number;
remaning: number;
};
export const scroll: Action<
HTMLElement,
undefined,
{
'on:web-scroll': (e: CustomEvent<ScrollInfo>) => void;
'on:web-resize': (e: CustomEvent<ScrollInfo>) => void;
}
HTMLElement,
undefined,
{
"on:web-scroll": (e: CustomEvent<ScrollInfo>) => void;
"on:web-resize": (e: CustomEvent<ScrollInfo>) => void;
}
> = (node) => {
function getScrollInfo(): ScrollInfo {
const { top, height } = node.getBoundingClientRect();
const { innerHeight } = window;
function getScrollInfo(): ScrollInfo {
const { top, height } = node.getBoundingClientRect();
const { innerHeight } = window;
const scrollHeight = height - innerHeight;
const scrollPercentage = (-1 * top) / scrollHeight;
const scrollHeight = height - innerHeight;
const scrollPercentage = (-1 * top) / scrollHeight;
const traversed = scrollPercentage * scrollHeight;
const remaning = scrollHeight - traversed;
return {
percentage: scrollPercentage,
traversed,
remaning
};
}
const createHandler = (eventName: 'web-scroll' | 'web-resize') => {
return () => {
node.dispatchEvent(
new CustomEvent<ScrollInfo>(eventName, {
detail: getScrollInfo()
})
);
};
};
const handleScroll = createHandler('web-scroll');
const handleResize = createHandler('web-resize');
handleScroll();
handleResize();
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleResize);
const traversed = scrollPercentage * scrollHeight;
const remaning = scrollHeight - traversed;
return {
destroy() {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleResize);
}
percentage: scrollPercentage,
traversed,
remaning,
};
}
const createHandler = (eventName: "web-scroll" | "web-resize") => {
return () => {
node.dispatchEvent(
new CustomEvent<ScrollInfo>(eventName, {
detail: getScrollInfo(),
}),
);
};
};
const handleScroll = createHandler("web-scroll");
const handleResize = createHandler("web-resize");
handleScroll();
handleResize();
window.addEventListener("scroll", handleScroll);
window.addEventListener("resize", handleResize);
return {
destroy() {
window.removeEventListener("scroll", handleScroll);
window.removeEventListener("resize", handleResize);
},
};
};
type TimelineEvent = {
at: number;
callback: () => void;
at: number;
callback: () => void;
};
export function createTimeline(events: TimelineEvent[]) {
let timeoutIds: NodeJS.Timeout[] = [];
let timeoutIds: NodeJS.Timeout[] = [];
const play = () => {
events.forEach((event) => {
const timeoutId = setTimeout(event.callback, event.at);
timeoutIds.push(timeoutId);
});
};
const play = () => {
events.forEach((event) => {
const timeoutId = setTimeout(event.callback, event.at);
timeoutIds.push(timeoutId);
});
};
const cancel = () => {
timeoutIds.forEach(clearTimeout);
timeoutIds = [];
};
const cancel = () => {
timeoutIds.forEach(clearTimeout);
timeoutIds = [];
};
return { play, cancel };
return { play, cancel };
}
type ProgressEvent = {
percentage: number;
callback: () => void;
percentage: number;
callback: () => void;
};
/**
@@ -191,54 +203,56 @@ type ProgressEvent = {
* handler(0.45) // will execute the event with percentage 0.4.
*/
export function createProgressSequence(events: ProgressEvent[]) {
// Sort from highest to lowest percentage
const sortedEvents = [...events].sort((a, b) => b.percentage - a.percentage);
// Sort from highest to lowest percentage
const sortedEvents = [...events].sort((a, b) => b.percentage - a.percentage);
let lastEventIdx = -1;
let lastEventIdx = -1;
const handler = (percentage: number) => {
const idx = sortedEvents.findIndex((event) => event.percentage <= percentage);
if (idx === lastEventIdx) {
return;
}
const event = sortedEvents[idx];
event?.callback();
lastEventIdx = idx;
};
const handler = (percentage: number) => {
const idx = sortedEvents.findIndex(
(event) => event.percentage <= percentage,
);
if (idx === lastEventIdx) {
return;
}
const event = sortedEvents[idx];
event?.callback();
lastEventIdx = idx;
};
handler.resetLastEventIdx = () => {
lastEventIdx = -1;
};
handler.resetLastEventIdx = () => {
lastEventIdx = -1;
};
return handler;
return handler;
}
export type ProgressSequence = ReturnType<typeof createProgressSequence>;
export function write(text: string, cb: (v: string) => void, duration = 500) {
const step = duration / text.length;
let i = 0;
return new Promise((resolve) => {
const interval = setInterval(() => {
cb(text.slice(0, ++i));
if (i === text.length) {
clearInterval(interval);
resolve(undefined);
}
}, step);
});
const step = duration / text.length;
let i = 0;
return new Promise((resolve) => {
const interval = setInterval(() => {
cb(text.slice(0, ++i));
if (i === text.length) {
clearInterval(interval);
resolve(undefined);
}
}, step);
});
}
export function sleep(duration: number) {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
}
export function getInitials(name: string) {
return name
.split(' ')
.map((word) => word?.[0]?.toUpperCase() ?? '')
.join('')
.slice(0, 2);
return name
.split(" ")
.map((word) => word?.[0]?.toUpperCase() ?? "")
.join("")
.slice(0, 2);
}

View File

@@ -1,9 +1,14 @@
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID } from '$env/static/public';
import { Client, Databases, Functions } from '@appwrite.io/console';
import {
PUBLIC_APPWRITE_ENDPOINT,
PUBLIC_APPWRITE_PROJECT_ID,
} from "$env/static/public";
import { Client, Databases, Functions } from "@appwrite.io/console";
export const client = new Client();
client.setEndpoint(PUBLIC_APPWRITE_ENDPOINT).setProject(PUBLIC_APPWRITE_PROJECT_ID);
client
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
.setProject(PUBLIC_APPWRITE_PROJECT_ID);
export const databases = new Databases(client);
export const functions = new Functions(client);

View File

@@ -1,14 +1,17 @@
import { APPWRITE_API_KEY_INIT } from '$env/static/private';
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_INIT_ID } from '$env/static/public';
import { Account, Client, Databases } from '@appwrite.io/console';
import { APPWRITE_API_KEY_INIT } from "$env/static/private";
import {
PUBLIC_APPWRITE_ENDPOINT,
PUBLIC_APPWRITE_PROJECT_INIT_ID,
} from "$env/static/public";
import { Account, Client, Databases } from "@appwrite.io/console";
const clientServer = new Client();
clientServer
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
.setProject(PUBLIC_APPWRITE_PROJECT_INIT_ID)
.setKey(APPWRITE_API_KEY_INIT);
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
.setProject(PUBLIC_APPWRITE_PROJECT_INIT_ID)
.setKey(APPWRITE_API_KEY_INIT);
export const appwriteInitServer = {
account: new Account(clientServer),
databases: new Databases(clientServer)
account: new Account(clientServer),
databases: new Databases(clientServer),
};

View File

@@ -1,10 +1,15 @@
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_INIT_ID } from '$env/static/public';
import { Client, Account } from '@appwrite.io/console';
import {
PUBLIC_APPWRITE_ENDPOINT,
PUBLIC_APPWRITE_PROJECT_INIT_ID,
} from "$env/static/public";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
client.setEndpoint(PUBLIC_APPWRITE_ENDPOINT).setProject(PUBLIC_APPWRITE_PROJECT_INIT_ID);
client
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
.setProject(PUBLIC_APPWRITE_PROJECT_INIT_ID);
export const appwriteInit = {
client,
account: new Account(client)
client,
account: new Account(client),
};

View File

@@ -1,4 +1,4 @@
import Root from './Root.svelte';
import Item from './Item.svelte';
import Root from "./Root.svelte";
import Item from "./Item.svelte";
export { Root as Accordion, Item as AccordionItem };

View File

@@ -1,24 +1,24 @@
<script lang="ts">
import { browser } from '$app/environment';
import { BANNER_KEY } from '$lib/constants';
import { browser } from "$app/environment";
import { BANNER_KEY } from "$lib/constants";
const hideTopBanner = () => {
document.body.dataset.bannerHidden = '';
localStorage.setItem(BANNER_KEY, 'true');
};
const hideTopBanner = () => {
document.body.dataset.bannerHidden = "";
localStorage.setItem(BANNER_KEY, "true");
};
</script>
<div class="web-top-banner">
<div class="web-top-banner-content web-u-color-text-primary">
<slot />
{#if browser}
<button
class="web-top-banner-button"
aria-label="close discord message"
on:click={hideTopBanner}
>
<span class="web-icon-close" aria-hidden="true" />
</button>
{/if}
</div>
<div class="web-top-banner-content web-u-color-text-primary">
<slot />
{#if browser}
<button
class="web-top-banner-button"
aria-label="close discord message"
on:click={hideTopBanner}
>
<span class="web-icon-close" aria-hidden="true" />
</button>
{/if}
</div>
</div>

View File

@@ -1,24 +1,24 @@
<script lang="ts">
import { DropdownCtx } from './DropdownMenu.svelte';
import { DropdownCtx } from "./DropdownMenu.svelte";
export let checked = false;
export let checked = false;
const {
builders: { createCheckboxItem }
} = DropdownCtx.get();
const {
builders: { createCheckboxItem },
} = DropdownCtx.get();
const {
elements: { checkboxItem },
states: { checked: localChecked }
} = createCheckboxItem({
defaultChecked: checked,
onCheckedChange({ next }) {
checked = !!next;
return next;
}
});
const {
elements: { checkboxItem },
states: { checked: localChecked },
} = createCheckboxItem({
defaultChecked: checked,
onCheckedChange({ next }) {
checked = !!next;
return next;
},
});
$: localChecked.set(checked);
$: localChecked.set(checked);
</script>
<slot checkboxItem={$checkboxItem} />

View File

@@ -1,33 +1,33 @@
<script lang="ts" context="module">
const ctxKey = Symbol('dropdown');
const ctxKey = Symbol("dropdown");
export const DropdownCtx = {
get() {
return getContext<DropdownMenu>(ctxKey);
},
set(ctx: DropdownMenu) {
setContext(ctxKey, ctx);
}
};
export const DropdownCtx = {
get() {
return getContext<DropdownMenu>(ctxKey);
},
set(ctx: DropdownMenu) {
setContext(ctxKey, ctx);
},
};
</script>
<script lang="ts">
import { createDropdownMenu, type DropdownMenu } from '@melt-ui/svelte';
import { getContext, setContext } from 'svelte';
import { createDropdownMenu, type DropdownMenu } from "@melt-ui/svelte";
import { getContext, setContext } from "svelte";
const dropdown = createDropdownMenu({
forceVisible: true,
positioning: {
placement: 'bottom-start'
}
});
const dropdown = createDropdownMenu({
forceVisible: true,
positioning: {
placement: "bottom-start",
},
});
DropdownCtx.set(dropdown);
DropdownCtx.set(dropdown);
const {
elements: { menu, trigger },
states: { open }
} = dropdown;
const {
elements: { menu, trigger },
states: { open },
} = dropdown;
</script>
<slot menu={$menu} trigger={$trigger} open={$open} />

View File

@@ -1,51 +1,55 @@
<script lang="ts">
export let src: string;
export let alt = '';
export let size: number;
export let src: string;
export let alt = "";
export let size: number;
let className = '';
export { className as class };
let className = "";
export { className as class };
</script>
<div class="head-wrapper {className}" style:width="{size}px" style:height="{size}px">
<img {src} {alt} />
<div
class="head-wrapper {className}"
style:width="{size}px"
style:height="{size}px"
>
<img {src} {alt} />
</div>
<style lang="scss">
@use '$scss/abstract/mixins/border-gradient' as gradients;
@use "$scss/abstract/mixins/border-gradient" as gradients;
.head-wrapper {
@include gradients.border-gradient;
--m-border-radius: 50%;
--m-border-gradient-before: linear-gradient(
135.1deg,
rgba(255, 255, 255, 0.08) 10.1%,
rgba(255, 255, 255, 0) 120.69%
);
.head-wrapper {
@include gradients.border-gradient;
--m-border-radius: 50%;
--m-border-gradient-before: linear-gradient(
135.1deg,
rgba(255, 255, 255, 0.08) 10.1%,
rgba(255, 255, 255, 0) 120.69%
);
display: inline-block;
position: var(--position, absolute);
top: var(--top);
left: var(--left);
bottom: var(--bottom);
right: var(--right);
display: inline-block;
position: var(--position, absolute);
top: var(--top);
left: var(--left);
bottom: var(--bottom);
right: var(--right);
background: linear-gradient(
132.35deg,
rgba(255, 255, 255, 0.08) 12.05%,
rgba(255, 255, 255, 0) 161.63%
);
box-shadow: 0px 5.35px 10.7px 0px rgba(0, 0, 0, 0.02);
background: linear-gradient(
132.35deg,
rgba(255, 255, 255, 0.08) 12.05%,
rgba(255, 255, 255, 0) 161.63%
);
box-shadow: 0px 5.35px 10.7px 0px rgba(0, 0, 0, 0.02);
&.isRelative {
position: relative;
}
&.isRelative {
position: relative;
}
}
img {
border-radius: 50%;
padding: 10%;
aspect-ratio: 1 / 1;
object-fit: cover;
}
img {
border-radius: 50%;
padding: 10%;
aspect-ratio: 1 / 1;
object-fit: cover;
}
</style>

View File

@@ -1,52 +1,52 @@
<script lang="ts">
import { clamp } from '$lib/utils/clamp';
import FloatingHead from './FloatingHead.svelte';
import { clamp } from "$lib/utils/clamp";
import FloatingHead from "./FloatingHead.svelte";
type Head = {
src: string;
display: [number, number, number];
};
type Head = {
src: string;
display: [number, number, number];
};
const headPositions: Array<Head['display']> = [
[120, -25, 40],
[120, 20, -40],
[64, -40, 20],
[64, 25, 35],
[64, 10, 45],
[64, 40, 55],
[64, 40, -20],
[64, 10, -55],
[40, -45, 50],
[40, -40, -40]
];
const headPositions: Array<Head["display"]> = [
[120, -25, 40],
[120, 20, -40],
[64, -40, 20],
[64, 25, 35],
[64, 10, 45],
[64, 40, 55],
[64, 40, -20],
[64, 10, -55],
[40, -45, 50],
[40, -40, -40],
];
export let images: Array<string>;
export let images: Array<string>;
</script>
<div class="absolute web-u-hide-mobile root">
{#each headPositions as [size, top, left], i}
{@const image = clamp(0, images.length - 1, i % images.length)}
<FloatingHead
--top={`calc(50% - ${size / 2}px + ${top}%)`}
--left={`calc(50% - ${size / 2}px + ${left}%)`}
src={images[image]}
{size}
/>
<div style:margin-block-end="0" style:padding="10%">
<img style:border-radius="50%" class="block" alt="" />
</div>
{/each}
<div class="web-u-hide-mobile root absolute">
{#each headPositions as [size, top, left], i}
{@const image = clamp(0, images.length - 1, i % images.length)}
<FloatingHead
--top={`calc(50% - ${size / 2}px + ${top}%)`}
--left={`calc(50% - ${size / 2}px + ${left}%)`}
src={images[image]}
{size}
/>
<div style:margin-block-end="0" style:padding="10%">
<img style:border-radius="50%" class="block" alt="" />
</div>
{/each}
</div>
<style lang="scss">
.root {
inline-size: 100vw;
block-size: 100%;
top: 0;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
max-inline-size: 1280px;
}
.root {
inline-size: 100vw;
block-size: 100%;
top: 0;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
max-inline-size: 1280px;
}
</style>

View File

@@ -1,147 +1,157 @@
<script lang="ts">
import { createAccordion, melt } from '@melt-ui/svelte';
import { slide } from 'svelte/transition';
import { createAccordion, melt } from "@melt-ui/svelte";
import { slide } from "svelte/transition";
export let noBorder = false;
export let noBorder = false;
const {
elements: { content, heading, item, root, trigger },
helpers: { isSelected }
} = createAccordion({
multiple: true,
forceVisible: true
});
const {
elements: { content, heading, item, root, trigger },
helpers: { isSelected },
} = createAccordion({
multiple: true,
forceVisible: true,
});
const links: Record<string, { label: string; href: string; target?: string; rel?: string }[]> =
{
'Quick starts': [
{ label: 'Web', href: '/docs/quick-starts/web' },
{ label: 'Next.js', href: '/docs/quick-starts/nextjs' },
{ label: 'React', href: '/docs/quick-starts/react' },
{ label: 'Vue.js', href: '/docs/quick-starts/vue' },
{ label: 'Nuxt', href: '/docs/quick-starts/nuxt' },
{ label: 'SvelteKit', href: '/docs/quick-starts/sveltekit' },
{ label: 'Refine', href: '/docs/quick-starts/refine' },
{ label: 'Angular', href: '/docs/quick-starts/angular' },
{ label: 'React Native', href: '/docs/quick-starts/react-native' },
{ label: 'Flutter', href: '/docs/quick-starts/flutter' },
{ label: 'Apple', href: '/docs/quick-starts/apple' },
{ label: 'Android', href: '/docs/quick-starts/android' },
{ label: 'Qwik', href: '/docs/quick-starts/qwik' },
{ label: 'Astro', href: '/docs/quick-starts/astro' },
{ label: 'Solid', href: '/docs/quick-starts/solid' }
],
Products: [
{ label: 'Auth', href: '/docs/products/auth' },
{ label: 'Databases', href: '/docs/products/databases' },
{ label: 'Functions', href: '/docs/products/functions' },
{ label: 'Messaging', href: '/products/messaging' },
{ label: 'Storage', href: '/docs/products/storage' },
{ label: 'Realtime', href: '/docs/apis/realtime' }
],
Learn: [
{ label: 'Docs', href: '/docs' },
{ label: 'Integrations', href: '/integrations' },
{ label: 'Community', href: '/community' },
{ label: 'Init', href: '/init' },
{ label: 'Threads', href: '/threads' },
{ label: 'Blog', href: '/blog' },
{ label: 'Changelog', href: '/changelog' },
{
label: 'Roadmap',
href: 'https://github.com/orgs/appwrite/projects',
target: '_blank',
rel: 'noopener noreferrer'
},
{
label: 'Source code',
href: 'https://github.com/appwrite',
target: '_blank',
rel: 'noopener noreferrer'
}
// {
// label: 'Status',
// href: 'https://appwrite.online',
// target: '_blank',
// rel: 'noopener noreferrer'
// }
],
Programs: [
{ label: 'Heroes', href: '/heroes' },
{ label: 'Startups', href: '/startups' }
],
About: [
{ label: 'Company', href: '/company' },
{ label: 'Pricing', href: '/pricing' },
{
label: 'Careers',
href: 'https://appwrite.careers',
target: '_blank',
rel: 'noopener noreferrer'
},
{
label: 'Store',
href: 'https://appwrite.store',
target: '_blank',
rel: 'noopener noreferrer'
},
{ label: 'Contact us', href: '/contact-us' },
{ label: 'Assets', href: '/assets' }
]
};
const links: Record<
string,
{ label: string; href: string; target?: string; rel?: string }[]
> = {
"Quick starts": [
{ label: "Web", href: "/docs/quick-starts/web" },
{ label: "Next.js", href: "/docs/quick-starts/nextjs" },
{ label: "React", href: "/docs/quick-starts/react" },
{ label: "Vue.js", href: "/docs/quick-starts/vue" },
{ label: "Nuxt", href: "/docs/quick-starts/nuxt" },
{ label: "SvelteKit", href: "/docs/quick-starts/sveltekit" },
{ label: "Refine", href: "/docs/quick-starts/refine" },
{ label: "Angular", href: "/docs/quick-starts/angular" },
{ label: "React Native", href: "/docs/quick-starts/react-native" },
{ label: "Flutter", href: "/docs/quick-starts/flutter" },
{ label: "Apple", href: "/docs/quick-starts/apple" },
{ label: "Android", href: "/docs/quick-starts/android" },
{ label: "Qwik", href: "/docs/quick-starts/qwik" },
{ label: "Astro", href: "/docs/quick-starts/astro" },
{ label: "Solid", href: "/docs/quick-starts/solid" },
],
Products: [
{ label: "Auth", href: "/docs/products/auth" },
{ label: "Databases", href: "/docs/products/databases" },
{ label: "Functions", href: "/docs/products/functions" },
{ label: "Messaging", href: "/products/messaging" },
{ label: "Storage", href: "/docs/products/storage" },
{ label: "Realtime", href: "/docs/apis/realtime" },
],
Learn: [
{ label: "Docs", href: "/docs" },
{ label: "Integrations", href: "/integrations" },
{ label: "Community", href: "/community" },
{ label: "Init", href: "/init" },
{ label: "Threads", href: "/threads" },
{ label: "Blog", href: "/blog" },
{ label: "Changelog", href: "/changelog" },
{
label: "Roadmap",
href: "https://github.com/orgs/appwrite/projects",
target: "_blank",
rel: "noopener noreferrer",
},
{
label: "Source code",
href: "https://github.com/appwrite",
target: "_blank",
rel: "noopener noreferrer",
},
// {
// label: 'Status',
// href: 'https://appwrite.online',
// target: '_blank',
// rel: 'noopener noreferrer'
// }
],
Programs: [
{ label: "Heroes", href: "/heroes" },
{ label: "Startups", href: "/startups" },
],
About: [
{ label: "Company", href: "/company" },
{ label: "Pricing", href: "/pricing" },
{
label: "Careers",
href: "https://appwrite.careers",
target: "_blank",
rel: "noopener noreferrer",
},
{
label: "Store",
href: "https://appwrite.store",
target: "_blank",
rel: "noopener noreferrer",
},
{ label: "Contact us", href: "/contact-us" },
{ label: "Assets", href: "/assets" },
],
};
</script>
<nav
aria-label="Footer"
class="web-footer-nav relative mt-24"
class:web-u-sep-block-start={!noBorder}
aria-label="Footer"
class="web-footer-nav relative mt-24"
class:web-u-sep-block-start={!noBorder}
>
<img class="web-logo" src="/images/logos/appwrite.svg" alt="appwrite" height="24" width="130" />
<ul class="web-footer-nav-main-list" use:melt={$root}>
{#each Object.entries(links) as [title, items]}
<li class="web-footer-nav-main-item web-is-not-mobile">
<h2 class="web-footer-nav-main-title web-is-not-mobile web-caption-500 web-eyebrow">
{title}
</h2>
<ul class="web-footer-nav-secondary-list web-sub-body-400">
{#each items as { href, label, target, rel }}
<li>
<a class="web-link" {href} {target} {rel}>{label}</a>
</li>
{/each}
</ul>
<img
class="web-logo"
src="/images/logos/appwrite.svg"
alt="appwrite"
height="24"
width="130"
/>
<ul class="web-footer-nav-main-list" use:melt={$root}>
{#each Object.entries(links) as [title, items]}
<li class="web-footer-nav-main-item web-is-not-mobile">
<h2
class="web-footer-nav-main-title web-is-not-mobile web-caption-500 web-eyebrow"
>
{title}
</h2>
<ul class="web-footer-nav-secondary-list web-sub-body-400">
{#each items as { href, label, target, rel }}
<li>
<a class="web-link" {href} {target} {rel}>{label}</a>
</li>
<li
class="web-footer-nav-main-item web-is-only-mobile"
use:melt={$item({ value: title })}
>
<h5 use:melt={$heading({ level: 5 })}>
<button
class="web-footer-nav-button web-is-only-mobile"
use:melt={$trigger({ value: title })}
>
<span class="web-caption-500 web-eyebrow">{title}</span>
<span
class="web-icon-chevron-down web-u-transition"
class:web-u-rotate-180={$isSelected(title)}
style:font-size="1rem"
/>
</button>
</h5>
{#if $isSelected(title)}
<ul
class="web-footer-nav-secondary-list web-sub-body-400"
use:melt={$content({ value: title })}
transition:slide={{ duration: 250 }}
>
{#each items as { href, label, target, rel }}
<li>
<a class="web-link" {href} {target} {rel}>{label}</a>
</li>
{/each}
</ul>
{/if}
</li>
{/each}
</ul>
{/each}
</ul>
</li>
<li
class="web-footer-nav-main-item web-is-only-mobile"
use:melt={$item({ value: title })}
>
<h5 use:melt={$heading({ level: 5 })}>
<button
class="web-footer-nav-button web-is-only-mobile"
use:melt={$trigger({ value: title })}
>
<span class="web-caption-500 web-eyebrow">{title}</span>
<span
class="web-icon-chevron-down web-u-transition"
class:web-u-rotate-180={$isSelected(title)}
style:font-size="1rem"
/>
</button>
</h5>
{#if $isSelected(title)}
<ul
class="web-footer-nav-secondary-list web-sub-body-400"
use:melt={$content({ value: title })}
transition:slide={{ duration: 250 }}
>
{#each items as { href, label, target, rel }}
<li>
<a class="web-link" {href} {target} {rel}>{label}</a>
</li>
{/each}
</ul>
{/if}
</li>
{/each}
</ul>
</nav>

View File

@@ -1,254 +1,254 @@
<script lang="ts">
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { page } from "$app/stores";
import { onMount } from "svelte";
let mounted = false;
let mounted = false;
onMount(() => {
mounted = true;
});
onMount(() => {
mounted = true;
});
let groups = [1, 2, 1, 3, 1, 2, 1, 3, 3, 2];
let groups = [1, 2, 1, 3, 1, 2, 1, 3, 3, 2];
const getRandomXValue = () => {
return Math.floor(Math.random() * 120);
};
const getRandomXValue = () => {
return Math.floor(Math.random() * 120);
};
const getRandomWidth = (index: number) => {
const minWidth = 80;
const maxRandomWidth = 125 - minWidth;
const getRandomWidth = (index: number) => {
const minWidth = 80;
const maxRandomWidth = 125 - minWidth;
const seed = index;
const random = Math.sin(seed) * 10000;
const randomNumber = random - Math.floor(random);
const seed = index;
const random = Math.sin(seed) * 10000;
const randomNumber = random - Math.floor(random);
return Math.floor(randomNumber * maxRandomWidth) + minWidth;
};
return Math.floor(randomNumber * maxRandomWidth) + minWidth;
};
const randomDelay = () => Math.floor(Math.random() * 750);
const randomDelay = () => Math.floor(Math.random() * 750);
</script>
<div class="banner" class:hidden={$page.url.pathname.includes('init')}>
<div class="content web-u-color-text-primary">
<div class="headings">
<span style:font-weight="500"
><span
style:font-size="20px"
style:font-family="var(--web-font-family-aeonik-pro)"
style:font-weight="600">init</span
> has started
</span>
<span class="web-u-color-text-secondary">The start of something new</span>
<div class="shadow" />
<div class="banner" class:hidden={$page.url.pathname.includes("init")}>
<div class="content web-u-color-text-primary">
<div class="headings">
<span style:font-weight="500"
><span
style:font-size="20px"
style:font-family="var(--web-font-family-aeonik-pro)"
style:font-weight="600">init</span
> has started
</span>
<span class="web-u-color-text-secondary">The start of something new</span>
<div class="shadow" />
</div>
<a href="/init" rel="noopener noreferrer" class="action">
<span class="web-caption-500">Join now</span>
<span class="web-icon-arrow-right" aria-hidden="true" />
<div class="shadow" />
</a>
</div>
<div class="shine" />
<div class="border" />
<div class="lines">
{#if mounted}
{#each Array.from({ length: groups.length }) as _, i}
<div style:position="relative" class="group">
{#each Array.from({ length: groups[i] }) as _, index}
<div
class="line"
style={`--width:${getRandomWidth(index)}px;--initial-delay:${randomDelay()}ms;left:${getRandomXValue()}px;`}
/>
{/each}
</div>
<a href="/init" rel="noopener noreferrer" class="action">
<span class="web-caption-500">Join now</span>
<span class="web-icon-arrow-right" aria-hidden="true" />
<div class="shadow" />
</a>
</div>
<div class="shine" />
<div class="border" />
<div class="lines">
{#if mounted}
{#each Array.from({ length: groups.length }) as _, i}
<div style:position="relative" class="group">
{#each Array.from({ length: groups[i] }) as _, index}
<div
class="line"
style={`--width:${getRandomWidth(index)}px;--initial-delay:${randomDelay()}ms;left:${getRandomXValue()}px;`}
/>
{/each}
</div>
{/each}
{/if}
</div>
{/each}
{/if}
</div>
</div>
<style lang="scss">
.banner {
--shine: rgba(255, 255, 255, 0.04);
position: relative;
min-height: 76px;
display: flex;
align-items: center;
max-width: 100vw;
overflow: hidden;
border-bottom: 1px solid hsl(var(--web-color-border));
.banner {
--shine: rgba(255, 255, 255, 0.04);
position: relative;
min-height: 76px;
display: flex;
align-items: center;
max-width: 100vw;
overflow: hidden;
border-bottom: 1px solid hsl(var(--web-color-border));
&.hidden {
display: none;
}
.content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
position: relative;
z-index: 10;
padding-inline: clamp(1.25rem, 4vw, 120rem);
.headings {
z-index: 10;
position: relative;
display: flex;
flex-direction: column;
span {
position: relative;
z-index: 10;
}
.shadow {
width: 250px;
height: 76px;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
z-index: 0;
backdrop-filter: blur(6px);
background-color: hsl(var(--web-color-background) / 50%);
mask-composite: intersect;
mask-image: linear-gradient(
to top,
transparent,
rgba(0, 0, 0, 1) 25%,
rgba(0, 0, 0, 1) 75%,
transparent
),
linear-gradient(
to right,
transparent,
rgba(0, 0, 0, 1) 25%,
rgba(0, 0, 0, 1) 75%,
transparent
);
}
}
.action {
position: relative;
z-index: 10;
span {
position: relative;
z-index: 10;
}
.shadow {
width: 200px;
height: 76px;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
z-index: 0;
backdrop-filter: blur(6px);
background-color: hsl(var(--web-color-background) / 50%);
mask-composite: intersect;
mask-image: linear-gradient(
to top,
transparent,
rgba(0, 0, 0, 1) 25%,
rgba(0, 0, 0, 1) 75%,
transparent
),
linear-gradient(
to right,
transparent,
rgba(0, 0, 0, 1) 25%,
rgba(0, 0, 0, 1) 75%,
transparent
);
}
}
}
.shine {
position: absolute;
inset: 0;
background: hsl(var(--web-color-background))
linear-gradient(
-24deg,
rgba(255, 255, 255, 0) 0,
var(--shine) 20%,
rgba(255, 255, 255, 0) 40%,
var(--shine) 50%,
rgba(255, 255, 255, 0) 60%,
var(--shine) 80%,
rgba(255, 255, 255, 0) 100%
);
}
.border {
position: absolute;
left: 0;
right: 0;
height: 1px;
background-color: hsl(var(--web-color-border));
bottom: 0;
}
.lines {
position: absolute;
inset: 0;
z-index: 0;
margin: 0 auto;
opacity: 0.6;
display: flex;
justify-content: space-between;
gap: 16px;
padding: 16px 0;
.group {
display: flex;
flex-direction: column;
gap: 16px;
position: relative;
justify-content: center;
}
--gradient: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%,
rgba(255, 255, 255, 1) 100%
);
--height: 1px;
--starting-position: -80vw;
--duration: 1s;
.line {
position: relative;
height: var(--height);
width: var(--width);
z-index: 20;
@keyframes line {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
&::after {
content: '';
display: block;
position: absolute;
transform-origin: left;
width: var(--width);
height: var(--height);
background: var(--gradient);
border-radius: 8px;
transform: scaleX(0);
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8));
animation: line var(--duration) ease forwards;
animation-delay: var(--initial-delay);
}
}
}
&.hidden {
display: none;
}
.content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
position: relative;
z-index: 10;
padding-inline: clamp(1.25rem, 4vw, 120rem);
.headings {
z-index: 10;
position: relative;
display: flex;
flex-direction: column;
span {
position: relative;
z-index: 10;
}
.shadow {
width: 250px;
height: 76px;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
z-index: 0;
backdrop-filter: blur(6px);
background-color: hsl(var(--web-color-background) / 50%);
mask-composite: intersect;
mask-image: linear-gradient(
to top,
transparent,
rgba(0, 0, 0, 1) 25%,
rgba(0, 0, 0, 1) 75%,
transparent
),
linear-gradient(
to right,
transparent,
rgba(0, 0, 0, 1) 25%,
rgba(0, 0, 0, 1) 75%,
transparent
);
}
}
.action {
position: relative;
z-index: 10;
span {
position: relative;
z-index: 10;
}
.shadow {
width: 200px;
height: 76px;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
z-index: 0;
backdrop-filter: blur(6px);
background-color: hsl(var(--web-color-background) / 50%);
mask-composite: intersect;
mask-image: linear-gradient(
to top,
transparent,
rgba(0, 0, 0, 1) 25%,
rgba(0, 0, 0, 1) 75%,
transparent
),
linear-gradient(
to right,
transparent,
rgba(0, 0, 0, 1) 25%,
rgba(0, 0, 0, 1) 75%,
transparent
);
}
}
}
.shine {
position: absolute;
inset: 0;
background: hsl(var(--web-color-background))
linear-gradient(
-24deg,
rgba(255, 255, 255, 0) 0,
var(--shine) 20%,
rgba(255, 255, 255, 0) 40%,
var(--shine) 50%,
rgba(255, 255, 255, 0) 60%,
var(--shine) 80%,
rgba(255, 255, 255, 0) 100%
);
}
.border {
position: absolute;
left: 0;
right: 0;
height: 1px;
background-color: hsl(var(--web-color-border));
bottom: 0;
}
.lines {
position: absolute;
inset: 0;
z-index: 0;
margin: 0 auto;
opacity: 0.6;
display: flex;
justify-content: space-between;
gap: 16px;
padding: 16px 0;
.group {
display: flex;
flex-direction: column;
gap: 16px;
position: relative;
justify-content: center;
}
--gradient: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%,
rgba(255, 255, 255, 1) 100%
);
--height: 1px;
--starting-position: -80vw;
--duration: 1s;
.line {
position: relative;
height: var(--height);
width: var(--width);
z-index: 20;
@keyframes line {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
&::after {
content: "";
display: block;
position: absolute;
transform-origin: left;
width: var(--width);
height: var(--height);
background: var(--gradient);
border-radius: 8px;
transform: scaleX(0);
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8));
animation: line var(--duration) ease forwards;
animation-delay: var(--initial-delay);
}
}
}
}
</style>

View File

@@ -1,15 +1,15 @@
<script lang="ts">
export let metric: string;
export let description: string;
export let metric: string;
export let description: string;
</script>
<div class="web-card is-normal has-border-gradient">
<div class="web-title web-u-color-text-primary">{metric}</div>
<div class="web-description">{description}</div>
<div class="web-title web-u-color-text-primary">{metric}</div>
<div class="web-description">{description}</div>
</div>
<style lang="scss">
.web-card {
padding: 1.25rem;
}
.web-card {
padding: 1.25rem;
}
</style>

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { buildOpenGraphImage } from '$lib/utils/metadata';
import { buildOpenGraphImage } from "$lib/utils/metadata";
export let title: string;
export let description: string;
const ogImage = buildOpenGraphImage(title, description);
export let title: string;
export let description: string;
const ogImage = buildOpenGraphImage(title, description);
</script>
<meta property="og:image" content={ogImage} />

View File

@@ -1,84 +1,89 @@
<script lang="ts">
import Tooltip from '$lib/components/Tooltip.svelte';
import { themeInUse } from '$routes/+layout.svelte';
import Tooltip from "$lib/components/Tooltip.svelte";
import { themeInUse } from "$routes/+layout.svelte";
$: platforms = [
{
name: 'Flutter',
href: '/docs/quick-starts/flutter',
image: `/images/platforms/${$themeInUse}/flutter.svg`
},
{
name: 'Next',
href: '/docs/quick-starts/nextjs',
image: `/images/platforms/${$themeInUse}/nextjs.svg`
},
{
name: 'React',
href: '/docs/quick-starts/react',
image: `/images/platforms/${$themeInUse}/react.svg`
},
{
name: 'Svelte',
href: '/docs/quick-starts/sveltekit',
image: `/images/platforms/${$themeInUse}/svelte.svg`
},
{
name: 'Nuxt',
href: '/docs/quick-starts/nuxt',
image: `/images/platforms/${$themeInUse}/nuxt.svg`
},
{
name: 'Vue',
href: '/docs/quick-starts/vue',
image: `/images/platforms/${$themeInUse}/vue.svg`
},
{
name: 'Angular',
href: '/docs/quick-starts/angular',
image: `/images/platforms/${$themeInUse}/angular.svg`
},
{
name: 'Refine',
href: '/docs/quick-starts/refine',
image: `/images/platforms/${$themeInUse}/refine.svg`
},
{
name: 'Apple',
href: '/docs/quick-starts/apple',
image: `/images/platforms/${$themeInUse}/apple.svg`
},
{
name: 'Android',
href: '/docs/quick-starts/android',
image: `/images/platforms/${$themeInUse}/android.svg`
},
{
name: 'React Native',
href: '/docs/quick-starts/react-native',
image: `/images/platforms/${$themeInUse}/react-native.svg`
}
] as Array<{
name: string;
href: string;
image: string;
}>;
$: platforms = [
{
name: "Flutter",
href: "/docs/quick-starts/flutter",
image: `/images/platforms/${$themeInUse}/flutter.svg`,
},
{
name: "Next",
href: "/docs/quick-starts/nextjs",
image: `/images/platforms/${$themeInUse}/nextjs.svg`,
},
{
name: "React",
href: "/docs/quick-starts/react",
image: `/images/platforms/${$themeInUse}/react.svg`,
},
{
name: "Svelte",
href: "/docs/quick-starts/sveltekit",
image: `/images/platforms/${$themeInUse}/svelte.svg`,
},
{
name: "Nuxt",
href: "/docs/quick-starts/nuxt",
image: `/images/platforms/${$themeInUse}/nuxt.svg`,
},
{
name: "Vue",
href: "/docs/quick-starts/vue",
image: `/images/platforms/${$themeInUse}/vue.svg`,
},
{
name: "Angular",
href: "/docs/quick-starts/angular",
image: `/images/platforms/${$themeInUse}/angular.svg`,
},
{
name: "Refine",
href: "/docs/quick-starts/refine",
image: `/images/platforms/${$themeInUse}/refine.svg`,
},
{
name: "Apple",
href: "/docs/quick-starts/apple",
image: `/images/platforms/${$themeInUse}/apple.svg`,
},
{
name: "Android",
href: "/docs/quick-starts/android",
image: `/images/platforms/${$themeInUse}/android.svg`,
},
{
name: "React Native",
href: "/docs/quick-starts/react-native",
image: `/images/platforms/${$themeInUse}/react-native.svg`,
},
] as Array<{
name: string;
href: string;
image: string;
}>;
</script>
<ul class="flex flex-wrap gap-4 web-u-margin-block-32-mobile web-u-margin-block-40-not-mobile">
{#each platforms as platform}
<Tooltip>
<li>
<a href={platform.href} class="web-icon-button web-box-icon has-border-gradient">
<img
src={platform.image}
alt="{platform.name} quick start"
width="32"
height="32"
/>
</a>
</li>
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment>
</Tooltip>
{/each}
<ul
class="web-u-margin-block-32-mobile web-u-margin-block-40-not-mobile flex flex-wrap gap-4"
>
{#each platforms as platform}
<Tooltip>
<li>
<a
href={platform.href}
class="web-icon-button web-box-icon has-border-gradient"
>
<img
src={platform.image}
alt="{platform.name} quick start"
width="32"
height="32"
/>
</a>
</li>
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment>
</Tooltip>
{/each}
</ul>

View File

@@ -1,25 +1,25 @@
<script lang="ts">
import { type Theme, currentTheme } from '$routes/+layout.svelte';
import { type Theme, currentTheme } from "$routes/+layout.svelte";
import Select, { type SelectOption } from './Select.svelte';
import Select, { type SelectOption } from "./Select.svelte";
const options: SelectOption<Theme>[] = [
{
value: 'dark',
label: 'Dark',
icon: 'web-icon-dark'
},
{
value: 'light',
label: 'Light',
icon: 'web-icon-light'
},
{
value: 'system',
label: 'System',
icon: 'web-icon-system'
}
];
const options: SelectOption<Theme>[] = [
{
value: "dark",
label: "Dark",
icon: "web-icon-dark",
},
{
value: "light",
label: "Light",
icon: "web-icon-light",
},
{
value: "system",
label: "System",
icon: "web-icon-system",
},
];
</script>
<Select {options} bind:value={$currentTheme} placement="top" />

View File

@@ -1,105 +1,109 @@
<script lang="ts">
import { getTocCtx } from './TocRoot.svelte';
import TocTree from './TocTree.svelte';
import { getTocCtx } from "./TocRoot.svelte";
import TocTree from "./TocTree.svelte";
export let showToc = true;
export let showToc = true;
const {
toc: {
elements: { item },
states: { activeHeadingIdxs, headingsTree }
}
} = getTocCtx();
const {
toc: {
elements: { item },
states: { activeHeadingIdxs, headingsTree },
},
} = getTocCtx();
$: progress = Math.max(...$activeHeadingIdxs) / ($headingsTree.length - 1);
$: progress = Math.max(...$activeHeadingIdxs) / ($headingsTree.length - 1);
</script>
<aside class="web-grid-120-1fr-auto-side" class:web-is-mobile-closed={!showToc}>
<div class="web-page-steps">
<div
class="web-page-steps-location web-is-not-mobile"
style="--location:{progress * 100}%;"
<div class="web-page-steps">
<div
class="web-page-steps-location web-is-not-mobile"
style="--location:{progress * 100}%;"
>
<span class="web-page-steps-location-button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<span class="web-page-steps-location-button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<g clip-path="url(#clip0_1684_10747)">
<g filter="url(#filter0_b_1684_10747)">
<circle
cx="8"
cy="8"
r="8"
fill="url(#paint0_linear_1684_10747)"
fill-opacity="0.32"
/>
<circle
cx="8"
cy="8"
r="7.75"
stroke="url(#paint1_linear_1684_10747)"
stroke-width="0.5"
/>
</g>
<circle cx="8" cy="7.99219" r="3" fill="white" />
</g>
<defs>
<filter
id="filter0_b_1684_10747"
x="-200"
y="-200"
width="416"
height="416"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="100" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur_1684_10747"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur_1684_10747"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear_1684_10747"
x1="2.02105"
y1="1.10843"
x2="16.3872"
y2="17.2901"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.4" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_1684_10747"
x1="7.45643"
y1="-1.10615"
x2="5.53812"
y2="17.9973"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.16" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<clipPath id="clip0_1684_10747">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
</span>
</div>
<TocTree tree={$headingsTree} activeHeadingIdxs={$activeHeadingIdxs} {item} />
<g clip-path="url(#clip0_1684_10747)">
<g filter="url(#filter0_b_1684_10747)">
<circle
cx="8"
cy="8"
r="8"
fill="url(#paint0_linear_1684_10747)"
fill-opacity="0.32"
/>
<circle
cx="8"
cy="8"
r="7.75"
stroke="url(#paint1_linear_1684_10747)"
stroke-width="0.5"
/>
</g>
<circle cx="8" cy="7.99219" r="3" fill="white" />
</g>
<defs>
<filter
id="filter0_b_1684_10747"
x="-200"
y="-200"
width="416"
height="416"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="100" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur_1684_10747"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur_1684_10747"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear_1684_10747"
x1="2.02105"
y1="1.10843"
x2="16.3872"
y2="17.2901"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.4" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_1684_10747"
x1="7.45643"
y1="-1.10615"
x2="5.53812"
y2="17.9973"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.16" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<clipPath id="clip0_1684_10747">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
</span>
</div>
<TocTree
tree={$headingsTree}
activeHeadingIdxs={$activeHeadingIdxs}
{item}
/>
</div>
</aside>

View File

@@ -1,39 +1,45 @@
<script context="module" lang="ts">
import {
createTableOfContents,
type CreateTableOfContentsArgs,
type TableOfContents
} from '@melt-ui/svelte';
import { getContext, setContext } from 'svelte';
import {
createTableOfContents,
type CreateTableOfContentsArgs,
type TableOfContents,
} from "@melt-ui/svelte";
import { getContext, setContext } from "svelte";
const TOC_KEY = Symbol();
export type TocContext = {
toc: TableOfContents;
};
const TOC_KEY = Symbol();
export type TocContext = {
toc: TableOfContents;
};
const setCtx = (ctx: TocContext) => {
setContext<TocContext>(TOC_KEY, ctx);
};
const setCtx = (ctx: TocContext) => {
setContext<TocContext>(TOC_KEY, ctx);
};
export const getTocCtx = () => {
return getContext<TocContext>(TOC_KEY);
};
export const getTocCtx = () => {
return getContext<TocContext>(TOC_KEY);
};
</script>
<script lang="ts">
export let selector = '#main';
export let exclude: CreateTableOfContentsArgs['exclude'] = ['h1', 'h3', 'h4', 'h5', 'h6'];
export let activeType: CreateTableOfContentsArgs['activeType'] = 'highest';
export let scrollOffset: CreateTableOfContentsArgs['scrollOffset'] = 0;
export let selector = "#main";
export let exclude: CreateTableOfContentsArgs["exclude"] = [
"h1",
"h3",
"h4",
"h5",
"h6",
];
export let activeType: CreateTableOfContentsArgs["activeType"] = "highest";
export let scrollOffset: CreateTableOfContentsArgs["scrollOffset"] = 0;
const toc = createTableOfContents({
selector,
exclude,
activeType,
scrollOffset
});
const toc = createTableOfContents({
selector,
exclude,
activeType,
scrollOffset,
});
setCtx({ toc });
setCtx({ toc });
</script>
<slot />

View File

@@ -1,40 +1,44 @@
<script lang="ts">
import { type TableOfContentsItem, type TableOfContentsElements, melt } from '@melt-ui/svelte';
import { getTocCtx } from './TocRoot.svelte';
import {
type TableOfContentsItem,
type TableOfContentsElements,
melt,
} from "@melt-ui/svelte";
import { getTocCtx } from "./TocRoot.svelte";
export let tree: TableOfContentsItem[] = [];
export let activeHeadingIdxs: number[];
export let item: TableOfContentsElements['item'];
export let level = 1;
export let tree: TableOfContentsItem[] = [];
export let activeHeadingIdxs: number[];
export let item: TableOfContentsElements["item"];
export let level = 1;
const {
toc: {
helpers: { isActive }
}
} = getTocCtx();
const {
toc: {
helpers: { isActive },
},
} = getTocCtx();
</script>
<ul class="web-page-steps-list web-sub-body-500">
{#if tree && tree.length}
{#each tree as heading, i (i)}
<li>
<a
class:is-selected={$isActive(heading.id)}
href="#{heading.id}"
use:melt={$item(heading.id)}
>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html heading.node.innerHTML}
</a>
{#if heading.children && heading.children.length}
<svelte:self
tree={heading.children}
level={level + 1}
{activeHeadingIdxs}
{item}
/>
{/if}
</li>
{/each}
{/if}
{#if tree && tree.length}
{#each tree as heading, i (i)}
<li>
<a
class:is-selected={$isActive(heading.id)}
href="#{heading.id}"
use:melt={$item(heading.id)}
>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html heading.node.innerHTML}
</a>
{#if heading.children && heading.children.length}
<svelte:self
tree={heading.children}
level={level + 1}
{activeHeadingIdxs}
{item}
/>
{/if}
</li>
{/each}
{/if}
</ul>

View File

@@ -1,70 +1,74 @@
<script lang="ts">
import { createTooltip, melt } from '@melt-ui/svelte';
import type { FloatingConfig } from '@melt-ui/svelte/internal/actions';
import { fly, type FlyParams } from 'svelte/transition';
import { createTooltip, melt } from "@melt-ui/svelte";
import type { FloatingConfig } from "@melt-ui/svelte/internal/actions";
import { fly, type FlyParams } from "svelte/transition";
export let placement: NonNullable<FloatingConfig>['placement'] = 'top';
export let disabled = false;
export let closeOnPointerDown = false;
export let disableHoverableContent = false;
export let placement: NonNullable<FloatingConfig>["placement"] = "top";
export let disabled = false;
export let closeOnPointerDown = false;
export let disableHoverableContent = false;
const {
elements: { trigger, content, arrow },
states: { open }
} = createTooltip({
positioning: {
placement
},
openDelay: 0,
closeOnPointerDown,
forceVisible: true,
disableHoverableContent
});
const {
elements: { trigger, content, arrow },
states: { open },
} = createTooltip({
positioning: {
placement,
},
openDelay: 0,
closeOnPointerDown,
forceVisible: true,
disableHoverableContent,
});
$: flyParams = (function getFlyParams() {
const params: FlyParams = {
duration: 150
};
$: flyParams = (function getFlyParams() {
const params: FlyParams = {
duration: 150,
};
switch (placement) {
case 'top':
case 'top-start':
case 'top-end':
params.y = 4;
break;
case 'bottom':
case 'bottom-start':
case 'bottom-end':
params.y = -4;
break;
switch (placement) {
case "top":
case "top-start":
case "top-end":
params.y = 4;
break;
case "bottom":
case "bottom-start":
case "bottom-end":
params.y = -4;
break;
case 'left':
case 'left-start':
case 'left-end':
params.x = 4;
break;
case 'right':
case 'right-start':
case 'right-end':
params.x = -4;
break;
}
case "left":
case "left-start":
case "left-end":
params.x = 4;
break;
case "right":
case "right-start":
case "right-end":
params.x = -4;
break;
}
return params;
})();
return params;
})();
</script>
<slot name="asChild" trigger={$trigger} />
{#if !$$slots.asChild}
<span use:melt={$trigger}>
<slot />
</span>
<span use:melt={$trigger}>
<slot />
</span>
{/if}
{#if $open && !disabled}
<div use:melt={$content} class="web-tooltip web-sub-body-400" transition:fly={flyParams}>
<div use:melt={$arrow} />
<slot name="tooltip" />
</div>
<div
use:melt={$content}
class="web-tooltip web-sub-body-400"
transition:fly={flyParams}
>
<div use:melt={$arrow} />
<slot name="tooltip" />
</div>
{/if}

View File

@@ -1,25 +1,25 @@
<li class="slide web-carousel-item">
<div class="embla__slide__number">
<slot />
</div>
<div class="embla__slide__number">
<slot />
</div>
</li>
<style lang="scss">
@use '$scss/abstract' as *;
@use "$scss/abstract" as *;
.slide {
cursor: grab;
.slide {
cursor: grab;
&:active {
cursor: grabbing;
}
@media (max-width: 768px) {
flex: 0 0 100%;
}
flex: 0 0 50%;
min-width: 0;
margin-right: pxToRem(16);
&:active {
cursor: grabbing;
}
@media (max-width: 768px) {
flex: 0 0 100%;
}
flex: 0 0 50%;
min-width: 0;
margin-right: pxToRem(16);
}
</style>

View File

@@ -1,2 +1,2 @@
export { default as Root } from './Carousel.svelte';
export { default as Slide } from './CarouselSlide.svelte';
export { default as Root } from "./Carousel.svelte";
export { default as Slide } from "./CarouselSlide.svelte";

View File

@@ -1,7 +1,7 @@
export const GITHUB_STARS = '42.8K';
export const BANNER_KEY: Banners = 'discord-banner-01'; // Change key to force banner to show again
export const GITHUB_STARS = "42.8K";
export const BANNER_KEY: Banners = "discord-banner-01"; // Change key to force banner to show again
export const SENTRY_DSN =
'https://27d41dc8bb67b596f137924ab8599e59@o1063647.ingest.us.sentry.io/4507497727000576';
"https://27d41dc8bb67b596f137924ab8599e59@o1063647.ingest.us.sentry.io/4507497727000576";
/**
* History:
@@ -9,73 +9,72 @@ export const SENTRY_DSN =
* init-banner-02
* pricing-banner-01
*/
type Banners = 'discord-banner-01' | 'init-banner-02' | 'pricing-banner-01'
type Banners = "discord-banner-01" | "init-banner-02" | "pricing-banner-01";
export type Social = {
icon: string;
label: string;
link: string;
icon: string;
label: string;
link: string;
};
export type SocialShareOption = {
icon: string;
label: string;
link: string;
type: 'link' | 'copy';
icon: string;
label: string;
link: string;
type: "link" | "copy";
};
export const socialSharingOptions: Array<SocialShareOption> = [
{
icon: 'web-icon-x',
label: 'Twitter',
link: 'https://x.com/intent/post?text={TITLE}\n&url={URL}',
type: 'link'
},
{
icon: 'web-icon-linkedin',
label: 'LinkedIn',
link: 'https://www.linkedin.com/sharing/share-offsite?text={TITLE}\n&url={URL}',
type: 'link'
},
{
icon: 'web-icon-ycombinator',
label: 'YCombinator',
link: 'https://news.ycombinator.com/submitlink?t={TITLE}\n&u={URL}',
type: 'link'
},
{
icon: 'web-icon-copy',
label: 'Copy',
link: '',
type: 'copy'
}
]
{
icon: "web-icon-x",
label: "Twitter",
link: "https://x.com/intent/post?text={TITLE}\n&url={URL}",
type: "link",
},
{
icon: "web-icon-linkedin",
label: "LinkedIn",
link: "https://www.linkedin.com/sharing/share-offsite?text={TITLE}\n&url={URL}",
type: "link",
},
{
icon: "web-icon-ycombinator",
label: "YCombinator",
link: "https://news.ycombinator.com/submitlink?t={TITLE}\n&u={URL}",
type: "link",
},
{
icon: "web-icon-copy",
label: "Copy",
link: "",
type: "copy",
},
];
export const socials: Array<Social> = [
{
icon: 'web-icon-discord',
label: 'Discord',
link: 'https://appwrite.io/discord'
},
{
icon: 'web-icon-github',
label: 'Github',
link: 'https://github.com/appwrite'
},
{
icon: 'web-icon-x',
label: 'Twitter',
link: 'https://twitter.com/intent/follow?screen_name=appwrite'
},
{
icon: 'web-icon-linkedin',
label: 'LinkedIn',
link: 'https://www.linkedin.com/company/appwrite'
},
{
icon: 'web-icon-youtube',
label: 'YouTube',
link: 'https://www.youtube.com/c/appwrite?sub_confirmation=1'
}
{
icon: "web-icon-discord",
label: "Discord",
link: "https://appwrite.io/discord",
},
{
icon: "web-icon-github",
label: "Github",
link: "https://github.com/appwrite",
},
{
icon: "web-icon-x",
label: "Twitter",
link: "https://twitter.com/intent/follow?screen_name=appwrite",
},
{
icon: "web-icon-linkedin",
label: "LinkedIn",
link: "https://www.linkedin.com/company/appwrite",
},
{
icon: "web-icon-youtube",
label: "YouTube",
link: "https://www.youtube.com/c/appwrite?sub_confirmation=1",
},
];

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
<script lang="ts" generics="T">
import Fuse, { type IFuseOptions, type FuseResult } from 'fuse.js';
import Fuse, { type IFuseOptions, type FuseResult } from "fuse.js";
export let list: ReadonlyArray<T>;
export let options: IFuseOptions<T>;
export let query: string;
export let list: ReadonlyArray<T>;
export let options: IFuseOptions<T>;
export let query: string;
export let result: FuseResult<T>[];
export let result: FuseResult<T>[];
$: fuse = new Fuse(list, options);
$: if (list) {
fuse.setCollection(list);
}
$: if (list || query) {
result = fuse.search(query);
}
$: fuse = new Fuse(list, options);
$: if (list) {
fuse.setCollection(list);
}
$: if (list || query) {
result = fuse.search(query);
}
</script>

View File

@@ -1,6 +1,6 @@
import type { FuseResult } from 'fuse.js';
import type { FuseResult } from "fuse.js";
// Reexport your entry components here
export { default as Fuse } from './Fuse.svelte';
export { default as Fuse } from "./Fuse.svelte";
export type ResultType<T> = FuseResult<T>[];

View File

@@ -1,4 +1,4 @@
export { default as Main } from './Main.svelte';
export { default as Docs } from './Docs.svelte';
export { default as DocsArticle } from './DocsArticle.svelte';
export { default as DocsTutorial } from './DocsTutorial.svelte';
export { default as Main } from "./Main.svelte";
export { default as Docs } from "./Docs.svelte";
export { default as DocsArticle } from "./DocsArticle.svelte";
export { default as DocsTutorial } from "./DocsTutorial.svelte";

View File

@@ -1,3 +1,3 @@
export function capitalize(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
return str.charAt(0).toUpperCase() + str.slice(1);
}

View File

@@ -3,114 +3,114 @@ type Cancel = () => void;
type Promised<T> = T extends Promise<infer U> ? U : T;
type Args<T> = {
returned: Promised<T>;
cancel: Cancel;
returned: Promised<T>;
cancel: Cancel;
};
export type Chain = {
execute: () => Promise<void>;
cancel: Cancel;
execute: () => Promise<void>;
cancel: Cancel;
};
interface ChainFn {
<A>(fn1: (args: Args<undefined>) => A): Chain;
<A, B>(fn1: (args: Args<undefined>) => A, fn2: (args: Args<A>) => B): Chain;
<A, B, C>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C
): Chain;
<A, B, C, D>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D
): Chain;
<A, B, C, D, E>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E
): Chain;
<A, B, C, D, E, F>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F
): Chain;
<A, B, C, D, E, F, G>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G
): Chain;
<A, B, C, D, E, F, G, H>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H
): Chain;
<A, B, C, D, E, F, G, H, I>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
fn9: (args: Args<H>) => I
): Chain;
<A, B, C, D, E, F, G, H, I, J>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
fn9: (args: Args<H>) => I,
fn10: (args: Args<I>) => J
): Chain;
<A, B, C, D, E, F, G, H, I, J, K>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
fn9: (args: Args<H>) => I,
fn10: (args: Args<I>) => J,
fn11: (args: Args<J>) => K
): Chain;
<A, B, C, D, E, F, G, H, I, J, K, L>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
fn9: (args: Args<H>) => I,
fn10: (args: Args<I>) => J,
fn11: (args: Args<J>) => K,
fn12: (args: Args<K>) => L
): Chain;
// So on...
<A>(fn1: (args: Args<undefined>) => A): Chain;
<A, B>(fn1: (args: Args<undefined>) => A, fn2: (args: Args<A>) => B): Chain;
<A, B, C>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
): Chain;
<A, B, C, D>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
): Chain;
<A, B, C, D, E>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
): Chain;
<A, B, C, D, E, F>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
): Chain;
<A, B, C, D, E, F, G>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
): Chain;
<A, B, C, D, E, F, G, H>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
): Chain;
<A, B, C, D, E, F, G, H, I>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
fn9: (args: Args<H>) => I,
): Chain;
<A, B, C, D, E, F, G, H, I, J>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
fn9: (args: Args<H>) => I,
fn10: (args: Args<I>) => J,
): Chain;
<A, B, C, D, E, F, G, H, I, J, K>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
fn9: (args: Args<H>) => I,
fn10: (args: Args<I>) => J,
fn11: (args: Args<J>) => K,
): Chain;
<A, B, C, D, E, F, G, H, I, J, K, L>(
fn1: (args: Args<undefined>) => A,
fn2: (args: Args<A>) => B,
fn3: (args: Args<B>) => C,
fn4: (args: Args<C>) => D,
fn5: (args: Args<D>) => E,
fn6: (args: Args<E>) => F,
fn7: (args: Args<F>) => G,
fn8: (args: Args<G>) => H,
fn9: (args: Args<H>) => I,
fn10: (args: Args<I>) => J,
fn11: (args: Args<J>) => K,
fn12: (args: Args<K>) => L,
): Chain;
// So on...
}
/**
@@ -120,32 +120,32 @@ interface ChainFn {
* The cancel method will cancel the execution of the remaining callbacks.
*/
export const chain: ChainFn = (...fns: Function[]) => {
const cancelled = {} as Record<string, boolean>;
const cancelled = {} as Record<string, boolean>;
const cancel = () => {
Object.keys(cancelled).forEach((key) => (cancelled[key] = true));
};
const cancel = () => {
Object.keys(cancelled).forEach((key) => (cancelled[key] = true));
};
let lastRes: any = undefined;
let lastRes: any = undefined;
const execute = async () => {
const executionId = stupidId();
cancelled[executionId] = false;
for (let i = 0; i < fns.length; i++) {
const fn = fns[i];
if (cancelled[executionId]) {
delete cancelled[executionId];
return;
}
lastRes = await fn({ returned: lastRes, cancel });
}
const execute = async () => {
const executionId = stupidId();
cancelled[executionId] = false;
for (let i = 0; i < fns.length; i++) {
const fn = fns[i];
if (cancelled[executionId]) {
delete cancelled[executionId];
};
return;
}
return { execute, cancel };
lastRes = await fn({ returned: lastRes, cancel });
}
delete cancelled[executionId];
};
return { execute, cancel };
};
// Stupid way of generating unique id

View File

@@ -1,27 +1,27 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect } from "vitest";
import { clamp } from './clamp';
import type { TestCases } from './test';
import { clamp } from "./clamp";
import type { TestCases } from "./test";
const testCases: TestCases<typeof clamp> = [
{
args: [0, 5, 10],
expected: 5
},
{
args: [0, -5, 10],
expected: 0
},
{
args: [0, 15, 10],
expected: 10
}
{
args: [0, 5, 10],
expected: 5,
},
{
args: [0, -5, 10],
expected: 0,
},
{
args: [0, 15, 10],
expected: 10,
},
];
describe('clamp', () => {
testCases.forEach(({ args, expected }) => {
it(`should return ${expected} when given ${JSON.stringify(args)}`, () => {
expect(clamp(...args)).toBe(expected);
});
describe("clamp", () => {
testCases.forEach(({ args, expected }) => {
it(`should return ${expected} when given ${JSON.stringify(args)}`, () => {
expect(clamp(...args)).toBe(expected);
});
});
});

View File

@@ -1,3 +1,3 @@
export function clamp(min: number, value: number, max: number) {
return Math.min(Math.max(min, value), max);
return Math.min(Math.max(min, value), max);
}

Some files were not shown because too many files have changed in this diff Show More