mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-06 04:22:07 +00:00
prettify
This commit is contained in:
@@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
36
.github/workflows/index.yml
vendored
36
.github/workflows/index.yml
vendored
@@ -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 }}
|
||||
|
||||
132
.github/workflows/production.yml
vendored
132
.github/workflows/production.yml
vendored
@@ -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 }}
|
||||
|
||||
136
.github/workflows/staging.yml
vendored
136
.github/workflows/staging.yml
vendored
@@ -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 }}
|
||||
|
||||
34
.github/workflows/stale.yml
vendored
34
.github/workflows/stale.yml
vendored
@@ -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"
|
||||
|
||||
84
.github/workflows/tests.yml
vendored
84
.github/workflows/tests.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
14
.prettierrc
14
.prettierrc
@@ -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" } }]
|
||||
}
|
||||
|
||||
20
CONTENT.md
20
CONTENT.md
@@ -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
|
||||
|
||||
@@ -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!**
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
252
docker/stage.yml
252
docker/stage.yml
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
184
package.json
184
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {}
|
||||
}
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
14
src/app.d.ts
vendored
@@ -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 {};
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { generateIcons } from './scripts.js';
|
||||
import { generateIcons } from "./scripts.js";
|
||||
|
||||
generateIcons();
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { optimizeSVG } from './scripts.js';
|
||||
import { optimizeSVG } from "./scripts.js";
|
||||
|
||||
optimizeSVG();
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>");
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './portal';
|
||||
export * from './rect';
|
||||
export * from './visible';
|
||||
export * from "./portal";
|
||||
export * from "./rect";
|
||||
export * from "./visible";
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
@@ -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} />
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>[];
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user