feat: immich data (#1)

This commit is contained in:
Jason Rasmussen
2024-09-30 12:39:19 -04:00
committed by GitHub
parent f1db331e9b
commit 17a4af7509
58 changed files with 7590 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
name: Build
on:
push:
branches: [main]
pull_request:
branches: [main]
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./web
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './web/.nvmrc'
- name: Run npm install
run: npm ci
- name: Run build
run: npm run build
- name: Upload build output
uses: actions/upload-artifact@v4
with:
name: build-output-web
path: build
retention-days: 1
+178
View File
@@ -0,0 +1,178 @@
name: Deploy
on:
workflow_run:
workflows: ['Build']
types:
- completed
jobs:
checks:
name: Deploy checks
runs-on: ubuntu-latest
outputs:
parameters: ${{ steps.parameters.outputs.result }}
steps:
- if: ${{ github.event.workflow_run.conclusion == 'failure' }}
run: echo 'The triggering workflow failed' && exit 1
- name: Determine deploy parameters
id: parameters
uses: actions/github-script@v7
with:
script: |
const eventType = context.payload.workflow_run.event;
const isFork = context.payload.workflow_run.repository.fork;
let parameters;
console.log({eventType, isFork});
if (eventType == "push") {
const branch = context.payload.workflow_run.head_branch;
console.log({branch});
const shouldDeploy = !isFork && branch == "main";
parameters = {
event: "branch",
name: "main",
shouldDeploy
};
} else if (eventType == "pull_request") {
let pull_number = context.payload.workflow_run.pull_requests[0]?.number;
if(!pull_number) {
const response = await github.rest.search.issuesAndPullRequests({q: 'repo:${{ github.repository }} is:pr sha:${{ github.event.workflow_run.head_sha }}',per_page: 1,})
const items = response.data.items
if (items.length < 1) {
throw new Error("No pull request found for the commit")
}
const pullRequestNumber = items[0].number
console.info("Pull request number is", pullRequestNumber)
pull_number = pullRequestNumber
}
const {data: pr} = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number
});
console.log({pull_number});
parameters = {
event: "pr",
name: `pr-${pull_number}`,
pr_number: pull_number,
shouldDeploy: true
};
} else if (eventType == "release") {
parameters = {
event: "release",
name: context.payload.workflow_run.head_branch,
shouldDeploy: !isFork
};
}
console.log(parameters);
return parameters;
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: checks
if: ${{ fromJson(needs.checks.outputs.parameters).shouldDeploy }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Load parameters
id: parameters
uses: actions/github-script@v7
with:
script: |
const json = `${{ needs.checks.outputs.parameters }}`;
const parameters = JSON.parse(json);
core.setOutput("event", parameters.event);
core.setOutput("name", parameters.name);
core.setOutput("shouldDeploy", parameters.shouldDeploy);
- run: |
echo "Starting docs deployment for ${{ steps.parameters.outputs.event }} ${{ steps.parameters.outputs.name }}"
- name: Download artifact
uses: actions/github-script@v7
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.find((artifact) => artifact.name == "build-output-web");
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/build-output.zip`, Buffer.from(download.data));
- name: Unzip artifact
run: unzip "${{ github.workspace }}/build-output.zip" -d "${{ github.workspace }}/build"
- name: Deploy App
env:
TF_VAR_prefix_name: ${{ steps.parameters.outputs.name }}
TF_VAR_prefix_event_type: ${{ steps.parameters.outputs.event }}
TF_VAR_app_url: data.immich.app
TF_VAR_app_name: immich-data
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@v2
with:
tg_version: '0.58.12'
tofu_version: '1.7.1'
tg_dir: 'deployment/modules/cloudflare/static-pages'
tg_command: 'apply'
- name: Deploy App Output
id: terraform-output
env:
TF_VAR_prefix_name: ${{ steps.parameters.outputs.name }}
TF_VAR_prefix_event_type: ${{ steps.parameters.outputs.event }}
TF_VAR_app_url: data.immich.app
TF_VAR_app_name: immich-data
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@v2
with:
tg_version: '0.58.12'
tofu_version: '1.7.1'
tg_dir: 'deployment/modules/cloudflare/static-pages'
tg_command: 'output -json'
- name: Output Cleaning
id: clean
run: |
TG_OUT=$(echo '${{ steps.terraform-output.outputs.tg_action_output }}' | sed 's|%0A|\n|g ; s|%3C|<|g' | jq -c .)
echo "output=$TG_OUT" >> $GITHUB_OUTPUT
- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ fromJson(steps.clean.outputs.output).pages_project_name.value }}
directory: 'build'
branch: ${{ steps.parameters.outputs.name }}
wranglerVersion: '3'
- name: Comment
uses: actions-cool/maintain-one-comment@v3
if: ${{ steps.parameters.outputs.event == 'pr' }}
with:
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
body: |
📖 Preview of web deployed to [${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }}](https://${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }})
emojis: 'rocket'
body-include: '<!-- web PR URL -->'
+34
View File
@@ -0,0 +1,34 @@
name: Docs destroy
on:
pull_request_target:
types: [closed]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Destroy Docs Subdomain
env:
TF_VAR_prefix_name: 'pr-${{ github.event.number }}'
TF_VAR_prefix_event_type: 'pr'
TF_VAR_app_url: data.immich.app
TF_VAR_app_name: immich-data
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@v2
with:
tg_version: '0.58.12'
tofu_version: '1.7.1'
tg_dir: 'deployment/modules/cloudflare/static-pages'
tg_command: 'destroy'
- name: Comment
uses: actions-cool/maintain-one-comment@v3
with:
number: ${{ github.event.number }}
delete: true
body-include: '<!-- web PR URL -->'
+46
View File
@@ -0,0 +1,46 @@
name: Test
on:
workflow_dispatch:
pull_request:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./web
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './web/.nvmrc'
- name: Run npm install
run: npm ci
- name: Run linter
run: npm run lint
if: ${{ !cancelled() }}
- name: Run formatter
run: npm run format
if: ${{ !cancelled() }}
- name: Run tsc
run: npm run check
if: ${{ !cancelled() }}
- name: Run unit tests
run: npm test
if: ${{ !cancelled() }}
+38
View File
@@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
+38
View File
@@ -0,0 +1,38 @@
# OpenTofu
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Terragrunt
# terragrunt cache directories
**/.terragrunt-cache/*
# Terragrunt debug output file (when using `--terragrunt-debug` option)
# See: https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-debug
terragrunt-debug.tfvars.json
@@ -0,0 +1,38 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.42.0"
constraints = "4.42.0"
hashes = [
"h1:+1iz3OcEEYBLeVS07cqRP5Kef0Y0XS8YEj/I1HH9J/A=",
"h1:/XZtSyok+I0hAHqj4hcA2RP4G9uvTABG4oMGC3vlS6o=",
"h1:5Z9Kfc5ufjqbLBXP9lZqQ11PAc4m6aTf/0M6miceOVQ=",
"h1:6cupVsiQundwTt+/lSuiR3s1s1H0JDN1H7USaF/+nDg=",
"h1:FasXNkaO7MNWy6GtuB/QFlnpQ7UuQQNotN1asY/r0Z8=",
"h1:G+5pvEHQlVkPXW6HK1twiWHOV/pavtSpQthZ0wb0HYM=",
"h1:HYhFNiRR1InZGjelCdQmg/KJHL2lFDlaWGSl2KA2fwk=",
"h1:LkMNEQUnYBAx8EFvl1BU/AVG1cs1Xi62kCvTVvYvDZ8=",
"h1:McPTXlnBSEzFGmVJHU7KyoBI3hJRh/eRcFTcijAfO/I=",
"h1:MoqiPtyXewRdNsevZUMqb0rXXpeLUVxNVEzux4AC7y4=",
"h1:RUgEZET04NprAFc4XAJwqqLiwNKaCTQEQEXtIZtNu1E=",
"h1:UCneKnBS53RlyJsdCqCCWrw19GE48tzTyO2u/V5u24c=",
"h1:aGbtjN8I2mLyW7aJ8KVRYuVdYeQCh3Ee8c3ixiijmjM=",
"h1:phTeKoT4aUmPXQO6d7ZbSQ8Cu41dhosACCEiDpRsL/I=",
"zh:1abb93dda2bf73b1656dc63673c752642bfa4c31c8ca1e83cdd278f42fa121c1",
"zh:2b9c90fe4fffaadfeccd0f457bd1354ba2dba920c5525748d8f8f24656d6c7bd",
"zh:710a0cf84406df09705115a62bd6a418188a3b884f615fe7122a6fc51ec59bc0",
"zh:7875b38b281d17a24d89116b33f92b8b24292be160a2c618874a0f674171bd34",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:ad28c94908c336ca97feabb02734a2c115eddaa5f4a625f00bfafb3ac65646e4",
"zh:aee7861d44cf1a71a4846e1ffda20992259838ee600ca79d3abc80b43f4ff0f0",
"zh:b1327e8ea04f0df5efc2b8848c358bf0d256716ad5372f91f5960296a1f68677",
"zh:b9ec2310feffef38c1488bed038ad7942c54f930f2166a600b5fc850e377bddf",
"zh:d04178727d7157aae8eb66b4f7318338c89cd685b53c67f5ce41de4160c2d484",
"zh:d76c4bb5a5bfed710fcc8a1d919172cbfcbda0cec177f1d2cb44aff82a862a32",
"zh:ddf3c126b5c98267240f960f8fed381115675793e5b86dd2cbdebeb628efb0cb",
"zh:e3efb3b9409626f15931a81edaeb96e4baf462a24b32dac9d97cc36d1fef1f01",
"zh:e925c963cddf5778829f0b26f72ad8bcbc1aed3510cfa0bd5a9e7cbf4c64a6e8",
"zh:ff78a903b2432fbff7b04e84a23c71425589bad7a4b846e08bc131268b80900f",
]
}
@@ -0,0 +1,11 @@
terraform {
backend "pg" {}
required_version = "~> 1.7"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.42.0"
}
}
}
@@ -0,0 +1,26 @@
resource "cloudflare_pages_domain" "immich_app_branch_domain" {
account_id = var.cloudflare_account_id
project_name = data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_names[var.app_url]
domain = local.domain
}
resource "cloudflare_record" "immich_app_branch_subdomain" {
name = local.domain
proxied = true
ttl = 1
type = "CNAME"
value = local.is_main ? data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_subdomains[var.app_url] : "${replace(var.prefix_name, "/\\/|\\./", "-")}.${data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_subdomains[var.app_url]}"
zone_id = data.terraform_remote_state.cloudflare_account.outputs.immich_app_zone_id
}
output "immich_app_branch_subdomain" {
value = cloudflare_record.immich_app_branch_subdomain.hostname
}
output "immich_app_branch_pages_hostname" {
value = cloudflare_record.immich_app_branch_subdomain.value
}
output "pages_project_name" {
value = cloudflare_pages_domain.immich_app_branch_domain.project_name
}
@@ -0,0 +1,5 @@
locals {
is_main = var.prefix_name == "main"
domain_prefix = !local.is_main && contains(["branch", "pr"], var.prefix_event_type) ? "${var.prefix_name}.preview." : ""
domain = "${local.domain_prefix}${var.app_url}"
}
@@ -0,0 +1,3 @@
provider "cloudflare" {
api_token = data.terraform_remote_state.api_keys_state.outputs.terraform_key_cloudflare_docs
}
@@ -0,0 +1,17 @@
data "terraform_remote_state" "api_keys_state" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "prod_cloudflare_api_keys"
}
}
data "terraform_remote_state" "cloudflare_account" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "prod_cloudflare_account"
}
}
@@ -0,0 +1,25 @@
terraform {
source = "."
extra_arguments custom_vars {
commands = get_terraform_commands_that_need_vars()
}
}
include {
path = find_in_parent_folders("state.hcl")
}
locals {
prefix_name = get_env("TF_VAR_prefix_name")
app_name = replace(get_env("TF_VAR_app_name"), "-", "_")
}
remote_state {
backend = "pg"
config = {
conn_str = get_env("TF_STATE_POSTGRES_CONN_STR")
schema_name = "prod_cloudflare_immich_app_${local.app_name}_${local.prefix_name}"
}
}
@@ -0,0 +1,6 @@
variable "cloudflare_account_id" {}
variable "tf_state_postgres_conn_str" {}
variable "prefix_name" {}
variable "prefix_event_type" {}
variable "app_url" {}
+20
View File
@@ -0,0 +1,20 @@
locals {
cloudflare_account_id = get_env("CLOUDFLARE_ACCOUNT_ID")
cloudflare_api_token = get_env("CLOUDFLARE_API_TOKEN")
tf_state_postgres_conn_str = get_env("TF_STATE_POSTGRES_CONN_STR")
}
remote_state {
backend = "pg"
config = {
conn_str = local.tf_state_postgres_conn_str
}
}
inputs = {
cloudflare_account_id = local.cloudflare_account_id
cloudflare_api_token = local.cloudflare_api_token
tf_state_postgres_conn_str = local.tf_state_postgres_conn_str
}
+41
View File
@@ -0,0 +1,41 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", "docker:pinDigests"],
"minimumReleaseAge": "5 days",
"packageRules": [
{
"groupName": "core",
"matchUpdateTypes": ["minor", "patch"],
"excludePackageNames": ["node", "@types/node"],
"schedule": "on tuesday"
},
{
"groupName": "svelte",
"matchUpdateTypes": ["major"],
"matchPackagePrefixes": ["@sveltejs"],
"schedule": "on tuesday"
},
{
"matchFileNames": [".github/**"],
"groupName": "github-actions",
"schedule": "on tuesday"
},
{
"matchDatasources": ["npm"],
"rangeStrategy": "bump",
"groupName": "node",
"versioning": "node",
"matchPackageNames": ["node", "@types/node"],
"schedule": "on tuesday"
},
{
"groupName": "node",
"matchDatasources": ["docker"],
"matchPackageNames": ["node"],
"versionCompatibility": "^(?<version>[^-]+)(?<compatibility>-.*)?$",
"versioning": "node",
"schedule": "on tuesday"
}
],
"labels": ["dependencies", "renovate"]
}
+21
View File
@@ -0,0 +1,21 @@
node_modules
# Output
.output
.vercel
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
+1
View File
@@ -0,0 +1 @@
engine-strict=true
+1
View File
@@ -0,0 +1 @@
20.17
+5
View File
@@ -0,0 +1,5 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
src/lib/assets/data/*.json
+7
View File
@@ -0,0 +1,7 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
+33
View File
@@ -0,0 +1,33 @@
import js from '@eslint/js';
import ts from 'typescript-eslint';
import svelte from 'eslint-plugin-svelte';
import prettier from 'eslint-config-prettier';
import globals from 'globals';
/** @type {import('eslint').Linter.Config[]} */
export default [
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs['flat/recommended'],
prettier,
...svelte.configs['flat/prettier'],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
},
{
files: ['**/*.svelte'],
languageOptions: {
parserOptions: {
parser: ts.parser,
},
},
},
{
ignores: ['build/', '.svelte-kit/', 'dist/'],
},
];
+3
View File
@@ -0,0 +1,3 @@
IMMICH_APP=buy.immich.app
PUBLIC_IMMICH_PAY_HOST=https://futopay-test.azurewebsites.net
orderId=e1eddcf3-ae45-4124-bba0-f9c7ec67b255
+6190
View File
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
{
"name": "my.immich.app",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "vite dev",
"build": "npm run sync && vite build",
"preview": "vite preview",
"check": "npm run sync && svelte-check --tsconfig ./tsconfig.json",
"sync": "svelte-kit sync",
"test": "vitest",
"lint": "prettier --check . && eslint . --max-warnings 0",
"lint:fix": "npm run lint -- --fix",
"format": "prettier --check .",
"format:fix": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "^9.0.0",
"@types/luxon": "^3.4.2",
"autoprefixer": "^10.4.19",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
"globals": "^15.0.0",
"postcss": "^8.4.39",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.7",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.4",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"typescript-eslint": "^8.0.0-alpha.20",
"vite": "^5.0.3",
"vitest": "^2.0.0"
},
"type": "module",
"dependencies": {
"@mdi/js": "^7.4.47",
"@unovis/svelte": "^1.4.3-beta.0",
"@unovis/ts": "^1.4.3-beta.0",
"luxon": "^3.5.0"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
+13
View File
@@ -0,0 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
+16
View File
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Immich - Data</title>
<meta name="theme-color" content="currentColor" />
<meta name="description" content="Graphs and charts for data around Immich" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" class="bg-gray-200">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
import { describe, it, expect } from 'vitest';
describe('sum test', () => {
it('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
});
+77
View File
@@ -0,0 +1,77 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* light */
--immich-primary: 66 80 175;
--immich-bg: 255 255 255;
--immich-fg: 0 0 0;
--immich-gray: 246 246 244;
--immich-error: 229 115 115;
--immich-success: 129 199 132;
--immich-warning: 255 183 77;
/* dark */
--immich-dark-primary: 172 203 250;
--immich-dark-bg: 0 0 0;
--immich-dark-fg: 229 231 235;
--immich-dark-gray: 33 33 33;
--immich-dark-error: 211 47 47;
--immich-dark-success: 56 142 60;
--immich-dark-warning: 245 124 0;
}
}
@font-face {
font-family: 'Overpass';
src: url('$lib/assets/fonts/overpass/Overpass.ttf') format('truetype-variations');
font-weight: 1 999;
font-style: normal;
ascent-override: 100%;
}
:root {
font-family: 'Overpass', sans-serif;
}
:root.dark {
color-scheme: dark;
}
:root:not(.dark) {
color-scheme: light;
}
html {
height: 100%;
width: 100%;
font-size: 17px;
}
body {
margin: 0;
color: #3a3a3a;
}
@layer utilities {
.immich-gradient {
background:
linear-gradient(to bottom right, #ededed 0%, #f0f1fc 50%) bottom right / 50% 50% no-repeat,
linear-gradient(to bottom left, #ededed 0%, #f0f1fc 50%) bottom left / 50% 50% no-repeat,
linear-gradient(to top left, #ededed 0%, #f0f1fc 50%) top left / 50% 50% no-repeat,
linear-gradient(to top right, #ededed 0%, #f0f1fc 50%) top right / 50% 50% no-repeat;
}
}
.area-graph {
--vis-area-cursor: default;
--vis-area-fill-opacity: 1;
--vis-area-stroke-color: none;
--vis-area-stroke-width: 0px;
--vis-area-stroke-dasharray: none;
--vis-area-stroke-opacity: 1;
--vis-area-hover-fill-opacity: none;
--vis-area-hover-stroke-width: none;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
+84
View File
@@ -0,0 +1,84 @@
<script lang="ts" context="module">
export type Type = 'button' | 'submit' | 'reset';
export type Color =
| 'primary'
| 'secondary'
| 'transparent-primary'
| 'text-primary'
| 'light-red'
| 'red'
| 'green'
| 'gray'
| 'transparent-gray'
| 'dark-gray'
| 'overlay-primary';
export type Size = 'tiny' | 'icon' | 'link' | 'sm' | 'base' | 'lg';
export type Rounded = 'lg' | '3xl' | 'full' | false;
export type Shadow = 'md' | false;
</script>
<script lang="ts">
export let type: Type = 'button';
export let color: Color = 'primary';
export let size: Size = 'base';
export let rounded: Rounded = '3xl';
export let shadow: Shadow = 'md';
export let disabled = false;
export let fullwidth = false;
export let border = false;
export let title: string | undefined = '';
export let form: string | undefined = undefined;
let className = '';
export { className as class };
const colorClasses: Record<Color, string> = {
primary:
'bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray enabled:dark:hover:bg-immich-dark-primary/80 enabled:hover:bg-immich-primary/90',
secondary:
'bg-gray-500 dark:bg-gray-200 text-white dark:text-immich-dark-gray enabled:hover:bg-gray-500/90 enabled:dark:hover:bg-gray-200/90',
'transparent-primary':
'text-gray-500 dark:text-immich-dark-primary enabled:hover:bg-gray-100 enabled:dark:hover:bg-gray-700',
'text-primary':
'text-immich-primary dark:text-immich-dark-primary enabled:dark:hover:bg-immich-dark-primary/10 enabled:hover:bg-immich-primary/10',
'light-red': 'bg-[#F9DEDC] text-[#410E0B] enabled:hover:bg-red-50',
red: 'bg-red-500 text-white enabled:hover:bg-red-400',
green: 'bg-green-400 text-gray-800 enabled:hover:bg-green-400/90',
gray: 'bg-gray-500 dark:bg-gray-200 enabled:hover:bg-gray-500/75 enabled:dark:hover:bg-gray-200/80 text-white dark:text-immich-dark-gray',
'transparent-gray':
'dark:text-immich-dark-fg enabled:hover:bg-immich-primary/5 enabled:hover:text-gray-700 enabled:hover:dark:text-immich-dark-fg enabled:dark:hover:bg-immich-dark-primary/25',
'dark-gray':
'dark:border-immich-dark-gray dark:bg-gray-500 enabled:dark:hover:bg-immich-dark-primary/50 enabled:hover:bg-immich-primary/10 dark:text-white',
'overlay-primary': 'text-gray-500 enabled:hover:bg-gray-100',
};
const sizeClasses: Record<Size, string> = {
tiny: 'p-0 ml-2 mr-0 align-top',
icon: 'p-2.5',
link: 'p-2 font-medium',
sm: 'px-4 py-2 text-sm font-medium',
base: 'px-6 py-3 font-medium',
lg: 'px-6 py-4 font-semibold',
};
</script>
<button
{type}
{disabled}
{title}
{form}
on:click
on:focus
on:blur
class="{className} inline-flex items-center justify-center transition-colors disabled:cursor-not-allowed disabled:opacity-60 {colorClasses[
color
]} {sizeClasses[size]}"
class:rounded-lg={rounded === 'lg'}
class:rounded-3xl={rounded === '3xl'}
class:rounded-full={rounded === 'full'}
class:shadow-md={shadow === 'md'}
class:w-full={fullwidth}
class:border
>
<slot />
</button>
+28
View File
@@ -0,0 +1,28 @@
<script lang="ts">
import Icon from '$lib/components/icon.svelte';
import LoadingSpinner from '$lib/components/loading-spinner.svelte';
import { mdiAlertCircleOutline, mdiCheckCircleOutline } from '@mdi/js';
import { fade } from 'svelte/transition';
export let status: 'loading' | 'success' | 'error' | undefined = undefined;
</script>
<div
class="flex gap-4 flex-col place-content-center place-items-center text-center mt-4 justify-between relative border p-10 rounded-3xl bg-gray-100"
>
<div class="absolute -top-[24px] left-[calc(50%-24px)]">
{#if status === 'loading'}
<div in:fade={{ duration: 200 }}>
<div class="bg-immich-bg rounded-full p-1 border">
<LoadingSpinner size="48" />
</div>
</div>
{:else if status === 'success'}
<Icon path={mdiCheckCircleOutline} size="48" class="text-white rounded-full bg-immich-primary" />
{:else if status === 'error'}
<Icon path={mdiAlertCircleOutline} size="48" class="text-white rounded-full bg-red-500" />
{/if}
</div>
<slot />
</div>
@@ -0,0 +1,26 @@
<script lang="ts">
import '$lib/app.css';
import starData from '$lib/assets/data/star-data.json';
import { VisAxis, VisCrosshair, VisArea, VisTooltip, VisXYContainer } from '@unovis/svelte';
import { DateTime } from 'luxon';
type DataRecord = [DateTime, number];
const data = starData.map(([timestamp, value]) => [DateTime.fromSeconds(timestamp), value]);
const x = ([timestamp]: DataRecord) => timestamp.toMillis();
const y = ([, value]: DataRecord) => value;
const tickFormatX = (value: number) => DateTime.fromMillis(value).toFormat('MMM yy');
const tickFormatY = (i: number) =>
new Intl.NumberFormat(navigator.language, { maximumSignificantDigits: 3 }).format(i);
const template = ([timestamp, value]: DataRecord) =>
`${timestamp.toLocaleString()} - ${value.toLocaleString()} discussions`;
</script>
<VisXYContainer {data} height={250} class="area-graph">
<VisArea {x} {y} color="royalblue" />
<!-- <VisBulletLegend {items} {onLegendItemClick} bulletSize="15px" /> -->
<VisTooltip />
<VisCrosshair {x} {y} {template} />
<VisAxis tickFormat={tickFormatX} type="x" numTicks={6} gridLine={false} />
<VisAxis tickFormat={tickFormatY} type="y" numTicks={4} gridLine={true} />
</VisXYContainer>
@@ -0,0 +1,25 @@
<script lang="ts">
import '$lib/app.css';
import issueData from '$lib/assets/data/issue-data.json';
import { VisAxis, VisCrosshair, VisArea, VisTooltip, VisXYContainer } from '@unovis/svelte';
import { DateTime } from 'luxon';
type DataRecord = [DateTime, number, number, number, number];
const data = issueData.map(([timestamp, ...rest]) => [DateTime.fromSeconds(timestamp), ...rest]);
const x = ([timestamp]: DataRecord) => timestamp.toMillis();
const y = ([, , , total]: DataRecord) => total;
const tickFormatX = (value: number) => DateTime.fromMillis(value).toFormat('MMM yy');
const tickFormatY = (i: number) =>
new Intl.NumberFormat(navigator.language, { maximumSignificantDigits: 3 }).format(i);
const template = ([timestamp, value]: DataRecord) =>
`${timestamp.toLocaleString()} - ${value.toLocaleString()} open issues`;
</script>
<VisXYContainer {data} height={250} class="area-graph">
<VisArea {x} {y} color="#3fb950" />
<VisTooltip />
<VisCrosshair {x} {y} {template} />
<VisAxis tickFormat={tickFormatX} type="x" numTicks={6} gridLine={false} />
<VisAxis tickFormat={tickFormatY} type="y" numTicks={4} gridLine={true} />
</VisXYContainer>
@@ -0,0 +1,31 @@
<script lang="ts">
import '$lib/app.css';
import prData from '$lib/assets/data/pr-data.json';
import { VisAxis, VisCrosshair, VisArea, VisTooltip, VisXYContainer } from '@unovis/svelte';
import { DateTime } from 'luxon';
type DataRecord = [DateTime, number, number, number];
// remove headers
prData.shift();
const data = (prData as Array<[number, number, number, number]>).map(([timestamp, , , total]) => [
DateTime.fromSeconds(timestamp),
total,
]);
const x = ([timestamp]: DataRecord) => timestamp.toMillis();
const y = ([, value]: DataRecord) => value;
const tickFormatX = (value: number) => DateTime.fromMillis(value).toFormat('MMM yy');
const tickFormatY = (i: number) =>
new Intl.NumberFormat(navigator.language, { maximumSignificantDigits: 3 }).format(i);
const template = ([timestamp, value]: DataRecord) =>
`${timestamp.toLocaleString()} - ${value.toLocaleString()} open pull requests`;
</script>
<VisXYContainer {data} height={250} class="area-graph">
<VisArea {x} {y} color="#ab7df8" />
<VisTooltip />
<VisCrosshair {x} {y} {template} />
<VisAxis tickFormat={tickFormatX} type="x" numTicks={6} gridLine={false} />
<VisAxis tickFormat={tickFormatY} type="y" numTicks={4} gridLine={true} />
</VisXYContainer>
@@ -0,0 +1,25 @@
<script lang="ts">
import '$lib/app.css';
import starData from '$lib/assets/data/star-data.json';
import { VisAxis, VisCrosshair, VisArea, VisTooltip, VisXYContainer } from '@unovis/svelte';
import { DateTime } from 'luxon';
type DataRecord = [DateTime, number];
const data = starData.map(([timestamp, value]) => [DateTime.fromSeconds(timestamp), value]);
const x = ([timestamp]: DataRecord) => timestamp.toMillis();
const y = ([, value]: DataRecord) => value;
const tickFormatX = (value: number) => DateTime.fromMillis(value).toFormat('MMM yy');
const tickFormatY = (i: number) =>
new Intl.NumberFormat(navigator.language, { maximumSignificantDigits: 3 }).format(i);
const template = ([timestamp, value]: DataRecord) =>
`${timestamp.toLocaleString()} - ${value.toLocaleString()} stars`;
</script>
<VisXYContainer {data} height={250} class="area-graph">
<VisArea {x} {y} color="#ffc501" />
<VisTooltip />
<VisCrosshair {x} {y} {template} />
<VisAxis tickFormat={tickFormatX} type="x" numTicks={6} gridLine={false} />
<VisAxis tickFormat={tickFormatY} type="y" numTicks={4} gridLine={true} />
</VisXYContainer>
+36
View File
@@ -0,0 +1,36 @@
<script lang="ts">
import type { AriaRole } from 'svelte/elements';
export let size: string | number = '1em';
export let color = 'currentColor';
export let path: string;
export let title: string | null = null;
export let desc = '';
export let flipped = false;
let className = '';
export { className as class };
export let viewBox = '0 0 24 24';
export let role: AriaRole = 'img';
export let ariaHidden: boolean | undefined = undefined;
export let ariaLabel: string | undefined = undefined;
export let ariaLabelledby: string | undefined = undefined;
</script>
<svg
width={size}
height={size}
{viewBox}
class="{className} {flipped ? '-scale-x-100' : ''}"
{role}
aria-label={ariaLabel}
aria-hidden={ariaHidden}
aria-labelledby={ariaLabelledby}
>
{#if title}
<title>{title}</title>
{/if}
{#if desc}
<desc>{desc}</desc>
{/if}
<path d={path} fill={color} />
</svg>
@@ -0,0 +1,24 @@
<script lang="ts">
export let size: string = '24';
</script>
<div>
<svg
role="status"
style:height="{size}px"
style:width="{size}px"
class="animate-spin fill-immich-primary text-gray-400 dark:text-gray-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
</div>
+7
View File
@@ -0,0 +1,7 @@
// place files you want to import through the `$lib` alias in this folder.
export enum StorageKey {
INSTANCE_URL = 'immich-instance-url',
}
export type Fetch = typeof fetch;
+8
View File
@@ -0,0 +1,8 @@
<script lang="ts">
import '$lib/app.css';
// document.documentElement.classList.add('dark');
</script>
<section class="h-full flex flex-col align-center gap-2 max-w-screen-lg mx-auto bg-white pb-36">
<slot />
</section>
+57
View File
@@ -0,0 +1,57 @@
<script lang="ts">
import '$lib/app.css';
import GithubDiscussionGraph from '$lib/components/graphs/github-discussion-graph.svelte';
import GithubIssueGraph from '$lib/components/graphs/github-issue-graph.svelte';
import GithubPullRequestGraph from '$lib/components/graphs/github-pull-request-graph.svelte';
import GithubStarsGraph from '$lib/components/graphs/github-stars-graph.svelte';
import Icon from '$lib/components/icon.svelte';
import { mdiBug, mdiGithub, mdiMessageOutline, mdiSourceBranch, mdiStarOutline } from '@mdi/js';
</script>
<main class="p-4 md:p-8">
<div
class="flex flex-col align-center gap-2 dark:border-immich-dark-gray dark:bg-immich-dark-gray rounded-lg md:rounded-2xl"
>
<div class="w-full h-full flex gap-2 place-items-center mb-2">
<img src="/img/immich-logo.svg" alt={'immich-logo'} class="h-12 w-12" />
<p class="dark:text-gray-100 text-2xl font-medium text-immich-primary">Immich Data</p>
</div>
<section class="flex flex-col gap-4">
<h2 class="flex gap-1 text-xl">
<Icon path={mdiGithub} size="24" />
<span>Repro Trends</span>
</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div>
<h3 class="flex gap-1 text-lg">
<Icon path={mdiStarOutline} size="24" />
<span>Stars</span>
</h3>
<GithubStarsGraph />
</div>
<div>
<h3 class="flex gap-1 text-lg">
<Icon path={mdiBug} size="24" />
<span>Open Issues</span>
</h3>
<GithubIssueGraph />
</div>
<div>
<h3 class="flex gap-1 text-lg">
<Icon path={mdiSourceBranch} size="24" />
<span>Open Pull Requests</span>
</h3>
<GithubPullRequestGraph />
</div>
<div>
<h3 class="flex gap-1 text-lg">
<Icon path={mdiMessageOutline} size="24" />
<span>Total Discussions</span>
</h3>
<GithubDiscussionGraph />
</div>
</div>
</section>
</div>
</main>
+1
View File
@@ -0,0 +1 @@
export const ssr = false;
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Router_Medium_x5F_White" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 792 266.25" style="enable-background:new 0 0 792 266.25;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4251B0;}
.st1{fill:#FA2921;}
.st2{fill:#ED79B5;}
.st3{fill:#FFB400;}
.st4{fill:#1E83F7;}
.st5{fill:#18C249;}
</style>
<g>
<path class="st0" d="M268.73,63.18c6.34,0,11.52,5.18,11.52,11.35c0,6.34-5.18,11.35-11.52,11.35s-11.69-5.01-11.69-11.35
C257.04,68.36,262.39,63.18,268.73,63.18z M258.88,122.45c0-3.01-0.67-7.85-0.67-10.68c0-6.01,4.67-10.68,10.52-10.68
c5.84,0,10.52,4.67,10.52,10.68c0,2.84-0.83,7.68-0.83,10.68v38.73c0,3.01,0.83,7.85,0.83,10.68c0,6.01-4.67,10.68-10.52,10.68
c-5.84,0-10.52-4.67-10.52-10.68c0-2.84,0.67-7.68,0.67-10.68V122.45z"/>
<path class="st0" d="M394.28,171.87c0-2.84,0.83-7.68,0.83-10.68V132.3c0-10.18-5.34-16.86-14.52-16.86c-6.01,0-11.35,3-14.86,8.85
c0.33,1.84,0.5,3.67,0.5,5.68v31.22c0,3.01,0.83,7.85,0.83,10.68c0,6.01-4.67,10.68-10.68,10.68c-5.51,0-10.35-4.67-10.35-10.68
c0-2.84,0.83-7.68,0.83-10.68V131.8c0-3.17-0.5-6.01-1.67-8.51c-2.17-4.84-6.51-7.85-12.52-7.85c-6.18,0-11.19,3.17-14.86,8.85
v36.9c0,3.01,0.83,7.85,0.83,10.68c0,6.01-4.84,10.68-10.52,10.68c-5.84,0-10.52-4.67-10.52-10.68c0-2.84,0.67-7.68,0.67-10.68
v-38.57c0-3.01-1.5-8.35-1.5-10.85c0-6.01,4.34-10.68,10.18-10.68c5.51,0,8.68,3.67,9.68,8.51c5.01-6.68,12.02-10.85,21.2-10.85
c10.85,0,18.7,5.18,23.54,13.52c5.51-8.68,13.52-13.52,23.54-13.52c16.86,0,29.72,12.19,29.72,31.72v30.72
c0,3.01,0.67,7.85,0.67,10.68c0,6.01-4.51,10.68-10.52,10.68C399.12,182.55,394.28,177.88,394.28,171.87z"/>
<path class="st0" d="M528.5,171.87c0-2.84,0.83-7.68,0.83-10.68V132.3c0-10.18-5.34-16.86-14.52-16.86c-6.01,0-11.35,3-14.86,8.85
c0.33,1.84,0.5,3.67,0.5,5.68v31.22c0,3.01,0.84,7.85,0.84,10.68c0,6.01-4.67,10.68-10.68,10.68c-5.51,0-10.35-4.67-10.35-10.68
c0-2.84,0.84-7.68,0.84-10.68V131.8c0-3.17-0.5-6.01-1.67-8.51c-2.17-4.84-6.51-7.85-12.52-7.85c-6.18,0-11.19,3.17-14.86,8.85
v36.9c0,3.01,0.83,7.85,0.83,10.68c0,6.01-4.84,10.68-10.52,10.68c-5.84,0-10.52-4.67-10.52-10.68c0-2.84,0.67-7.68,0.67-10.68
v-38.57c0-3.01-1.5-8.35-1.5-10.85c0-6.01,4.34-10.68,10.18-10.68c5.51,0,8.68,3.67,9.68,8.51c5.01-6.68,12.02-10.85,21.2-10.85
c10.85,0,18.7,5.18,23.54,13.52c5.51-8.68,13.52-13.52,23.54-13.52c16.86,0,29.72,12.19,29.72,31.72v30.72
c0,3.01,0.67,7.85,0.67,10.68c0,6.01-4.51,10.68-10.52,10.68C533.35,182.55,528.5,177.88,528.5,171.87z"/>
<path class="st0" d="M576.92,63.18c6.34,0,11.52,5.18,11.52,11.35c0,6.34-5.18,11.35-11.52,11.35s-11.69-5.01-11.69-11.35
C565.23,68.36,570.57,63.18,576.92,63.18z M567.07,122.45c0-3.01-0.67-7.85-0.67-10.68c0-6.01,4.67-10.68,10.52-10.68
s10.52,4.67,10.52,10.68c0,2.84-0.84,7.68-0.84,10.68v38.73c0,3.01,0.84,7.85,0.84,10.68c0,6.01-4.67,10.68-10.52,10.68
s-10.52-4.67-10.52-10.68c0-2.84,0.67-7.68,0.67-10.68V122.45z"/>
<path class="st0" d="M601.79,141.31c0-23.54,14.69-42.57,39.07-42.57c12.86,0,24.71,5.84,30.05,14.53c2,3.17,2.34,5.01,2.34,6.51
c0,5.18-4.01,9.52-9.85,9.52c-3.84,0-7.34-2.17-8.85-6.01c-2.34-5.18-6.85-8.18-13.69-8.18c-12.86,0-20.03,11.52-20.03,26.04
c0,14.69,7.51,26.04,20.53,26.04c7.01,0,12.02-2.5,14.36-7.68c1.67-3.51,4.84-6.51,9.18-6.51c6.01,0,9.68,4.17,9.68,9.35
c0,2.5-1,5.51-3.17,8.35c-5.51,7.35-15.86,13.19-30.05,13.19C616.32,183.89,601.79,165.19,601.79,141.31z"/>
<path class="st0" d="M737.69,171.87c0-2.84,0.67-7.68,0.67-10.68v-28.55c0-10.18-5.68-17.2-15.36-17.2
c-6.68,0-12.35,3.17-16.03,8.35v37.4c0,3.01,0.67,7.85,0.67,10.68c0,6.01-4.67,10.68-10.52,10.68s-10.52-4.67-10.52-10.68
c0-2.84,0.84-7.68,0.84-10.68v-80.8c0-3.01-0.84-7.85-0.84-10.68c0-6.01,4.84-10.68,10.52-10.68c5.84,0,10.52,4.67,10.52,10.68
c0,2.84-0.67,7.68-0.67,10.68v27.21c5.01-5.51,12.19-8.85,21.37-8.85c17.2,0,29.55,12.86,29.55,31.22v31.22
c0,3.01,0.83,7.85,0.83,10.68c0,6.01-4.84,10.68-10.52,10.68C742.36,182.55,737.69,177.88,737.69,171.87z"/>
</g>
<g>
<path class="st1" d="M114.82,96.21c11.92,10.55,21.52,21.86,27.7,32.52c10.62-18.99,17.71-41.55,17.8-55.92c0-0.1,0-0.19,0-0.28
c0-21.26-21.21-29.54-39.48-29.54s-39.48,8.28-39.48,29.54c0,0.29,0,0.68,0,1.15C91.54,78.2,103.61,86.29,114.82,96.21z"/>
<path class="st2" d="M49.8,154.19c7.45-8.29,18.88-17.27,31.77-24.86c13.72-8.07,27.44-13.71,39.49-16.3
c-14.78-15.96-34.04-29.68-47.68-34.21c-0.1-0.03-0.18-0.06-0.27-0.09c-20.22-6.57-34.65,11.05-40.3,28.42s-4.33,40.11,15.89,46.68
C48.99,153.93,49.35,154.05,49.8,154.19z"/>
<path class="st3" d="M209.07,106.86c-5.65-17.38-20.07-34.99-40.3-28.42c-0.28,0.09-0.65,0.21-1.09,0.35
c-1.16,11.08-5.12,25.07-11.09,38.79c-6.35,14.6-14.14,27.23-22.36,36.39c21.34,4.23,44.99,4,58.68-0.35
c0.1-0.03,0.19-0.06,0.27-0.09C213.4,146.97,214.71,124.24,209.07,106.86z"/>
<path class="st4" d="M102.8,171.18c-3.44-15.54-4.56-30.34-3.3-42.59c-19.75,9.12-38.75,23.2-47.27,34.78
c-0.06,0.08-0.11,0.16-0.16,0.23c-12.5,17.2-0.2,36.37,14.58,47.11s36.81,16.51,49.31-0.69c0.17-0.24,0.4-0.55,0.68-0.93
C111.05,199.44,106.04,185.79,102.8,171.18z"/>
<path class="st5" d="M189.48,162.49c-10.9,2.33-25.42,2.88-40.32,1.44c-15.84-1.53-30.26-5.03-41.52-10.02
c2.57,21.6,10.09,44.02,18.47,55.7c0.06,0.08,0.11,0.16,0.16,0.23c12.5,17.2,34.52,11.43,49.31,0.69
c14.78-10.74,27.08-29.9,14.58-47.11C189.99,163.18,189.76,162.86,189.48,162.49z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Router_Medium_x5F_Black_00000037681990313894948460000012967653829507626171_"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 792 792"
style="enable-background:new 0 0 792 792;" xml:space="preserve">
<style type="text/css">
.st0{fill:#ACCBFA;}
.st1{fill:#FA2921;}
.st2{fill:#ED79B5;}
.st3{fill:#FFB400;}
.st4{fill:#1E83F7;}
.st5{fill:#18C249;}
</style>
<g>
<path class="st0" d="M110.16,537.4c7.85,0,14.25,6.4,14.25,14.04c0,7.85-6.4,14.04-14.25,14.04s-14.45-6.19-14.45-14.04
C95.71,543.8,102.32,537.4,110.16,537.4z M97.98,610.7c0-3.72-0.83-9.71-0.83-13.22c0-7.43,5.78-13.22,13.01-13.22
s13.01,5.78,13.01,13.22c0,3.51-1.03,9.5-1.03,13.22v47.9c0,3.72,1.03,9.71,1.03,13.22c0,7.43-5.78,13.22-13.01,13.22
s-13.01-5.78-13.01-13.22c0-3.51,0.83-9.5,0.83-13.22V610.7z"/>
<path class="st0" d="M265.44,671.82c0-3.51,1.03-9.5,1.03-13.22v-35.72c0-12.6-6.61-20.85-17.96-20.85
c-7.43,0-14.04,3.72-18.38,10.94c0.41,2.27,0.62,4.54,0.62,7.02v38.61c0,3.72,1.03,9.71,1.03,13.22c0,7.43-5.78,13.22-13.22,13.22
c-6.81,0-12.8-5.78-12.8-13.22c0-3.51,1.03-9.5,1.03-13.22v-36.34c0-3.92-0.62-7.43-2.06-10.53c-2.69-5.99-8.05-9.71-15.49-9.71
c-7.64,0-13.83,3.92-18.38,10.94v45.63c0,3.72,1.03,9.71,1.03,13.22c0,7.43-5.99,13.22-13.01,13.22c-7.23,0-13.01-5.78-13.01-13.22
c0-3.51,0.83-9.5,0.83-13.22v-47.7c0-3.72-1.86-10.32-1.86-13.42c0-7.43,5.37-13.22,12.6-13.22c6.81,0,10.74,4.54,11.98,10.53
c6.19-8.26,14.87-13.42,26.22-13.42c13.42,0,23.13,6.4,29.11,16.73c6.81-10.74,16.73-16.73,29.11-16.73
c20.86,0,36.75,15.07,36.75,39.23v37.99c0,3.72,0.83,9.71,0.83,13.22c0,7.43-5.57,13.22-13.01,13.22
C271.43,685.04,265.44,679.26,265.44,671.82z"/>
<path class="st0" d="M431.45,671.82c0-3.51,1.03-9.5,1.03-13.22v-35.72c0-12.6-6.61-20.85-17.96-20.85
c-7.43,0-14.04,3.72-18.38,10.94c0.41,2.27,0.62,4.54,0.62,7.02v38.61c0,3.72,1.03,9.71,1.03,13.22c0,7.43-5.78,13.22-13.22,13.22
c-6.82,0-12.8-5.78-12.8-13.22c0-3.51,1.03-9.5,1.03-13.22v-36.34c0-3.92-0.62-7.43-2.06-10.53c-2.68-5.99-8.05-9.71-15.49-9.71
c-7.64,0-13.83,3.92-18.38,10.94v45.63c0,3.72,1.03,9.71,1.03,13.22c0,7.43-5.99,13.22-13.01,13.22c-7.23,0-13.01-5.78-13.01-13.22
c0-3.51,0.83-9.5,0.83-13.22v-47.7c0-3.72-1.86-10.32-1.86-13.42c0-7.43,5.37-13.22,12.6-13.22c6.82,0,10.74,4.54,11.98,10.53
c6.2-8.26,14.87-13.42,26.22-13.42c13.42,0,23.13,6.4,29.11,16.73c6.81-10.74,16.72-16.73,29.11-16.73
c20.86,0,36.75,15.07,36.75,39.23v37.99c0,3.72,0.83,9.71,0.83,13.22c0,7.43-5.57,13.22-13.01,13.22
C437.44,685.04,431.45,679.26,431.45,671.82z"/>
<path class="st0" d="M491.33,537.4c7.85,0,14.25,6.4,14.25,14.04c0,7.85-6.4,14.04-14.25,14.04s-14.45-6.19-14.45-14.04
C476.87,543.8,483.48,537.4,491.33,537.4z M479.15,610.7c0-3.72-0.83-9.71-0.83-13.22c0-7.43,5.78-13.22,13.01-13.22
s13.01,5.78,13.01,13.22c0,3.51-1.03,9.5-1.03,13.22v47.9c0,3.72,1.03,9.71,1.03,13.22c0,7.43-5.78,13.22-13.01,13.22
s-13.01-5.78-13.01-13.22c0-3.51,0.83-9.5,0.83-13.22V610.7z"/>
<path class="st0" d="M522.09,634.04c0-29.11,18.17-52.65,48.32-52.65c15.9,0,30.56,7.23,37.17,17.97c2.48,3.92,2.89,6.19,2.89,8.05
c0,6.4-4.96,11.77-12.18,11.77c-4.75,0-9.08-2.68-10.94-7.43c-2.89-6.4-8.47-10.12-16.93-10.12c-15.9,0-24.78,14.25-24.78,32.21
c0,18.17,9.29,32.21,25.4,32.21c8.67,0,14.87-3.1,17.76-9.5c2.06-4.34,5.99-8.05,11.36-8.05c7.43,0,11.98,5.16,11.98,11.56
c0,3.1-1.24,6.81-3.92,10.32c-6.82,9.09-19.62,16.31-37.17,16.31C540.06,686.69,522.09,663.56,522.09,634.04z"/>
<path class="st0" d="M690.17,671.82c0-3.51,0.83-9.5,0.83-13.22v-35.3c0-12.6-7.02-21.27-19-21.27c-8.26,0-15.28,3.92-19.82,10.32
v46.25c0,3.72,0.83,9.71,0.83,13.22c0,7.43-5.78,13.22-13.01,13.22s-13.01-5.78-13.01-13.22c0-3.51,1.03-9.5,1.03-13.22v-99.94
c0-3.72-1.03-9.71-1.03-13.22c0-7.43,5.99-13.22,13.01-13.22c7.23,0,13.01,5.78,13.01,13.22c0,3.51-0.83,9.5-0.83,13.22v33.66
c6.2-6.81,15.07-10.94,26.43-10.94c21.27,0,36.55,15.9,36.55,38.61v38.61c0,3.72,1.03,9.71,1.03,13.22
c0,7.43-5.99,13.22-13.01,13.22C695.95,685.04,690.17,679.26,690.17,671.82z"/>
</g>
<g>
<path class="st1" d="M376.76,216.42c28.32,25.07,51.15,51.95,65.83,77.27c25.23-45.12,42.08-98.73,42.3-132.88
c0-0.24,0-0.46,0-0.66c0-50.53-50.41-70.2-93.82-70.2s-93.82,19.66-93.82,70.2c0,0.69,0,1.62,0,2.73
C321.44,173.62,350.14,192.84,376.76,216.42z"/>
<path class="st2" d="M222.27,354.21c17.7-19.69,44.85-41.04,75.5-59.08c32.6-19.19,65.21-32.59,93.83-38.73
c-35.11-37.94-80.89-70.53-113.31-81.29c-0.23-0.07-0.44-0.14-0.63-0.21c-48.06-15.61-82.34,26.25-95.75,67.54
c-13.42,41.29-10.29,95.31,37.77,110.92C220.33,353.58,221.21,353.86,222.27,354.21z"/>
<path class="st3" d="M600.73,241.74c-13.42-41.29-47.69-83.15-95.75-67.54c-0.66,0.21-1.54,0.5-2.6,0.84
c-2.75,26.34-12.16,59.57-26.36,92.17c-15.09,34.68-33.6,64.69-53.14,86.48c50.7,10.05,106.9,9.52,139.45-0.83
c0.23-0.07,0.44-0.14,0.63-0.21C611.02,337.05,614.15,283.03,600.73,241.74z"/>
<path class="st4" d="M348.22,394.58c-8.17-36.93-10.84-72.09-7.84-101.2c-46.93,21.67-92.08,55.14-112.33,82.64
c-0.14,0.19-0.27,0.37-0.39,0.54c-29.7,40.88-0.48,86.42,34.64,111.94s87.46,39.24,117.16-1.64c0.41-0.56,0.95-1.31,1.6-2.21
C367.81,461.72,355.9,429.3,348.22,394.58z"/>
<path class="st5" d="M554.19,373.91c-25.9,5.53-60.41,6.84-95.81,3.42c-37.65-3.64-71.91-11.96-98.67-23.82
c6.11,51.33,23.99,104.61,43.89,132.37c0.14,0.19,0.27,0.37,0.39,0.54c29.7,40.88,82.04,27.16,117.16,1.64S585.5,417,555.8,376.12
C555.39,375.56,554.85,374.81,554.19,373.91z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Flower" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 792 792" style="enable-background:new 0 0 792 792;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FA2921;}
.st1{fill:#ED79B5;}
.st2{fill:#FFB400;}
.st3{fill:#1E83F7;}
.st4{fill:#18C249;}
</style>
<g id="Flower_00000077325900055813483940000000694823054982625702_">
<path class="st0" d="M375.48,267.63c38.64,34.21,69.78,70.87,89.82,105.42c34.42-61.56,57.42-134.71,57.71-181.3
c0-0.33,0-0.63,0-0.91c0-68.94-68.77-95.77-128.01-95.77s-128.01,26.83-128.01,95.77c0,0.94,0,2.2,0,3.72
C300.01,209.24,339.15,235.47,375.48,267.63z"/>
<path class="st1" d="M164.7,455.63c24.15-26.87,61.2-55.99,103.01-80.61c44.48-26.18,88.97-44.47,128.02-52.84
c-47.91-51.76-110.37-96.24-154.6-110.91c-0.31-0.1-0.6-0.19-0.86-0.28c-65.57-21.3-112.34,35.81-130.64,92.15
c-18.3,56.34-14.04,130.04,51.53,151.34C162.05,454.77,163.25,455.16,164.7,455.63z"/>
<path class="st2" d="M681.07,302.19c-18.3-56.34-65.07-113.45-130.64-92.15c-0.9,0.29-2.1,0.68-3.54,1.15
c-3.75,35.93-16.6,81.27-35.96,125.76c-20.59,47.32-45.84,88.27-72.51,118c69.18,13.72,145.86,12.98,190.26-1.14
c0.31-0.1,0.6-0.2,0.86-0.28C695.11,432.22,699.37,358.52,681.07,302.19z"/>
<path class="st3" d="M336.54,510.71c-11.15-50.39-14.8-98.36-10.7-138.08c-64.03,29.57-125.63,75.23-153.26,112.76
c-0.19,0.26-0.37,0.51-0.53,0.73c-40.52,55.78-0.66,117.91,47.27,152.72c47.92,34.82,119.33,53.54,159.86-2.24
c0.56-0.76,1.3-1.78,2.19-3.01C363.28,602.32,347.02,558.08,336.54,510.71z"/>
<path class="st4" d="M617.57,482.52c-35.33,7.54-82.42,9.33-130.72,4.66c-51.37-4.96-98.11-16.32-134.63-32.5
c8.33,70.03,32.73,142.73,59.88,180.6c0.19,0.26,0.37,0.51,0.53,0.73c40.52,55.78,111.93,37.06,159.86,2.24
c47.92-34.82,87.79-96.95,47.27-152.72C619.2,484.77,618.46,483.75,617.57,482.52z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+17
View File
@@ -0,0 +1,17 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
fallback: 'index.html',
}),
},
};
export default config;
+38
View File
@@ -0,0 +1,38 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./{apps,src}/**/*.{html,js,svelte,ts}'],
darkMode: 'class',
theme: {
extend: {
colors: {
// Light Theme
'immich-primary': 'rgb(var(--immich-primary) / <alpha-value>)',
'immich-bg': 'rgb(var(--immich-bg) / <alpha-value>)',
'immich-fg': 'rgb(var(--immich-fg) / <alpha-value>)',
'immich-gray': 'rgb(var(--immich-gray) / <alpha-value>)',
'immich-error': 'rgb(var(--immich-error) / <alpha-value>)',
'immich-success': 'rgb(var(--immich-success) / <alpha-value>)',
'immich-warning': 'rgb(var(--immich-warning) / <alpha-value>)',
// Dark Theme
'immich-dark-primary': 'rgb(var(--immich-dark-primary) / <alpha-value>)',
'immich-dark-bg': 'rgb(var(--immich-dark-bg) / <alpha-value>)',
'immich-dark-fg': 'rgb(var(--immich-dark-fg) / <alpha-value>)',
'immich-dark-gray': 'rgb(var(--immich-dark-gray) / <alpha-value>)',
'immich-dark-error': 'rgb(var(--immich-dark-error) / <alpha-value>)',
'immich-dark-success': 'rgb(var(--immich-dark-success) / <alpha-value>)',
'immich-dark-warning': 'rgb(var(--immich-dark-warning) / <alpha-value>)',
},
fontFamily: {
'immich-title': ['Snowburst One', 'cursive'],
'immich-mono': ['Overpass Mono', 'monospace'],
},
spacing: {
18: '4.5rem',
},
screens: {
tall: { raw: '(min-height: 800px)' },
},
},
},
};
+19
View File
@@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
+9
View File
@@ -0,0 +1,9 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
},
});