Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ab5a080a9 | ||
|
|
60ff980809 | ||
|
|
e2c8ba10ce | ||
|
|
515678f035 | ||
|
|
e8a5768a2f | ||
|
|
2a6397b964 | ||
|
|
de808dbb67 | ||
|
|
f63563ce16 | ||
|
|
8d5d8ac2d7 | ||
|
|
59b81396f9 | ||
|
|
4087599629 | ||
|
|
577c7d0d5b |
@@ -12,9 +12,10 @@ run-name: "Release ${{ gitea.event.inputs.tag || gitea.ref_name }}"
|
|||||||
# work-in-progress commits never ship.
|
# work-in-progress commits never ship.
|
||||||
#
|
#
|
||||||
# Local flow: `./release 1.6.7` (or `make release VERSION=1.6.7`) bumps
|
# Local flow: `./release 1.6.7` (or `make release VERSION=1.6.7`) bumps
|
||||||
# wails.json, commits the bump, creates an annotated tag carrying the
|
# build/config.yml (and the generated version.go), commits the bump,
|
||||||
# git-cliff notes, and pushes commit + tag. This workflow verifies
|
# creates an annotated tag carrying the git-cliff notes, and pushes
|
||||||
# wails.json matches the tag before building.
|
# commit + tag. This workflow verifies build/config.yml matches the tag
|
||||||
|
# before building.
|
||||||
#
|
#
|
||||||
# Secrets test (verifies environment.go wiring without shipping): tag the
|
# Secrets test (verifies environment.go wiring without shipping): tag the
|
||||||
# current bumped commit with a suffix and push, e.g.
|
# current bumped commit with a suffix and push, e.g.
|
||||||
@@ -49,7 +50,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ gitea.event.inputs.tag || gitea.ref }}
|
ref: ${{ gitea.event.inputs.tag || gitea.ref }}
|
||||||
|
|
||||||
- name: Guard wails.json matches tag
|
- name: Guard build/config.yml matches tag
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
: "${TAG:?TAG is required}"
|
: "${TAG:?TAG is required}"
|
||||||
@@ -58,21 +59,21 @@ jobs:
|
|||||||
NORM="${TAG#v}"
|
NORM="${TAG#v}"
|
||||||
NORM="${NORM#AniTrack-}"
|
NORM="${NORM#AniTrack-}"
|
||||||
BASE="${NORM%%-*}"
|
BASE="${NORM%%-*}"
|
||||||
WAILS_VER="$(python3 -c "import json; print(json.load(open('wails.json'))['info']['productVersion'])")"
|
CFG_VER="$(python3 -c "import re; print(re.search(r'^ version: \"([^\"]*)\"', open('build/config.yml').read(), flags=re.M).group(1))")"
|
||||||
if [ "${NORM}" = "${BASE}" ]; then
|
if [ "${NORM}" = "${BASE}" ]; then
|
||||||
if [ "${WAILS_VER}" != "${NORM}" ]; then
|
if [ "${CFG_VER}" != "${NORM}" ]; then
|
||||||
echo "::error::wails.json productVersion (${WAILS_VER}) != tag (${NORM}). Bump via ./release ${NORM} first." >&2
|
echo "::error::build/config.yml info.version (${CFG_VER}) != tag (${NORM}). Bump via ./release ${NORM} first." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Pre-release (e.g. 1.6.7-rc1): wails.json must match the base version.
|
# Pre-release (e.g. 1.6.7-rc1): build/config.yml must match the base version.
|
||||||
if [ "${WAILS_VER}" != "${BASE}" ]; then
|
if [ "${CFG_VER}" != "${BASE}" ]; then
|
||||||
echo "::error::wails.json productVersion (${WAILS_VER}) != tag base (${BASE}). Bump via ./release ${BASE} first." >&2
|
echo "::error::build/config.yml info.version (${CFG_VER}) != tag base (${BASE}). Bump via ./release ${BASE} first." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
echo "VERSION=${NORM}" >> "${GITHUB_ENV}"
|
echo "VERSION=${NORM}" >> "${GITHUB_ENV}"
|
||||||
echo "Version guard passed: wails.json=${WAILS_VER} tag=${NORM}"
|
echo "Version guard passed: build/config.yml=${CFG_VER} tag=${NORM}"
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
@@ -119,13 +120,13 @@ jobs:
|
|||||||
# The compiled CLI binary. Bump the key whenever the @version pin
|
# The compiled CLI binary. Bump the key whenever the @version pin
|
||||||
# below changes, or the old CLI will be silently reused.
|
# below changes, or the old CLI will be silently reused.
|
||||||
path: ~/go/bin/wails3
|
path: ~/go/bin/wails3
|
||||||
key: wails3-v3.0.0-beta.20-${{ runner.os }}
|
key: wails3-v3.0.0-beta.22-${{ runner.os }}
|
||||||
|
|
||||||
- name: Install Wails CLI
|
- name: Install Wails CLI
|
||||||
if: steps.wails-cli.outputs.cache-hit != 'true'
|
if: steps.wails-cli.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.20
|
go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.22
|
||||||
|
|
||||||
- name: Add Go bin to PATH
|
- name: Add Go bin to PATH
|
||||||
run: echo "${HOME}/go/bin" >> "${GITHUB_PATH}"
|
run: echo "${HOME}/go/bin" >> "${GITHUB_PATH}"
|
||||||
|
|||||||
@@ -11,22 +11,26 @@ build:
|
|||||||
clean:
|
clean:
|
||||||
rm -rf build/bin/*
|
rm -rf build/bin/*
|
||||||
|
|
||||||
# Create a version commit (wails.json bump, committed and pushed) plus an
|
# Create a version commit (version bump, committed and pushed) plus an
|
||||||
# annotated version tag carrying auto-generated release notes (git-cliff),
|
# annotated version tag carrying auto-generated release notes (git-cliff),
|
||||||
# and push both. The tag push triggers .gitea/workflows/release.yml, which
|
# and push both. The tag push triggers .gitea/workflows/release.yml, which
|
||||||
# verifies wails.json matches the tag, builds via `make build`, packages
|
# verifies build/config.yml matches the tag, builds via `make build`, packages
|
||||||
# AniTrack-<VERSION>.tar.gz from build/, and publishes a Gitea Release
|
# AniTrack-<VERSION>.tar.gz from build/, and publishes a Gitea Release
|
||||||
# named AniTrack-<VERSION> with the archive attached. Notes come entirely
|
# named AniTrack-<VERSION> with the archive attached. Notes come entirely
|
||||||
# from Conventional Commits — no hand-written message required.
|
# from Conventional Commits — no hand-written message required.
|
||||||
#
|
#
|
||||||
|
# The single version source is build/config.yml info.version. `make release`
|
||||||
|
# bumps it and regenerates version.go (the compiled-in copy) from it, so the
|
||||||
|
# two can never drift; both ride the same bump commit.
|
||||||
|
#
|
||||||
# git-cliff's --latest needs the tag to exist to scope the notes, so we
|
# git-cliff's --latest needs the tag to exist to scope the notes, so we
|
||||||
# create a throwaway lightweight tag, generate the notes, replace it with
|
# create a throwaway lightweight tag, generate the notes, replace it with
|
||||||
# an annotated tag, then push. --cleanup=verbatim keeps the markdown "###"
|
# an annotated tag, then push. --cleanup=verbatim keeps the markdown "###"
|
||||||
# group headers (git's default cleanup would strip lines starting with "#").
|
# group headers (git's default cleanup would strip lines starting with "#").
|
||||||
#
|
#
|
||||||
# Tags are plain versions (1.6.7) to match wails.json productVersion and the
|
# Tags are plain versions (1.6.7) to match build/config.yml info.version and
|
||||||
# existing tag history. Pre-releases are created by tagging manually with a
|
# the existing tag history. Pre-releases are created by tagging manually with
|
||||||
# suffix (e.g. 1.6.7-rc1 on top of the bumped commit) — the workflow marks
|
# a suffix (e.g. 1.6.7-rc1 on top of the bumped commit) — the workflow marks
|
||||||
# those as prerelease. Use ./release as a shortcut.
|
# those as prerelease. Use ./release as a shortcut.
|
||||||
#
|
#
|
||||||
# Requires git-cliff: https://git-cliff.org/install
|
# Requires git-cliff: https://git-cliff.org/install
|
||||||
@@ -35,17 +39,14 @@ release:
|
|||||||
@test -n "$(VERSION)" || { echo "Usage: make release VERSION=1.6.7"; exit 1; }
|
@test -n "$(VERSION)" || { echo "Usage: make release VERSION=1.6.7"; exit 1; }
|
||||||
@echo "$(VERSION)" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "VERSION must be plain semver like 1.6.7 (no v prefix, no AniTrack- prefix, no suffix)"; exit 1; }
|
@echo "$(VERSION)" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "VERSION must be plain semver like 1.6.7 (no v prefix, no AniTrack- prefix, no suffix)"; exit 1; }
|
||||||
@command -v git-cliff >/dev/null 2>&1 || { echo "git-cliff not found — install: https://git-cliff.org/install"; exit 1; }
|
@command -v git-cliff >/dev/null 2>&1 || { echo "git-cliff not found — install: https://git-cliff.org/install"; exit 1; }
|
||||||
@command -v python3 >/dev/null 2>&1 || { echo "python3 not found — required to bump wails.json"; exit 1; }
|
@command -v python3 >/dev/null 2>&1 || { echo "python3 not found — required to bump versions"; exit 1; }
|
||||||
@git diff --quiet && git diff --cached --quiet || { echo "Working tree dirty — commit or stash first"; exit 1; }
|
@git diff --quiet && git diff --cached --quiet || { echo "Working tree dirty — commit or stash first"; exit 1; }
|
||||||
@if git rev-parse "$(VERSION)" >/dev/null 2>&1; then echo "Tag $(VERSION) already exists locally — delete it first: git tag -d $(VERSION)"; exit 1; fi
|
@if git rev-parse "$(VERSION)" >/dev/null 2>&1; then echo "Tag $(VERSION) already exists locally — delete it first: git tag -d $(VERSION)"; exit 1; fi
|
||||||
@echo "Bumping wails.json to $(VERSION)..."
|
|
||||||
@python3 -c "import json; p='wails.json'; d=json.load(open(p)); d['info']['productVersion']='$(VERSION)'; json.dump(d, open(p,'w'), indent=2); open(p,'a').write('\n')"
|
|
||||||
# TEMP until wails.json (v2 leftover) is retired: bump build/config.yml too.
|
|
||||||
# The regex targets only the indented info.version line, never the
|
|
||||||
# top-level schema `version: '3'`.
|
|
||||||
@echo "Bumping build/config.yml to $(VERSION)..."
|
@echo "Bumping build/config.yml to $(VERSION)..."
|
||||||
@python3 -c "import re; p='build/config.yml'; s=open(p).read(); m=re.search(r'^ version: \"([^\"]*)\"', s, flags=re.M); assert m, 'info.version not found'; print('build/config.yml', m.group(1), '-> $(VERSION)' if m.group(1) != '$(VERSION)' else '(already at version)'); open(p,'w').write(re.sub(r'^ version: \"[^\"]*\"', ' version: \"$(VERSION)\"', s, count=1, flags=re.M))"
|
@python3 -c "import re; p='build/config.yml'; s=open(p).read(); m=re.search(r'^ version: \"([^\"]*)\"', s, flags=re.M); assert m, 'info.version not found'; print('build/config.yml', m.group(1), '-> $(VERSION)' if m.group(1) != '$(VERSION)' else '(already at version)'); open(p,'w').write(re.sub(r'^ version: \"[^\"]*\"', ' version: \"$(VERSION)\"', s, count=1, flags=re.M))"
|
||||||
@git add wails.json build/config.yml
|
@echo "Regenerating version.go from build/config.yml..."
|
||||||
|
@python3 -c "import re; v=re.search(r'^ version: \"([^\"]*)\"', open('build/config.yml').read(), flags=re.M).group(1); open('version.go','w').write('package main\n\n// Code generated by \`make release\` from build/config.yml info.version.\n// DO NOT EDIT.\nconst appVersionString = \"%s\"\n' % v)"
|
||||||
|
@git add build/config.yml version.go
|
||||||
@git diff --cached --quiet && echo "versions already at $(VERSION), skipping bump commit" || git commit -m "chore(release): bump version to $(VERSION)"
|
@git diff --cached --quiet && echo "versions already at $(VERSION), skipping bump commit" || git commit -m "chore(release): bump version to $(VERSION)"
|
||||||
@echo "Generating release notes for $(VERSION)..."
|
@echo "Generating release notes for $(VERSION)..."
|
||||||
@git tag "$(VERSION)" HEAD && \
|
@git tag "$(VERSION)" HEAD && \
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed wails.json
|
|
||||||
var wailsJSON string
|
|
||||||
|
|
||||||
// App struct
|
// App struct
|
||||||
type App struct {
|
type App struct {
|
||||||
app *application.App
|
app *application.App
|
||||||
@@ -21,9 +16,10 @@ func NewApp(app *application.App) *App {
|
|||||||
return &App{app: app}
|
return &App{app: app}
|
||||||
}
|
}
|
||||||
|
|
||||||
// appVersion reads the product version from wails.json.
|
// appVersion is the release version, stamped into version.go by
|
||||||
|
// `make release` from build/config.yml.
|
||||||
func appVersion() string {
|
func appVersion() string {
|
||||||
return gjson.Get(wailsJSON, "info.productVersion").String()
|
return appVersionString
|
||||||
}
|
}
|
||||||
|
|
||||||
// appTitle is the window title: "AniTrack <version>".
|
// appTitle is the window title: "AniTrack <version>".
|
||||||
|
|||||||
+3
-2
@@ -1,5 +1,6 @@
|
|||||||
# Wails v3 project configuration.
|
# Wails v3 project configuration.
|
||||||
# NOTE: `make release` bumps `info.version` below alongside wails.json.
|
# NOTE: `info.version` below is the single version source: `make release`
|
||||||
|
# bumps it and regenerates version.go (the compiled-in copy) from it.
|
||||||
# Never touch the top-level `version: '3'` — that is the config file schema,
|
# Never touch the top-level `version: '3'` — that is the config file schema,
|
||||||
# not the app version.
|
# not the app version.
|
||||||
version: '3'
|
version: '3'
|
||||||
@@ -10,7 +11,7 @@ info:
|
|||||||
productIdentifier: "com.linuxhg.anitrack"
|
productIdentifier: "com.linuxhg.anitrack"
|
||||||
description: "Track anime watchlists across AniList, MyAnimeList, and Simkl"
|
description: "Track anime watchlists across AniList, MyAnimeList, and Simkl"
|
||||||
copyright: "John O'Keefe"
|
copyright: "John O'Keefe"
|
||||||
version: "1.99.1"
|
version: "2.0.0"
|
||||||
|
|
||||||
# Dev mode configuration
|
# Dev mode configuration
|
||||||
dev_mode:
|
dev_mode:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ This document summarizes the `wailsv3` branch (Wails `v2.15.0` → `v3.0.0-beta.
|
|||||||
|
|
||||||
**Build + CI:**
|
**Build + CI:**
|
||||||
- `Taskfile.yml` + `build/Taskfile.yml` (stock `common`) + `build/linux/Taskfile.yml` (stock minus `common:generate:icons` — macOS/Windows icon generation targets `build/appicon.png` which this Linux-only project doesn’t have) + `build/config.yml` (AniTrack metadata, `Version 1.6.8`). Root output stays `build/bin/AniTrack` so release packaging is untouched. `wails.json` → `v3` `frontend` block, `info.productVersion` retained.
|
- `Taskfile.yml` + `build/Taskfile.yml` (stock `common`) + `build/linux/Taskfile.yml` (stock minus `common:generate:icons` — macOS/Windows icon generation targets `build/appicon.png` which this Linux-only project doesn’t have) + `build/config.yml` (AniTrack metadata, `Version 1.6.8`). Root output stays `build/bin/AniTrack` so release packaging is untouched. `wails.json` → `v3` `frontend` block, `info.productVersion` retained.
|
||||||
- `Makefile`: `wails dev/build -tags webkit2_41` → `wails3 dev -port 5173` / `wails3 build` (no tags; `v3` defaults to `GTK4/WebKitGTK 6.0`, same `2.52.x` engine generation). Added `.task/` to `.gitignore`. Removed `build/darwin/`, `build/windows/`, `frontend/package.json.md5`. `make release` bumps both `wails.json` and `build/config.yml:info.version` (TEMP until `wails.json` is retired).
|
- `Makefile`: `wails dev/build -tags webkit2_41` → `wails3 dev -port 5173` / `wails3 build` (no tags; `v3` defaults to `GTK4/WebKitGTK 6.0`, same `2.52.x` engine generation). Added `.task/` to `.gitignore`. Removed `build/darwin/`, `build/windows/`, `frontend/package.json.md5`. Version flow: single source `build/config.yml:info.version`, `make release` bumps it and regenerates `version.go` (compiled-in copy) in the same commit — `wails.json` retired entirely (proof: `wails3 build` + `wails3 dev` both green without it; a first-boot `dev` failure turned out to be Vite 8 cold-start vs the app retry budget, fixed by rerunning warm, not by the file).
|
||||||
- `.gitea/workflows/release.yml`: Wails CLI `wails@v2.15.0` → `wails3@v3.0.0-beta.20` (path + cache key), apt `libgtk-3-dev/libwebkit2gtk-4.1-dev` → `libgtk-4-dev/libwebkitgtk-6.0-dev`, `make build` unchanged. Go/npm/go-build/Wails-CLI caches remain valid (npm cache now resolves since `frontend/package-lock.json` is committed).
|
- `.gitea/workflows/release.yml`: Wails CLI `wails@v2.15.0` → `wails3@v3.0.0-beta.20` (path + cache key), apt `libgtk-3-dev/libwebkit2gtk-4.1-dev` → `libgtk-4-dev/libwebkitgtk-6.0-dev`, `make build` unchanged. Go/npm/go-build/Wails-CLI caches remain valid (npm cache now resolves since `frontend/package-lock.json` is committed).
|
||||||
|
|
||||||
**Verification:** `go vet` clean, `wails3 build` green (`build/bin/AniTrack` ~13 MB), `wails3 dev` boots end-to-end (bindings regen → `build:dev` → vite on `:5173` → `Connected to frontend dev server` → `AniTrack 1.6.8` window), second instance exits 0 via the lock. `svelte-check` was already red on `main` (`wailsjs/go/models.ts` anonymous structs) and remains so — not introduced by the migration.
|
**Verification:** `go vet` clean, `wails3 build` green (`build/bin/AniTrack` ~13 MB), `wails3 dev` boots end-to-end (bindings regen → `build:dev` → vite on `:5173` → `Connected to frontend dev server` → `AniTrack 1.6.8` window), second instance exits 0 via the lock. `svelte-check` was already red on `main` (`wailsjs/go/models.ts` anonymous structs) and remains so — not introduced by the migration.
|
||||||
@@ -56,7 +56,7 @@ secret, never in the repo (see `.gitignore`).
|
|||||||
**Not yet done — staged after the desktop migration, in order** (each with its home branch, so nothing lands in the wrong place):
|
**Not yet done — staged after the desktop migration, in order** (each with its home branch, so nothing lands in the wrong place):
|
||||||
|
|
||||||
1. **Stabilize desktop `v3`:** real-world dogfooding of `main` (logins, watchlist sync, error modals). Merged to `main` as the `1.99.x` beta series while Wails `v3` is still beta upstream.
|
1. **Stabilize desktop `v3`:** real-world dogfooding of `main` (logins, watchlist sync, error modals). Merged to `main` as the `1.99.x` beta series while Wails `v3` is still beta upstream.
|
||||||
2. **Frontend toolchain refresh (on `wailsv3`, before the merge):** Svelte 4 → 5, Vite 4 → 8, `vite-plugin-svelte 2 → 7`, Tailwind 3 → 4 — majors deferred intentionally; they pair naturally with `v3`’s Svelte 5 templates and touch the same desktop files, so no separate branch.
|
2. **Frontend toolchain refresh:** done on `wailsv3` — Svelte 4.2 → 5.57, Vite 4.5 → 8.3, `vite-plugin-svelte 2 → 7`, Tailwind 3 → 4 (CSS-first), flowbite-svelte 0.46 → 1.33, router 4 → 5. `svelte-check` went 453 errors → 0/0; stores retyped to the generated bindings models; AnimeTable rewritten dependency-free (`svelte-headless-table` peers Svelte 4 only). Deliberately held: TypeScript at 5.9 (`svelte-check` peers `^5||^6`), `@wailsio/runtime` pinned to the Go framework version (lockstep upgrades only).
|
||||||
3. **Android (short-lived feature branch off `wailsv3`, e.g. `wailsv3-android`, merged back):** personal sideload, no Play/official F-Droid. Per-file `//go:build android` shims — `zalando` → `Android.Secure*` (`EncryptedSharedPreferences`), and `localhost:6734` OAuth callbacks → deep-link `anitrack://callback` + intent filter; start with one provider (AniList) to prove the pattern. Responsive / safe-area polish. Distribution via `adb install` + GitHub Releases + Obtainium. Kept off `wailsv3` proper so the mobile scaffolding (`build/android/`, manifests, gradle files) doesn't pollute the desktop-to-`main` merge.
|
3. **Android (short-lived feature branch off `wailsv3`, e.g. `wailsv3-android`, merged back):** personal sideload, no Play/official F-Droid. Per-file `//go:build android` shims — `zalando` → `Android.Secure*` (`EncryptedSharedPreferences`), and `localhost:6734` OAuth callbacks → deep-link `anitrack://callback` + intent filter; start with one provider (AniList) to prove the pattern. Responsive / safe-area polish. Distribution via `adb install` + GitHub Releases + Obtainium. Kept off `wailsv3` proper so the mobile scaffolding (`build/android/`, manifests, gradle files) doesn't pollute the desktop-to-`main` merge.
|
||||||
4. **CI follow-ups (wherever the work they support lives):** none required for desktop; the current 4 caches + the fixed runner cache backend already bring releases from ~12 min to ~3 min. Builder image only if the 2 min apt floor becomes annoying.
|
4. **CI follow-ups (wherever the work they support lives):** none required for desktop; the current 4 caches + the fixed runner cache backend already bring releases from ~12 min to ~3 min. Builder image only if the 2 min apt floor becomes annoying.
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,5 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="./src/main.ts" type="module"></script>
|
<script src="./src/main.ts" type="module"></script>
|
||||||
<script
|
|
||||||
src="./node_modules/flowbite/dist/flowbite.js"
|
|
||||||
type="module"
|
|
||||||
></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Generated
+1465
-2007
File diff suppressed because it is too large
Load Diff
+15
-18
@@ -11,26 +11,23 @@
|
|||||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.4.1",
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
||||||
"@tsconfig/svelte": "^4.0.1",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"autoprefixer": "^10.4.20",
|
"@tsconfig/svelte": "^5.0.8",
|
||||||
"postcss": "^8.4.45",
|
"svelte": "^5.57.0",
|
||||||
"svelte": "^4.0.0",
|
"svelte-check": "^4.7.6",
|
||||||
"svelte-check": "^3.4.3",
|
"svelte-spa-router": "^5.1.1",
|
||||||
"svelte-headless-table": "^0.18.3",
|
"tailwind-merge": "^3.7.0",
|
||||||
"svelte-preprocess": "^5.0.3",
|
"tailwindcss": "^4.3.3",
|
||||||
"svelte-spa-router": "^4.0.1",
|
"tslib": "^2.8.0",
|
||||||
"tailwind-merge": "^2.5.2",
|
"typescript": "^5.9.3",
|
||||||
"tailwindcss": "^3.4.10",
|
"vite": "^8.3.0"
|
||||||
"tslib": "^2.7.0",
|
|
||||||
"typescript": "^5.0.0",
|
|
||||||
"vite": "^4.5.5"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@wailsio/runtime": "3.0.0-beta.20",
|
"@wailsio/runtime": "3.0.0-beta.22",
|
||||||
"flowbite": "^2.5.1",
|
"flowbite": "^4.0.2",
|
||||||
"flowbite-svelte": "^0.46.16",
|
"flowbite-svelte": "^1.33.1",
|
||||||
"moment": "^2.30.1"
|
"moment": "^2.31.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
+25
-17
@@ -21,13 +21,12 @@
|
|||||||
import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte";
|
import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte";
|
||||||
import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte";
|
import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte";
|
||||||
import {App} from "../bindings/AniTrack";
|
import {App} from "../bindings/AniTrack";
|
||||||
import { loc } from "svelte-spa-router";
|
|
||||||
import ErrorModal from "./helperComponents/ErrorModal.svelte";
|
import ErrorModal from "./helperComponents/ErrorModal.svelte";
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let isAniListLoggedIn: boolean;
|
let isAniListLoggedIn!: boolean;
|
||||||
let isMALLoggedIn: boolean;
|
let isMALLoggedIn!: boolean;
|
||||||
let isSimklLoggedIn: boolean;
|
let isSimklLoggedIn!: boolean;
|
||||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||||
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
|
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
|
||||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||||
@@ -39,26 +38,35 @@
|
|||||||
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
|
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
|
||||||
});
|
});
|
||||||
|
|
||||||
$: if ($loc?.location === "/" && $watchlistNeedsRefresh) {
|
import { get } from "svelte/store";
|
||||||
(async () => {
|
|
||||||
if ($aniListLoggedIn && $aniListPrimary) {
|
|
||||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
|
||||||
}
|
|
||||||
if ($malLoggedIn && $malPrimary) {
|
|
||||||
await App.GetMyAnimeList(1000).then((w) => malWatchList.set(w));
|
|
||||||
}
|
|
||||||
if ($simklLoggedIn && $simklPrimary) {
|
|
||||||
await App.SimklGetUserWatchlist().then((w) => simklWatchList.set(w));
|
|
||||||
}
|
|
||||||
|
|
||||||
watchlistNeedsRefresh.set(false);
|
// Reloads all watchlists when returning home with the refresh flag set
|
||||||
})();
|
// (e.g. after saving changes on a detail page). Driven by the router's
|
||||||
|
// onRouteLoaded callback: the old `$: ... router.location ...` reactive
|
||||||
|
// statement did not reliably re-fire on navigation under Svelte 5,
|
||||||
|
// so homecoming refreshes silently never ran.
|
||||||
|
async function refreshHomeWatchlists(location: string): Promise<void> {
|
||||||
|
if (location !== "/" || !get(watchlistNeedsRefresh)) return;
|
||||||
|
if (get(aniListLoggedIn) && get(aniListPrimary)) {
|
||||||
|
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||||
|
}
|
||||||
|
if (get(malLoggedIn) && get(malPrimary)) {
|
||||||
|
await App.GetMyAnimeList(1000).then((w) => malWatchList.set(w));
|
||||||
|
}
|
||||||
|
if (get(simklLoggedIn) && get(simklPrimary)) {
|
||||||
|
await App.SimklGetUserWatchlist().then((w) => simklWatchList.set(w));
|
||||||
|
}
|
||||||
|
|
||||||
|
watchlistNeedsRefresh.set(false);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
<ErrorModal />
|
<ErrorModal />
|
||||||
<Router
|
<Router
|
||||||
|
onRouteLoaded={(detail) => {
|
||||||
|
void refreshHomeWatchlists(detail.location);
|
||||||
|
}}
|
||||||
routes={{
|
routes={{
|
||||||
"/": Home,
|
"/": Home,
|
||||||
"/anime/:id": wrap({
|
"/anime/:id": wrap({
|
||||||
|
|||||||
@@ -11,22 +11,22 @@
|
|||||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||||
import { push } from "svelte-spa-router";
|
import { push } from "svelte-spa-router";
|
||||||
import WebsiteLink from "./WebsiteLink.svelte";
|
import WebsiteLink from "./WebsiteLink.svelte";
|
||||||
import type { AniListGetSingleAnime } from "../anilist/types/AniListCurrentUserWatchListType";
|
import type {
|
||||||
|
AniListGetSingleAnime,
|
||||||
|
AniListUpdateVariables,
|
||||||
|
MALAnime,
|
||||||
|
MalListStatus,
|
||||||
|
MALUploadStatus,
|
||||||
|
SimklAnime,
|
||||||
|
} from "../../bindings/AniTrack/models";
|
||||||
import Rating from "./Rating.svelte";
|
import Rating from "./Rating.svelte";
|
||||||
import {
|
import {
|
||||||
convertAniListDateToString,
|
convertAniListDateToString,
|
||||||
convertAniListDateToDate,
|
convertAniListDateToDate,
|
||||||
} from "../helperFunctions/convertAniListDateIn";
|
} from "../helperFunctions/convertAniListDateIn";
|
||||||
import AnimeTable from "./AnimeTable.svelte";
|
import AnimeTable from "./AnimeTable.svelte";
|
||||||
import type {
|
|
||||||
MALAnime,
|
|
||||||
MalListStatus,
|
|
||||||
MALUploadStatus,
|
|
||||||
} from "../mal/types/MALTypes";
|
|
||||||
import type { SimklAnime } from "../simkl/types/simklTypes";
|
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { StatusOption, StatusOptions } from "../helperTypes/StatusTypes";
|
import type { StatusOption, StatusOptions } from "../helperTypes/StatusTypes";
|
||||||
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
|
|
||||||
import { convertDateToAniList } from "../helperFunctions/convertDateToAniList";
|
import { convertDateToAniList } from "../helperFunctions/convertDateToAniList";
|
||||||
import {App} from "../../bindings/AniTrack";
|
import {App} from "../../bindings/AniTrack";
|
||||||
import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte";
|
import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte";
|
||||||
@@ -35,12 +35,12 @@
|
|||||||
import { Badge, Tooltip } from "flowbite-svelte";
|
import { Badge, Tooltip } from "flowbite-svelte";
|
||||||
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
||||||
|
|
||||||
let isAniListLoggedIn: boolean;
|
let isAniListLoggedIn!: boolean;
|
||||||
let isMalLoggedIn: boolean;
|
let isMalLoggedIn!: boolean;
|
||||||
let isSimklLoggedIn: boolean;
|
let isSimklLoggedIn!: boolean;
|
||||||
let currentAniListAnime: AniListGetSingleAnime;
|
let currentAniListAnime!: AniListGetSingleAnime;
|
||||||
let currentMalAnime: MALAnime;
|
let currentMalAnime!: MALAnime;
|
||||||
let currentSimklAnime: SimklAnime;
|
let currentSimklAnime!: SimklAnime;
|
||||||
let submitting = writable(false);
|
let submitting = writable(false);
|
||||||
let isSubmitting: boolean;
|
let isSubmitting: boolean;
|
||||||
let submitSuccess = writable(false);
|
let submitSuccess = writable(false);
|
||||||
@@ -103,11 +103,15 @@
|
|||||||
let finishDate = "";
|
let finishDate = "";
|
||||||
if (currentMalAnime.my_list_status.start_date !== "") {
|
if (currentMalAnime.my_list_status.start_date !== "") {
|
||||||
const startArray = re.exec(currentMalAnime.my_list_status.start_date);
|
const startArray = re.exec(currentMalAnime.my_list_status.start_date);
|
||||||
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
if (startArray) {
|
||||||
|
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (currentMalAnime.my_list_status.finish_date !== "") {
|
if (currentMalAnime.my_list_status.finish_date !== "") {
|
||||||
const finishArray = re.exec(currentMalAnime.my_list_status.finish_date);
|
const finishArray = re.exec(currentMalAnime.my_list_status.finish_date);
|
||||||
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
if (finishArray) {
|
||||||
|
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AddAnimeServiceToTable({
|
AddAnimeServiceToTable({
|
||||||
id: `m-${currentMalAnime.id}`,
|
id: `m-${currentMalAnime.id}`,
|
||||||
@@ -165,7 +169,9 @@
|
|||||||
for (let field of formData) {
|
for (let field of formData) {
|
||||||
const [key, value] = field;
|
const [key, value] = field;
|
||||||
if (key === "rating") {
|
if (key === "rating") {
|
||||||
submitData.rating = Number(value) * 2;
|
// Form value is already 0-10 (StarInput works in backend units).
|
||||||
|
// The old * 2 belonged to the retired 0-5 slider scale.
|
||||||
|
submitData.rating = Number(value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (key === "episodes") {
|
if (key === "episodes") {
|
||||||
@@ -180,7 +186,11 @@
|
|||||||
submitData.status = startingAnilistStatusOption;
|
submitData.status = startingAnilistStatusOption;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
submitData[key] = value;
|
// The only remaining form field is "notes" (string); anything else
|
||||||
|
// is ignored rather than assigned into a mistyped slot.
|
||||||
|
if (key === "notes" && typeof value === "string") {
|
||||||
|
submitData.notes = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -271,13 +281,17 @@
|
|||||||
const startArray = re.exec(
|
const startArray = re.exec(
|
||||||
currentMalAnime.my_list_status.start_date,
|
currentMalAnime.my_list_status.start_date,
|
||||||
);
|
);
|
||||||
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
if (startArray) {
|
||||||
|
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (currentMalAnime.my_list_status.finish_date !== "") {
|
if (currentMalAnime.my_list_status.finish_date !== "") {
|
||||||
const finishArray = re.exec(
|
const finishArray = re.exec(
|
||||||
currentMalAnime.my_list_status.finish_date,
|
currentMalAnime.my_list_status.finish_date,
|
||||||
);
|
);
|
||||||
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
if (finishArray) {
|
||||||
|
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AddAnimeServiceToTable({
|
AddAnimeServiceToTable({
|
||||||
id: `m-${currentMalAnime.id}`,
|
id: `m-${currentMalAnime.id}`,
|
||||||
@@ -503,7 +517,7 @@
|
|||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-10 grid-flow-col gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-10 grid-flow-col gap-4">
|
||||||
<div class="md:col-span-2 space-y-3">
|
<div class="md:col-span-2 space-y-3 flex flex-col items-center">
|
||||||
<img
|
<img
|
||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
src={currentAniListAnime.data.MediaList.media.coverImage.large}
|
src={currentAniListAnime.data.MediaList.media.coverImage.large}
|
||||||
@@ -525,6 +539,7 @@
|
|||||||
<div class="relative flex items-center max-w-[8rem]">
|
<div class="relative flex items-center max-w-[8rem]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label="Decrease episode progress"
|
||||||
id="decrement-button"
|
id="decrement-button"
|
||||||
data-input-counter-decrement="quantity-input"
|
data-input-counter-decrement="quantity-input"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
@@ -583,6 +598,7 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label="Increase episode progress"
|
||||||
id="increment-button"
|
id="increment-button"
|
||||||
data-input-counter-increment="quantity-input"
|
data-input-counter-increment="quantity-input"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
@@ -682,7 +698,7 @@
|
|||||||
>
|
>
|
||||||
<Datepicker
|
<Datepicker
|
||||||
bind:value={startedAtDate}
|
bind:value={startedAtDate}
|
||||||
color="slate"
|
color="gray"
|
||||||
dateFormat={{
|
dateFormat={{
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
@@ -699,7 +715,7 @@
|
|||||||
>
|
>
|
||||||
<Datepicker
|
<Datepicker
|
||||||
bind:value={completedAtDate}
|
bind:value={completedAtDate}
|
||||||
color="slate"
|
color="gray"
|
||||||
dateFormat={{
|
dateFormat={{
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
|
|||||||
@@ -1,118 +1,95 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
|
||||||
createRender,
|
|
||||||
createTable,
|
|
||||||
Render,
|
|
||||||
Subscribe,
|
|
||||||
} from "svelte-headless-table";
|
|
||||||
// @ts-ignore
|
|
||||||
import { addSortBy } from "svelte-headless-table/plugins";
|
|
||||||
import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||||
|
import type { TableItem } from "../helperTypes/TableTypes";
|
||||||
import WebsiteLink from "./WebsiteLink.svelte";
|
import WebsiteLink from "./WebsiteLink.svelte";
|
||||||
|
|
||||||
//when adding sort here is code { sort: addSortBy() }
|
type SortKey = keyof TableItem;
|
||||||
const table = createTable(tableItems, { sort: addSortBy() });
|
type SortOrder = "asc" | "desc" | undefined;
|
||||||
|
|
||||||
const columns = table.createColumns([
|
const columns: { key: SortKey; header: string }[] = [
|
||||||
table.column({
|
{ key: "id", header: "Service Id" },
|
||||||
header: "Service Id",
|
{ key: "title", header: "Anime Title" },
|
||||||
cell: ({ value }) => createRender(WebsiteLink, { id: value }),
|
{ key: "service", header: "Service" },
|
||||||
accessor: "id",
|
{ key: "progress", header: "Episode Progress" },
|
||||||
}),
|
{ key: "status", header: "Status" },
|
||||||
table.column({
|
{ key: "startedAt", header: "Started At" },
|
||||||
header: "Anime Title",
|
{ key: "completedAt", header: "Completed At" },
|
||||||
accessor: "title",
|
{ key: "score", header: "Rating" },
|
||||||
}),
|
{ key: "repeat", header: "Repeat" },
|
||||||
table.column({
|
{ key: "notes", header: "Notes" },
|
||||||
header: "Service",
|
];
|
||||||
accessor: "service",
|
|
||||||
}),
|
|
||||||
table.column({
|
|
||||||
header: "Episode Progress",
|
|
||||||
accessor: "progress",
|
|
||||||
}),
|
|
||||||
table.column({
|
|
||||||
header: "Status",
|
|
||||||
accessor: "status",
|
|
||||||
}),
|
|
||||||
table.column({
|
|
||||||
header: "Started At",
|
|
||||||
accessor: "startedAt",
|
|
||||||
}),
|
|
||||||
table.column({
|
|
||||||
header: "Completed At",
|
|
||||||
accessor: "completedAt",
|
|
||||||
}),
|
|
||||||
table.column({
|
|
||||||
header: "Rating",
|
|
||||||
accessor: "score",
|
|
||||||
}),
|
|
||||||
table.column({
|
|
||||||
header: "Repeat",
|
|
||||||
accessor: "repeat",
|
|
||||||
}),
|
|
||||||
table.column({
|
|
||||||
header: "Notes",
|
|
||||||
accessor: "notes",
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
//add pluginStates when add sort back
|
let sortKey = $state<SortKey | undefined>(undefined);
|
||||||
const { headerRows, rows, tableAttrs, tableBodyAttrs } =
|
let sortOrder = $state<SortOrder>(undefined);
|
||||||
table.createViewModel(columns);
|
|
||||||
|
function toggleSort(key: SortKey) {
|
||||||
|
if (sortKey !== key) {
|
||||||
|
sortKey = key;
|
||||||
|
sortOrder = "asc";
|
||||||
|
} else if (sortOrder === "asc") {
|
||||||
|
sortOrder = "desc";
|
||||||
|
} else if (sortOrder === "desc") {
|
||||||
|
sortKey = undefined;
|
||||||
|
sortOrder = undefined;
|
||||||
|
} else {
|
||||||
|
sortOrder = "asc";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareItems(a: TableItem, b: TableItem): number {
|
||||||
|
if (sortKey === undefined || sortOrder === undefined) return 0;
|
||||||
|
const av = a[sortKey];
|
||||||
|
const bv = b[sortKey];
|
||||||
|
let result: number;
|
||||||
|
if (typeof av === "number" && typeof bv === "number") {
|
||||||
|
result = av - bv;
|
||||||
|
} else {
|
||||||
|
result = String(av ?? "").localeCompare(String(bv ?? ""));
|
||||||
|
}
|
||||||
|
return sortOrder === "asc" ? result : -result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sortedRows = $derived(
|
||||||
|
sortKey === undefined ? $tableItems : [...$tableItems].sort(compareItems),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative overflow-x-auto rounded-lg mb-5">
|
<div class="relative overflow-x-auto rounded-lg mb-5">
|
||||||
<table
|
<table class="w-full text-sm text-left rtl:text-right text-gray-400">
|
||||||
class="w-full text-sm text-left rtl:text-right text-gray-400"
|
|
||||||
{...$tableAttrs}
|
|
||||||
>
|
|
||||||
<thead class="text-xs uppercase bg-gray-700 text-gray-400">
|
<thead class="text-xs uppercase bg-gray-700 text-gray-400">
|
||||||
{#each $headerRows as headerRow (headerRow.id)}
|
<tr>
|
||||||
<Subscribe attrs={headerRow.attrs()} let:attrs>
|
{#each columns as column (column.key)}
|
||||||
<tr {...attrs}>
|
<th
|
||||||
{#each headerRow.cells as cell (cell.id)}
|
onclick={() => toggleSort(column.key)}
|
||||||
<Subscribe
|
class:sorted={sortKey === column.key &&
|
||||||
attrs={cell.attrs()}
|
sortOrder !== undefined}
|
||||||
let:attrs
|
class="px-6 py-3 cursor-pointer"
|
||||||
props={cell.props()}
|
>
|
||||||
let:props
|
<div>
|
||||||
>
|
{column.header}
|
||||||
<th
|
{#if sortKey === column.key && sortOrder === "asc"}
|
||||||
{...attrs}
|
⬇️
|
||||||
on:click={props.sort.toggle}
|
{:else if sortKey === column.key && sortOrder === "desc"}
|
||||||
class:sorted={props.sort.order !==
|
⬆️
|
||||||
undefined}
|
{/if}
|
||||||
class="px-6 py-3"
|
</div>
|
||||||
>
|
</th>
|
||||||
<div>
|
{/each}
|
||||||
<Render of={cell.render()} />
|
</tr>
|
||||||
{#if props.sort.order === "asc"}
|
|
||||||
⬇️
|
|
||||||
{:else if props.sort.order === "desc"}
|
|
||||||
⬆️
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</Subscribe>
|
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
</Subscribe>
|
|
||||||
{/each}
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody {...$tableBodyAttrs}>
|
<tbody>
|
||||||
{#each $rows as row (row.id)}
|
{#each sortedRows as row (row.id + row.service)}
|
||||||
<Subscribe attrs={row.attrs()} let:attrs>
|
<tr class="bg-gray-800 border-gray-700">
|
||||||
<tr {...attrs} class="bg-gray-800 border-gray-700">
|
{#each columns as column (column.key)}
|
||||||
{#each row.cells as cell (cell.id)}
|
<td class="px-6 py-4">
|
||||||
<Subscribe attrs={cell.attrs()} let:attrs>
|
{#if column.key === "id"}
|
||||||
<td {...attrs} class="px-6 py-4">
|
<WebsiteLink id={row.id} />
|
||||||
<Render of={cell.render()} />
|
{:else}
|
||||||
</td>
|
{row[column.key]}
|
||||||
</Subscribe>
|
{/if}
|
||||||
{/each}
|
</td>
|
||||||
</tr>
|
{/each}
|
||||||
</Subscribe>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Avatar } from "flowbite-svelte";
|
import { Avatar } from "flowbite-svelte";
|
||||||
import type { AniListUser } from "../anilist/types/AniListTypes";
|
import type { AniListUser, MyAnimeListUser, SimklUser } from "../../bindings/AniTrack/models";
|
||||||
import {
|
import {
|
||||||
aniListLoggedIn,
|
aniListLoggedIn,
|
||||||
aniListUser,
|
aniListUser,
|
||||||
@@ -17,16 +17,14 @@
|
|||||||
serviceLoggingIn,
|
serviceLoggingIn,
|
||||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||||
import {Application} from "@wailsio/runtime";
|
import {Application} from "@wailsio/runtime";
|
||||||
import type { MyAnimeListUser } from "../mal/types/MALTypes";
|
import {App} from "../../bindings/AniTrack";
|
||||||
import type { SimklUser } from "../simkl/types/simklTypes";
|
|
||||||
import {App} from "../../bindings/AniTrack";
|
|
||||||
|
|
||||||
let currentAniListUser: AniListUser;
|
let currentAniListUser: AniListUser;
|
||||||
let currentMALUser: MyAnimeListUser;
|
let currentMALUser: MyAnimeListUser;
|
||||||
let currentSimklUser: SimklUser;
|
let currentSimklUser: SimklUser;
|
||||||
let isAniListLoggedIn: boolean;
|
let isAniListLoggedIn!: boolean;
|
||||||
let isSimklLoggedIn: boolean;
|
let isSimklLoggedIn!: boolean;
|
||||||
let isMALLoggedIn: boolean;
|
let isMALLoggedIn!: boolean;
|
||||||
let loggingIn: string[] = [];
|
let loggingIn: string[] = [];
|
||||||
|
|
||||||
aniListUser.subscribe((value) => (currentAniListUser = value));
|
aniListUser.subscribe((value) => (currentAniListUser = value));
|
||||||
@@ -38,7 +36,8 @@
|
|||||||
serviceLoggingIn.subscribe((value) => (loggingIn = value));
|
serviceLoggingIn.subscribe((value) => (loggingIn = value));
|
||||||
|
|
||||||
function dropdownUser(): void {
|
function dropdownUser(): void {
|
||||||
let dropdown = document.querySelector("#userDropdown");
|
const dropdown = document.querySelector("#userDropdown");
|
||||||
|
if (!dropdown) return;
|
||||||
dropdown.classList.toggle("hidden");
|
dropdown.classList.toggle("hidden");
|
||||||
|
|
||||||
if (!dropdown.classList.contains("hidden")) {
|
if (!dropdown.classList.contains("hidden")) {
|
||||||
@@ -47,8 +46,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clickOutside(event: Event): void {
|
function clickOutside(event: Event): void {
|
||||||
let dropdown = document.querySelector("#userDropdown");
|
const dropdown = document.querySelector("#userDropdown");
|
||||||
let toggleBtn = document.querySelector("#userDropdownButton");
|
const toggleBtn = document.querySelector("#userDropdownButton");
|
||||||
|
if (!dropdown || !toggleBtn) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!dropdown.contains(event.target as Node) &&
|
!dropdown.contains(event.target as Node) &&
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
import { Button } from "flowbite-svelte";
|
import { Button } from "flowbite-svelte";
|
||||||
|
import type { ButtonProps } from "flowbite-svelte";
|
||||||
|
|
||||||
export let value: Date | null = null;
|
export let value: Date | null = null;
|
||||||
export let defaultDate: Date | null = null;
|
export let defaultDate: Date | null = null;
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
export let disabled: boolean = false;
|
export let disabled: boolean = false;
|
||||||
export let required: boolean = false;
|
export let required: boolean = false;
|
||||||
export let inputClass: string = "";
|
export let inputClass: string = "";
|
||||||
export let color: Button["color"] = "primary";
|
export let color: ButtonProps["color"] = "primary";
|
||||||
export let inline: boolean = false;
|
export let inline: boolean = false;
|
||||||
export let autohide: boolean = true;
|
export let autohide: boolean = true;
|
||||||
export let showActionButtons: boolean = false;
|
export let showActionButtons: boolean = false;
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Color handling functions
|
// Color handling functions
|
||||||
function getFocusRingClass(color: Button["color"]): string {
|
function getFocusRingClass(color: ButtonProps["color"]): string {
|
||||||
switch (color) {
|
switch (color) {
|
||||||
case "primary":
|
case "primary":
|
||||||
return "focus:ring-2 focus:ring-primary-400";
|
return "focus:ring-2 focus:ring-primary-400";
|
||||||
@@ -61,14 +62,14 @@
|
|||||||
return "focus:ring-2 focus:ring-yellow-400";
|
return "focus:ring-2 focus:ring-yellow-400";
|
||||||
case "purple":
|
case "purple":
|
||||||
return "focus:ring-2 focus:ring-purple-400";
|
return "focus:ring-2 focus:ring-purple-400";
|
||||||
case "slate":
|
case "gray":
|
||||||
return "focus:ring-2 focus:ring-slate-400";
|
return "focus:ring-2 focus:ring-gray-400";
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRangeBackgroundClass(color: Button["color"]): string {
|
function getRangeBackgroundClass(color: ButtonProps["color"]): string {
|
||||||
switch (color) {
|
switch (color) {
|
||||||
case "primary":
|
case "primary":
|
||||||
return "bg-primary-900";
|
return "bg-primary-900";
|
||||||
@@ -82,8 +83,8 @@
|
|||||||
return "bg-yellow-900";
|
return "bg-yellow-900";
|
||||||
case "purple":
|
case "purple":
|
||||||
return "bg-purple-900";
|
return "bg-purple-900";
|
||||||
case "slate":
|
case "gray":
|
||||||
return "bg-slate-900";
|
return "bg-gray-900";
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -352,7 +353,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<Button
|
<Button
|
||||||
on:click={() => changeMonth(-1)}
|
onclick={() => changeMonth(-1)}
|
||||||
{color}
|
{color}
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="Previous month"
|
aria-label="Previous month"
|
||||||
@@ -382,7 +383,7 @@
|
|||||||
})}
|
})}
|
||||||
</h3>
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
on:click={() => changeMonth(1)}
|
onclick={() => changeMonth(1)}
|
||||||
{color}
|
{color}
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="Next month"
|
aria-label="Next month"
|
||||||
@@ -424,8 +425,8 @@
|
|||||||
: ''} {isInRange(day)
|
: ''} {isInRange(day)
|
||||||
? getRangeBackgroundClass(color)
|
? getRangeBackgroundClass(color)
|
||||||
: ''}"
|
: ''}"
|
||||||
on:click={() => handleDaySelect(day)}
|
onclick={() => handleDaySelect(day)}
|
||||||
on:keydown={handleCalendarKeydown}
|
onkeydown={handleCalendarKeydown}
|
||||||
aria-label={day.toLocaleDateString(locale, {
|
aria-label={day.toLocaleDateString(locale, {
|
||||||
weekday: "long",
|
weekday: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
@@ -441,13 +442,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if showActionButtons}
|
{#if showActionButtons}
|
||||||
<div class="mt-4 flex justify-between">
|
<div class="mt-4 flex justify-between">
|
||||||
<Button on:click={handleToday} {color} size="sm"
|
<Button onclick={handleToday} {color} size="sm"
|
||||||
>Today</Button
|
>Today</Button
|
||||||
>
|
>
|
||||||
<Button on:click={handleClear} color="red" size="sm"
|
<Button onclick={handleClear} color="red" size="sm"
|
||||||
>Clear</Button
|
>Clear</Button
|
||||||
>
|
>
|
||||||
<Button on:click={handleApply} {color} size="sm"
|
<Button onclick={handleApply} {color} size="sm"
|
||||||
>Apply</Button
|
>Apply</Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,13 +61,15 @@
|
|||||||
dismiss this message to continue with limited functionality.
|
dismiss this message to continue with limited functionality.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div slot="footer" class="flex gap-3 justify-end">
|
{#snippet footer()}
|
||||||
{#if $apiError.canRetry}
|
<div class="flex gap-3 justify-end">
|
||||||
<Button on:click={handleRetry} class="bg-blue-600 hover:bg-blue-700">
|
{#if $apiError.canRetry}
|
||||||
Retry Connection
|
<Button onclick={handleRetry} class="bg-blue-600 hover:bg-blue-700">
|
||||||
</Button>
|
Retry Connection
|
||||||
{/if}
|
</Button>
|
||||||
<Button on:click={handleDismiss} color="alternative">Dismiss</Button>
|
{/if}
|
||||||
</div>
|
<Button onclick={handleDismiss} color="alternative">Dismiss</Button>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
import logo from "../assets/images/AniTrackLogo.svg";
|
import logo from "../assets/images/AniTrackLogo.svg";
|
||||||
import { link } from "svelte-spa-router";
|
import { link } from "svelte-spa-router";
|
||||||
|
|
||||||
let isAniListLoggedIn: boolean;
|
let isAniListLoggedIn!: boolean;
|
||||||
let isSimklLoggedIn: boolean;
|
let isSimklLoggedIn!: boolean;
|
||||||
let isMALLoggedIn: boolean;
|
let isMALLoggedIn!: boolean;
|
||||||
let loggingIn: string[] = [];
|
let loggingIn: string[] = [];
|
||||||
|
|
||||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||||
@@ -34,19 +34,20 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex items-center min-[950px]:order-2 space-x-3 min-[950px]:space-x-0 rtl:space-x-reverse"
|
class="flex items-center sm:order-2 space-x-3 sm:space-x-0 rtl:space-x-reverse"
|
||||||
>
|
>
|
||||||
<div class="min-[950px]:block min-[950px]:mr-4">
|
<div class="hidden sm:block sm:mr-4">
|
||||||
<Search />
|
<Search />
|
||||||
</div>
|
</div>
|
||||||
<AvatarMenu />
|
<AvatarMenu />
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
let menu = document.querySelector("#navbar-user");
|
const menu = document.querySelector("#navbar-user");
|
||||||
|
if (!menu) return;
|
||||||
menu.classList.toggle("hidden");
|
menu.classList.toggle("hidden");
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm rounded-lg min-[950px]:hidden focus:outline-none focus:ring-2 text-gray-400 hover:bg-gray-700 focus:ring-gray-600"
|
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm rounded-lg sm:hidden focus:outline-none focus:ring-2 text-gray-400 hover:bg-gray-700 focus:ring-gray-600"
|
||||||
aria-controls="navbar-user"
|
aria-controls="navbar-user"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
@@ -69,11 +70,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="hidden items-center justify-between w-full pb-4 min-[950px]:pb-0 min-[950px]:flex min-[950px]:w-auto min-[950px]:order-1 border border-gray-700 min-[950px]:border-0 bg-gray-800 min-[950px]:bg-transparent rounded-lg"
|
class="hidden items-center justify-between w-full pb-4 sm:pb-0 sm:flex sm:w-auto sm:order-1 border border-gray-700 sm:border-0 bg-gray-800 sm:bg-transparent rounded-lg"
|
||||||
id="navbar-user"
|
id="navbar-user"
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
class="flex flex-col font-medium pb-6 min-[950px]:p-0 mt-4 min-[950px]:space-x-8 rtl:space-x-reverse min-[950px]:flex-row min-[950px]:mt-0"
|
class="flex flex-col font-medium pb-6 sm:p-0 mt-4 sm:space-x-8 rtl:space-x-reverse sm:flex-row sm:mt-0"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
{#if !isAniListLoggedIn}
|
{#if !isAniListLoggedIn}
|
||||||
@@ -125,7 +126,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="flex justify-center min-[950px]:hidden">
|
<div class="flex justify-center sm:hidden">
|
||||||
<Search />
|
<Search />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
watchListPage,
|
watchListPage,
|
||||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||||
|
|
||||||
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
|
import type { AniListCurrentUserWatchList } from "../../bindings/AniTrack/models";
|
||||||
import {App} from "../../bindings/AniTrack";
|
import {App} from "../../bindings/AniTrack";
|
||||||
|
|
||||||
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
||||||
let page: number;
|
let page: number;
|
||||||
let perPage: number;
|
let perPage: number;
|
||||||
let sort: string;
|
let sort!: string;
|
||||||
|
|
||||||
watchListPage.subscribe((value) => (page = value));
|
watchListPage.subscribe((value) => (page = value));
|
||||||
animePerPage.subscribe((value) => (perPage = value));
|
animePerPage.subscribe((value) => (perPage = value));
|
||||||
@@ -43,14 +43,14 @@
|
|||||||
function changeCountPerPage(
|
function changeCountPerPage(
|
||||||
e: Event & { currentTarget: HTMLSelectElement },
|
e: Event & { currentTarget: HTMLSelectElement },
|
||||||
): void {
|
): void {
|
||||||
App.GetAniListUserWatchingList(1, Number(e.currentTarget.value), sort).then(
|
// Read synchronously: Svelte 5 nulls currentTarget after dispatch.
|
||||||
(result) => {
|
const count = Number(e.currentTarget.value);
|
||||||
animePerPage.set(Number(e.currentTarget.value));
|
App.GetAniListUserWatchingList(1, count, sort).then((result) => {
|
||||||
watchListPage.set(1);
|
animePerPage.set(count);
|
||||||
aniListWatchlist.set(result);
|
watchListPage.set(1);
|
||||||
aniListLoggedIn.set(true);
|
aniListWatchlist.set(result);
|
||||||
},
|
aniListLoggedIn.set(true);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -151,6 +151,7 @@
|
|||||||
<div class="relative flex items-center max-w-[11rem]">
|
<div class="relative flex items-center max-w-[11rem]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label="Previous page"
|
||||||
id="decrement-button"
|
id="decrement-button"
|
||||||
on:click={() => ChangeWatchListPage(page - 1)}
|
on:click={() => ChangeWatchListPage(page - 1)}
|
||||||
class={page <= 1
|
class={page <= 1
|
||||||
@@ -191,6 +192,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label="Next page"
|
||||||
id="increment-button"
|
id="increment-button"
|
||||||
on:click={() => ChangeWatchListPage(page + 1)}
|
on:click={() => ChangeWatchListPage(page + 1)}
|
||||||
class={page >= aniListWatchListLoaded.data.Page.pageInfo.lastPage
|
class={page >= aniListWatchListLoaded.data.Page.pageInfo.lastPage
|
||||||
|
|||||||
@@ -1,30 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import StarRatting from "../star-rating/Stars.svelte";
|
import StarInput from "../star-rating/StarInput.svelte";
|
||||||
|
|
||||||
export let score
|
let { score = $bindable(0) }: { score: number } = $props();
|
||||||
|
|
||||||
let config = {
|
const ratingInWords: Record<number, string> = {
|
||||||
readOnly: false,
|
|
||||||
countStars: 5,
|
|
||||||
range: {
|
|
||||||
min: 0,
|
|
||||||
max: 5,
|
|
||||||
step: 0.5
|
|
||||||
},
|
|
||||||
score: score / 2,
|
|
||||||
showScore: false,
|
|
||||||
name: "rating",
|
|
||||||
scoreFormat: function(){ return `(${this.score.toFixed(0)}/${this.countStars})` },
|
|
||||||
starConfig: {
|
|
||||||
size: 32,
|
|
||||||
fillColor: '#F9ED4F',
|
|
||||||
strokeColor: "#e2c714",
|
|
||||||
unfilledColor: '#FFF',
|
|
||||||
strokeUnfilledColor: '#000'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ratingInWords = {
|
|
||||||
0: "Not Reviewed",
|
0: "Not Reviewed",
|
||||||
1: "Appalling",
|
1: "Appalling",
|
||||||
2: "Horrible",
|
2: "Horrible",
|
||||||
@@ -37,14 +16,10 @@
|
|||||||
9: "Great",
|
9: "Great",
|
||||||
10: "Masterpiece",
|
10: "Masterpiece",
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeRating = (e: any) => {
|
|
||||||
score = e.target.valueAsNumber * 2
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<StarRatting bind:config on:change={changeRating}/>
|
<StarInput bind:value={score} min={0} max={10} step={1} name="rating" />
|
||||||
<p>Rating: {config.score * 2}</p>
|
<p>Rating: {score}</p>
|
||||||
<p>{ratingInWords[config.score * 2]}</p>
|
<p>{ratingInWords[score]}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
{ value: MediaListSort.MediaPopularityDesc, name: "Media Popularity Desc" },
|
{ value: MediaListSort.MediaPopularityDesc, name: "Media Popularity Desc" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let sort: string;
|
let sort!: string;
|
||||||
aniListSort.subscribe((value) => (sort = value));
|
aniListSort.subscribe((value) => (sort = value));
|
||||||
console.log(sort);
|
console.log(sort);
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
loading,
|
loading,
|
||||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||||
import { push } from "svelte-spa-router";
|
import { push } from "svelte-spa-router";
|
||||||
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
|
import type { AniListCurrentUserWatchList } from "../../bindings/AniTrack/models";
|
||||||
import { Rating } from "flowbite-svelte";
|
import { Rating } from "flowbite-svelte";
|
||||||
import loader from "../helperFunctions/loader";
|
import loader from "../helperFunctions/loader";
|
||||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||||
import Sort from "../helperComponents/Sort.svelte";
|
import Sort from "../helperComponents/Sort.svelte";
|
||||||
import RefreshWatchListButton from "./RefreshWatchListButton.svelte";
|
import RefreshWatchListButton from "./RefreshWatchListButton.svelte";
|
||||||
|
|
||||||
let isAniListLoggedIn: boolean;
|
let isAniListLoggedIn!: boolean;
|
||||||
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
||||||
|
|
||||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
isAniList = id.includes("a-");
|
isAniList = id.includes("a-");
|
||||||
isMAL = id.includes("m-");
|
isMAL = id.includes("m-");
|
||||||
isSimkl = id.includes("s-");
|
isSimkl = id.includes("s-");
|
||||||
if (isAniList || isMAL || isSimkl) newId = id.match(re)[1];
|
if (isAniList || isMAL || isSimkl) newId = id.match(re)![1];
|
||||||
else newId = id;
|
else newId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type {AniListGetSingleAnime} from "../anilist/types/AniListCurrentUserWatchListType";
|
import type {AniListGetSingleAnime} from "../../bindings/AniTrack/models";
|
||||||
|
|
||||||
export const AniListGetSingleAnimeDefaultData: AniListGetSingleAnime = {
|
export const AniListGetSingleAnimeDefaultData: AniListGetSingleAnime = {
|
||||||
data: {
|
data: {
|
||||||
@@ -10,23 +10,57 @@ export const AniListGetSingleAnimeDefaultData: AniListGetSingleAnime = {
|
|||||||
id: 0,
|
id: 0,
|
||||||
idMal: 0,
|
idMal: 0,
|
||||||
title: {
|
title: {
|
||||||
|
userPreferred: "",
|
||||||
romaji: "",
|
romaji: "",
|
||||||
english: "",
|
english: "",
|
||||||
native: "",
|
native: "",
|
||||||
},
|
},
|
||||||
description: "",
|
description: "",
|
||||||
coverImage: {
|
coverImage: {
|
||||||
|
ExtraLarge: "",
|
||||||
large: "",
|
large: "",
|
||||||
|
Medium: "",
|
||||||
|
Color: "",
|
||||||
},
|
},
|
||||||
|
BannerImage: "",
|
||||||
|
Format: "",
|
||||||
season: "",
|
season: "",
|
||||||
seasonYear: 0,
|
seasonYear: 0,
|
||||||
status: "",
|
status: "",
|
||||||
episodes: 0,
|
episodes: 0,
|
||||||
|
Duration: 0,
|
||||||
|
CountryOfOrigin: "",
|
||||||
|
Source: "",
|
||||||
|
Synonyms: null,
|
||||||
|
AverageScore: 0,
|
||||||
|
MeanScore: 0,
|
||||||
|
Popularity: 0,
|
||||||
|
Trending: 0,
|
||||||
|
Favourites: 0,
|
||||||
|
relations: {
|
||||||
|
nodes: null,
|
||||||
|
},
|
||||||
|
startDate: {
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
day: 0,
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
day: 0,
|
||||||
|
},
|
||||||
nextAiringEpisode: {
|
nextAiringEpisode: {
|
||||||
airingAt: 0,
|
airingAt: 0,
|
||||||
timeUntilAiring: 0,
|
timeUntilAiring: 0,
|
||||||
episode: 0,
|
episode: 0,
|
||||||
}
|
},
|
||||||
|
airingSchedule: {
|
||||||
|
nodes: null,
|
||||||
|
},
|
||||||
|
genres: [],
|
||||||
|
tags: [],
|
||||||
|
isAdult: false,
|
||||||
},
|
},
|
||||||
status: "",
|
status: "",
|
||||||
startedAt: {
|
startedAt: {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const convertAniListDateToString = (date: {
|
|||||||
) {
|
) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const newISODate = new Date(date.year, date.month - 1, date.day);
|
const newISODate = new Date(date.year, date.month! - 1, date.day);
|
||||||
const newMoment = moment(newISODate);
|
const newMoment = moment(newISODate);
|
||||||
return newMoment.format("MM-DD-YYYY");
|
return newMoment.format("MM-DD-YYYY");
|
||||||
};
|
};
|
||||||
@@ -31,7 +31,7 @@ const convertAniListDateToDate = (date: {
|
|||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Date(date.year, date.month - 1, date.day);
|
return new Date(date.year, date.month! - 1, date.day);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { convertAniListDateToString, convertAniListDateToDate };
|
export { convertAniListDateToString, convertAniListDateToDate };
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ const convertDateStringToAniList = (date: string): AnilistDate => {
|
|||||||
}
|
}
|
||||||
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
||||||
const newDate = re.exec(date);
|
const newDate = re.exec(date);
|
||||||
|
if (newDate === null) {
|
||||||
|
return {
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
day: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
year: Number(newDate[1]),
|
year: Number(newDate[1]),
|
||||||
month: Number(newDate[2]),
|
month: Number(newDate[2]),
|
||||||
|
|||||||
@@ -1,18 +1,29 @@
|
|||||||
|
import { mount, unmount } from 'svelte';
|
||||||
import Spinner from '../helperComponents/Spinner.svelte';
|
import Spinner from '../helperComponents/Spinner.svelte';
|
||||||
|
|
||||||
export default (node: any, loading: any) => {
|
export default (node: any, loading: any) => {
|
||||||
let Spin: any
|
let app: any;
|
||||||
loading.subscribe((loading: any) => {
|
const unsubscribe = loading.subscribe((isLoading: any) => {
|
||||||
if(loading){
|
if (isLoading && !app) {
|
||||||
Spin = new Spinner({
|
app = mount(Spinner, {
|
||||||
target: node,
|
target: node,
|
||||||
intro: true
|
intro: true
|
||||||
})
|
});
|
||||||
} else {
|
} else if (!isLoading && app) {
|
||||||
if(Spin){
|
const current = app;
|
||||||
Spin?.$destroy?.()
|
app = undefined;
|
||||||
Spin = undefined;
|
// Teardown must never break the caller.
|
||||||
|
void unmount(current).catch(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
unsubscribe();
|
||||||
|
if (app) {
|
||||||
|
const current = app;
|
||||||
|
app = undefined;
|
||||||
|
void unmount(current).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
}
|
}
|
||||||
@@ -3,19 +3,19 @@
|
|||||||
import { tableItems } from "./GlobalVariablesAndHelperFunctions.svelte"
|
import { tableItems } from "./GlobalVariablesAndHelperFunctions.svelte"
|
||||||
|
|
||||||
export function AddAnimeServiceToTable(animeItem: TableItem) {
|
export function AddAnimeServiceToTable(animeItem: TableItem) {
|
||||||
|
// Always return a NEW array: in-place mutation with the same
|
||||||
|
// reference does not reliably invalidate $derived consumers
|
||||||
|
// under Svelte 5, which left the table stale after submits.
|
||||||
tableItems.update((table) => {
|
tableItems.update((table) => {
|
||||||
if (table.length === 0) {
|
const index = table.findIndex(
|
||||||
table.push(animeItem)
|
(tableItem) => tableItem.service === animeItem.service,
|
||||||
} else {
|
);
|
||||||
for (const [index, tableItem] of table.entries()) {
|
if (index === -1) {
|
||||||
if(tableItem.service === animeItem.service) {
|
return [...table, animeItem];
|
||||||
table[index] = animeItem
|
|
||||||
return table
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table.push(animeItem)
|
|
||||||
}
|
}
|
||||||
return table
|
return table.map((tableItem, i) =>
|
||||||
|
i === index ? animeItem : tableItem,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
let isAniListPrimary: boolean;
|
let isAniListPrimary: boolean;
|
||||||
let page: number;
|
let page: number;
|
||||||
let perPage: number;
|
let perPage: number;
|
||||||
let sort: string;
|
let sort!: string;
|
||||||
|
|
||||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||||
watchListPage.subscribe((value) => (page = value));
|
watchListPage.subscribe((value) => (page = value));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import {App} from "../../bindings/AniTrack";
|
import {App} from "../../bindings/AniTrack";
|
||||||
import {malUser, malPrimary, malWatchList, malLoggedIn, serviceLoggingIn} from "./GlobalVariablesAndHelperFunctions.svelte"
|
import {malUser, malPrimary, malWatchList, malLoggedIn, serviceLoggingIn} from "./GlobalVariablesAndHelperFunctions.svelte"
|
||||||
import type { MyAnimeListUser } from "../mal/types/MALTypes";
|
import type { MyAnimeListUser } from "../../bindings/AniTrack/models";
|
||||||
|
|
||||||
let isMalPrimary: boolean
|
let isMalPrimary: boolean
|
||||||
malPrimary.subscribe(value => isMalPrimary = value)
|
malPrimary.subscribe(value => isMalPrimary = value)
|
||||||
|
|||||||
@@ -3,22 +3,16 @@
|
|||||||
import type {
|
import type {
|
||||||
AniListCurrentUserWatchList,
|
AniListCurrentUserWatchList,
|
||||||
AniListGetSingleAnime,
|
AniListGetSingleAnime,
|
||||||
} from "../anilist/types/AniListCurrentUserWatchListType.js";
|
AniListUser,
|
||||||
import { writable } from "svelte/store";
|
|
||||||
import type {
|
|
||||||
SimklAnime,
|
|
||||||
SimklUser,
|
|
||||||
SimklWatchList,
|
|
||||||
} from "../simkl/types/simklTypes";
|
|
||||||
import {
|
|
||||||
type AniListUser,
|
|
||||||
MediaListSort,
|
|
||||||
} from "../anilist/types/AniListTypes";
|
|
||||||
import type {
|
|
||||||
MALAnime,
|
MALAnime,
|
||||||
MALWatchlist,
|
MALWatchlist,
|
||||||
MyAnimeListUser,
|
MyAnimeListUser,
|
||||||
} from "../mal/types/MALTypes";
|
SimklAnime,
|
||||||
|
SimklUser,
|
||||||
|
SimklWatchListType,
|
||||||
|
} from "../../bindings/AniTrack/models";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
import { MediaListSort } from "../anilist/types/AniListTypes";
|
||||||
import type { TableItems } from "../helperTypes/TableTypes";
|
import type { TableItems } from "../helperTypes/TableTypes";
|
||||||
import { AniListGetSingleAnimeDefaultData } from "../helperDefaults/AniListGetSingleAnime";
|
import { AniListGetSingleAnimeDefaultData } from "../helperDefaults/AniListGetSingleAnime";
|
||||||
|
|
||||||
@@ -28,7 +22,7 @@
|
|||||||
export let simklLoggedIn = writable(false);
|
export let simklLoggedIn = writable(false);
|
||||||
export let malLoggedIn = writable(false);
|
export let malLoggedIn = writable(false);
|
||||||
export const serviceLoggingIn = writable([] as string[]);
|
export const serviceLoggingIn = writable([] as string[]);
|
||||||
export let simklWatchList = writable({} as SimklWatchList);
|
export let simklWatchList = writable({} as SimklWatchListType);
|
||||||
export let aniListPrimary = writable(true);
|
export let aniListPrimary = writable(true);
|
||||||
export let simklPrimary = writable(false);
|
export let simklPrimary = writable(false);
|
||||||
export let malPrimary = writable(false);
|
export let malPrimary = writable(false);
|
||||||
@@ -50,12 +44,12 @@
|
|||||||
let isAniListPrimary: boolean;
|
let isAniListPrimary: boolean;
|
||||||
let page: number;
|
let page: number;
|
||||||
let perPage: number;
|
let perPage: number;
|
||||||
let sort: string;
|
let sort!: string;
|
||||||
let aniWatchlist: AniListCurrentUserWatchList;
|
let aniWatchlist: AniListCurrentUserWatchList;
|
||||||
let currentAniListAnime: AniListGetSingleAnime;
|
let currentAniListAnime!: AniListGetSingleAnime;
|
||||||
|
|
||||||
let isMalLoggedIn: boolean;
|
let isMalLoggedIn!: boolean;
|
||||||
let isSimklLoggedIn: boolean;
|
let isSimklLoggedIn!: boolean;
|
||||||
|
|
||||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||||
watchListPage.subscribe((value) => (page = value));
|
watchListPage.subscribe((value) => (page = value));
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import './style.css'
|
import './style.css'
|
||||||
|
import { mount } from 'svelte'
|
||||||
import App from './App.svelte'
|
import App from './App.svelte'
|
||||||
|
|
||||||
const app = new App({
|
const app = mount(App, {
|
||||||
target: document.getElementById('app')
|
target: document.getElementById('app')!
|
||||||
})
|
})
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import loader from "../helperFunctions/loader";
|
import loader from "../helperFunctions/loader";
|
||||||
|
|
||||||
let isAniListPrimary: boolean;
|
let isAniListPrimary: boolean;
|
||||||
let isAniListLoggedIn: boolean;
|
let isAniListLoggedIn!: boolean;
|
||||||
|
|
||||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
<!-- Dumb reusable star-rating input. No stores, no backend knowledge:
|
||||||
|
`value` is written live on hover (reverted on leave unless committed)
|
||||||
|
and set on click/keyboard; the parent owns what the value means
|
||||||
|
(words, persistence). Reuses the Star leaf, so output is pixel-identical
|
||||||
|
to the old overlay-slider version without the overlay. -->
|
||||||
|
<script lang="ts">
|
||||||
|
import Star from './components/Star.svelte';
|
||||||
|
|
||||||
|
type StarColors = {
|
||||||
|
size: number;
|
||||||
|
fillColor: string;
|
||||||
|
strokeColor: string;
|
||||||
|
unfilledColor: string;
|
||||||
|
strokeUnfilledColor: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
value = $bindable(0),
|
||||||
|
min = 0,
|
||||||
|
max = 5,
|
||||||
|
step = 0.5,
|
||||||
|
count = 5,
|
||||||
|
disabled = false,
|
||||||
|
name,
|
||||||
|
starConfig = {
|
||||||
|
size: 32,
|
||||||
|
fillColor: '#F9ED4F',
|
||||||
|
strokeColor: '#BB8511',
|
||||||
|
unfilledColor: '#FFF',
|
||||||
|
strokeUnfilledColor: '#000'
|
||||||
|
},
|
||||||
|
onchange,
|
||||||
|
}: {
|
||||||
|
value?: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
count?: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
// When set, renders a hidden input so the value submits with a
|
||||||
|
// surrounding <form> via FormData (the old slider exposed
|
||||||
|
// name="rating" the same way).
|
||||||
|
name?: string;
|
||||||
|
starConfig?: StarColors;
|
||||||
|
onchange?: (value: number) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
// Value at hover entry; restored on leave unless committed. Null = idle.
|
||||||
|
let baseline: number | null = $state(null);
|
||||||
|
|
||||||
|
function clamp(v: number): number {
|
||||||
|
return Math.min(max, Math.max(min, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
function snap(v: number): number {
|
||||||
|
const snapped = min + Math.round((v - min) / step) * step;
|
||||||
|
return clamp(Math.round(snapped * 1e6) / 1e6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuous value for cursor position within star `index`.
|
||||||
|
function valueAt(index: number, fraction: number): number {
|
||||||
|
if (index === 0 && fraction < 0.2) return min;
|
||||||
|
const f = fraction >= 0.5 ? 1 : 0.5;
|
||||||
|
return snap(min + ((index + f) / count) * (max - min));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fractionOf(e: MouseEvent, el: HTMLElement): number {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
if (rect.width <= 0) return 1;
|
||||||
|
return Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillOf(index: number): number {
|
||||||
|
const per = (max - min) / count;
|
||||||
|
return Math.min(1, Math.max(0, (value - min - index * per) / per));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHoverMove(e: MouseEvent, el: HTMLElement, index: number) {
|
||||||
|
if (disabled) return;
|
||||||
|
if (baseline === null) baseline = value;
|
||||||
|
value = valueAt(index, fractionOf(e, el));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHoverLeave() {
|
||||||
|
if (disabled) return;
|
||||||
|
if (baseline !== null) {
|
||||||
|
value = baseline;
|
||||||
|
baseline = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit(v: number) {
|
||||||
|
if (disabled) return;
|
||||||
|
value = clamp(v);
|
||||||
|
baseline = value;
|
||||||
|
onchange?.(value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="stars-container"
|
||||||
|
role="group"
|
||||||
|
aria-label="Star rating"
|
||||||
|
onmouseleave={onHoverLeave}
|
||||||
|
>
|
||||||
|
<div class="stars">
|
||||||
|
{#if name !== undefined}
|
||||||
|
<input type="hidden" name={name} value={value} />
|
||||||
|
{/if}
|
||||||
|
{#each Array(count) as _, i}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex items-center justify-center p-0 border-0 bg-transparent"
|
||||||
|
disabled={disabled}
|
||||||
|
aria-label={`Rate ${i + 1} out of ${count}`}
|
||||||
|
onmousemove={(e) => onHoverMove(e, e.currentTarget, i)}
|
||||||
|
onclick={() => commit(value)}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
|
||||||
|
e.preventDefault();
|
||||||
|
commit(value + step);
|
||||||
|
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
|
||||||
|
e.preventDefault();
|
||||||
|
commit(value - step);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Star
|
||||||
|
id={`star-${i}`}
|
||||||
|
readOnly={disabled}
|
||||||
|
starConfig={starConfig}
|
||||||
|
fillPercentage={fillOf(i)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.stars-container{ position: relative; display: flex; align-items: center; justify-content: center; gap: .5rem; }
|
||||||
|
.stars{ display: flex; align-items: center; justify-content: center; gap: .5rem; }
|
||||||
|
</style>
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<!-- Originally from @ernane/svelte-star-rating. Wanted to give credit but could not use from the library without causing program crash. -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Star from './components/Star.svelte';
|
|
||||||
export let config = {
|
|
||||||
readOnly: false,
|
|
||||||
countStars: 5,
|
|
||||||
range: { min: 0, max: 5, step: 0.001 },
|
|
||||||
score: 0.0,
|
|
||||||
showScore: true,
|
|
||||||
name: "stars",
|
|
||||||
scoreFormat: function(){ return `(${this.score.toFixed(0)}/${this.countStars})` },
|
|
||||||
starConfig: {
|
|
||||||
size: 30,
|
|
||||||
fillColor: '#F9ED4F',
|
|
||||||
strokeColor: "#BB8511",
|
|
||||||
unfilledColor: '#FFF',
|
|
||||||
strokeUnfilledColor: '#000'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section class="stars-container">
|
|
||||||
<div class="range-stars">
|
|
||||||
<div class="stars">
|
|
||||||
{#each Array(config.countStars) as star, id}
|
|
||||||
{#if Math.floor(config.score) === id}
|
|
||||||
<Star id={config.name + id} readOnly={config.readOnly} starConfig={config.starConfig} fillPercentage={config.score - Math.floor(config.score)}/>
|
|
||||||
{:else if Math.floor(config.score) > id}
|
|
||||||
<Star id={config.name + id} readOnly={config.readOnly} starConfig={config.starConfig} fillPercentage={1}/>
|
|
||||||
{:else}
|
|
||||||
<Star id={config.name + id} readOnly={config.readOnly} starConfig={config.starConfig} fillPercentage={0}/>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<input name={config.name}
|
|
||||||
class="slider"
|
|
||||||
type="range"
|
|
||||||
min={config.readOnly ? config.score : config.range.min}
|
|
||||||
max={config.readOnly ? config.score : config.range.max}
|
|
||||||
step="{config.range.step}" bind:value={config.score}
|
|
||||||
on:change
|
|
||||||
on:click
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{#if config.showScore}
|
|
||||||
<span class="show-score" style="font-size: {config.starConfig.size/2}px;">
|
|
||||||
{#if config.scoreFormat}
|
|
||||||
{config.scoreFormat()}
|
|
||||||
{:else}
|
|
||||||
({((config.score/config.countStars)*100).toFixed(2)}%)
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.stars-container{ position: relative; display: flex; align-items: center; justify-content: center; gap: .5rem; }
|
|
||||||
.range-stars{ position: relative; }
|
|
||||||
.stars{ display: flex; align-items: center; justify-content: center; gap: .5rem; }
|
|
||||||
.slider{ opacity: 0; cursor: pointer; position: absolute; top: 0; left: 0; right: 0; height: 100%; }
|
|
||||||
.show-score{ user-select: none; color: #888 }
|
|
||||||
</style>
|
|
||||||
+57
-3
@@ -1,6 +1,60 @@
|
|||||||
@tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
@plugin 'flowbite/plugin';
|
||||||
|
|
||||||
|
@source "../node_modules/flowbite-svelte/dist";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-primary-50: #FFF5F2;
|
||||||
|
--color-primary-100: #FFF1EE;
|
||||||
|
--color-primary-200: #FFE4DE;
|
||||||
|
--color-primary-300: #FFD5CC;
|
||||||
|
--color-primary-400: #FFBCAD;
|
||||||
|
--color-primary-500: #FE795D;
|
||||||
|
--color-primary-600: #EF562F;
|
||||||
|
--color-primary-700: #EB4F27;
|
||||||
|
--color-primary-800: #CC4522;
|
||||||
|
--color-primary-900: #A5371B;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility container {
|
||||||
|
width: 100%;
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: 1rem;
|
||||||
|
|
||||||
|
@media (width >= theme(--breakpoint-sm)) {
|
||||||
|
max-width: theme(--breakpoint-sm);
|
||||||
|
padding-inline: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width >= theme(--breakpoint-md)) {
|
||||||
|
max-width: theme(--breakpoint-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width >= theme(--breakpoint-lg)) {
|
||||||
|
max-width: theme(--breakpoint-lg);
|
||||||
|
padding-inline: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width >= theme(--breakpoint-xl)) {
|
||||||
|
max-width: theme(--breakpoint-xl);
|
||||||
|
padding-inline: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width >= theme(--breakpoint-2xl)) {
|
||||||
|
max-width: theme(--breakpoint-2xl);
|
||||||
|
padding-inline: 6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
/* Tailwind v4 removed the v3 preflight rule that gave buttons a
|
||||||
|
pointer cursor. Restore it (disabled buttons keep the default). */
|
||||||
|
button:not(:disabled),
|
||||||
|
[role="button"]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background-color: rgba(27, 38, 54, 1);
|
background-color: rgba(27, 38, 54, 1);
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import sveltePreprocess from 'svelte-preprocess'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
|
||||||
// for more information about preprocessors
|
|
||||||
preprocess: sveltePreprocess()
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import flowbitePlugin from 'flowbite/plugin'
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: [
|
|
||||||
"./index.html",
|
|
||||||
"./src/**/*.{svelte,js,ts,jsx,tsx}",
|
|
||||||
"./node_modules/flowbite/**/*.{html,js,svelte,ts}",
|
|
||||||
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
|
|
||||||
],
|
|
||||||
plugins: [ flowbitePlugin ],
|
|
||||||
|
|
||||||
darkMode: 'media',
|
|
||||||
|
|
||||||
theme: {
|
|
||||||
container: {
|
|
||||||
center: true,
|
|
||||||
padding: {
|
|
||||||
DEFAULT: '1rem',
|
|
||||||
sm: '2rem',
|
|
||||||
lg: '4rem',
|
|
||||||
xl: '5rem',
|
|
||||||
'2xl': '6rem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
// flowbite-svelte
|
|
||||||
primary: {
|
|
||||||
50: '#FFF5F2',
|
|
||||||
100: '#FFF1EE',
|
|
||||||
200: '#FFE4DE',
|
|
||||||
300: '#FFD5CC',
|
|
||||||
400: '#FFBCAD',
|
|
||||||
500: '#FE795D',
|
|
||||||
600: '#EF562F',
|
|
||||||
700: '#EB4F27',
|
|
||||||
800: '#CC4522',
|
|
||||||
900: '#A5371B'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import {defineConfig} from 'vite'
|
import {defineConfig} from 'vite'
|
||||||
import {svelte} from '@sveltejs/vite-plugin-svelte'
|
import {svelte} from '@sveltejs/vite-plugin-svelte'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import wails from '@wailsio/runtime/plugins/vite'
|
import wails from '@wailsio/runtime/plugins/vite'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
@@ -9,5 +10,5 @@ export default defineConfig({
|
|||||||
port: Number(process.env.WAILS_VITE_PORT) || 5173,
|
port: Number(process.env.WAILS_VITE_PORT) || 5173,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
},
|
},
|
||||||
plugins: [svelte(), wails('./bindings')]
|
plugins: [tailwindcss(), svelte(), wails('./bindings')]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ module AniTrack
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/tidwall/gjson v1.19.0
|
github.com/wailsapp/wails/v3 v3.0.0-beta.22
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-beta.20
|
|
||||||
github.com/zalando/go-keyring v0.2.8
|
github.com/zalando/go-keyring v0.2.8
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +15,5 @@ require (
|
|||||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||||
github.com/tidwall/match v1.2.0 // indirect
|
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/sys v0.46.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,14 +26,8 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
github.com/wailsapp/wails/v3 v3.0.0-beta.22 h1:GsdqwRB7nESQyeZKVv16DLIS30xCLMlZP/8UhgKnHYc=
|
||||||
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
github.com/wailsapp/wails/v3 v3.0.0-beta.22/go.mod h1:/6QR46/nhGCSADHbS++XtDb9dkTnenTHlGskTPRo9S0=
|
||||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
|
||||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-beta.20 h1:AKKrRGMSqzJET1W0Jt9v+f0oOqM9Amsi7LKMM6XnSOE=
|
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-beta.20/go.mod h1:/6QR46/nhGCSADHbS++XtDb9dkTnenTHlGskTPRo9S0=
|
|
||||||
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
|
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
|
||||||
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
|
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# instead of:
|
# instead of:
|
||||||
# make release VERSION=1.6.7
|
# make release VERSION=1.6.7
|
||||||
# Lives in the repo (no machine-specific alias needed). Tags are plain
|
# Lives in the repo (no machine-specific alias needed). Tags are plain
|
||||||
# versions (1.6.7) to match wails.json productVersion.
|
# versions (1.6.7) to match build/config.yml info.version.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
[ "$#" -ge 1 ] || { echo "Usage: ./release 1.6.7" >&2; exit 1; }
|
[ "$#" -ge 1 ] || { echo "Usage: ./release 1.6.7" >&2; exit 1; }
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// Code generated by `make release` from build/config.yml info.version.
|
||||||
|
// DO NOT EDIT.
|
||||||
|
const appVersionString = "2.0.0"
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "AniTrack",
|
|
||||||
"frontend": {
|
|
||||||
"dir": "./frontend",
|
|
||||||
"install": "npm install",
|
|
||||||
"build": "npm run build",
|
|
||||||
"dev": "npm run dev",
|
|
||||||
"devServerUrl": "http://localhost:5173"
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"name": "John O'Keefe",
|
|
||||||
"email": "admin@linuxhg.com"
|
|
||||||
},
|
|
||||||
"info": {
|
|
||||||
"productName": "AniTrack",
|
|
||||||
"productVersion": "1.99.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user