Compare commits
83 Commits
896c6640e2
...
0.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a3b4857175 | |||
| 6e4b9c041a | |||
| ae54fd20dd | |||
| 2c7e2d0eff | |||
| 9a9f055c38 | |||
| bc497521e7 | |||
| 48a6005725 | |||
| 93a94d0797 | |||
| a2576b044c | |||
| 2ee2d85e9e | |||
| 1090f112f3 | |||
| 58c9f449e0 | |||
| f016c90353 | |||
| 6bbe0f0f48 | |||
| d841fee1e7 | |||
| f29d8f378e | |||
| 35e93c0ca9 | |||
| 8c169d549a | |||
| b2a8a504f3 | |||
| c85a53a278 | |||
| 2cf3844e76 | |||
| 6ed5fe8b71 | |||
| 8a8baf7f8f | |||
| ca8c8beaf3 | |||
| 3e7f7d1c95 | |||
| b0ca864dfe | |||
| 5ed6dedeab | |||
| 3271af445a | |||
| e7e9e5b826 | |||
| 5337758dee | |||
| 426793e56a | |||
| a794b77654 | |||
| c510c2a138 | |||
| 8cbf5cb20c | |||
| 54a8924384 | |||
| d70153064f | |||
| 7960f8e26d | |||
| 2e5a4a4493 | |||
| 874b3952ee | |||
| 60eac10545 | |||
| 6d66d711ff | |||
| cd62e6c658 | |||
| 063c5016f3 | |||
| 5c4caf68e6 | |||
| c10e853564 | |||
| cd043d093f | |||
| 6db01f7f9f | |||
| 8460d56d55 | |||
| 8fedbe4607 | |||
| e069c47242 | |||
| aba0f2d1d5 | |||
| af6cb7f08a | |||
| 487e5ee5a8 | |||
| 631bd8b885 | |||
| b35be6926a | |||
| 5a9f4391dc | |||
| 72004c98b4 | |||
| 3db25bc33a | |||
| f3a1536953 | |||
| dc01aa314c | |||
| baed9a4a67 | |||
| 0e00120778 | |||
| 697692c277 | |||
| 3e82677c2c | |||
| b81549e5a6 | |||
| fd806df0a9 | |||
| 26f85dd412 | |||
| 4d9c54a116 | |||
| 068e568ec6 | |||
| 4e6f910e74 | |||
| b39f19f03a | |||
| 1a083deb54 | |||
| 61f8f5dd84 | |||
| 4509e479bc | |||
| ee8dd2e866 | |||
| 0c6a8a40c3 | |||
| 45845c2a69 | |||
| 3ec5eb1a03 | |||
| a2aa90edec | |||
| d08283bd2d | |||
| 73d349ee1a | |||
| c9c6650829 | |||
| 0475d39c6c |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -30,3 +30,9 @@ package-lock.json
|
||||
.idea
|
||||
.env
|
||||
environment.go
|
||||
|
||||
# REST (http files)
|
||||
http-client.private.env.json
|
||||
|
||||
# Build artifacts
|
||||
build/*.tar.gz
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -93,6 +94,7 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
genres
|
||||
tags{
|
||||
id
|
||||
name
|
||||
@@ -182,7 +184,7 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
|
||||
return post
|
||||
}
|
||||
|
||||
func (a *App) AniListSearch(query string) any {
|
||||
func (a *App) AniListSearch(query string) (interface{}, error) {
|
||||
type Variables struct {
|
||||
Search string `json:"search"`
|
||||
ListType string `json:"listType"`
|
||||
@@ -222,6 +224,7 @@ func (a *App) AniListSearch(query string) any {
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
genres
|
||||
tags{
|
||||
id
|
||||
name
|
||||
@@ -240,18 +243,20 @@ func (a *App) AniListSearch(query string) any {
|
||||
ListType: "ANIME",
|
||||
},
|
||||
}
|
||||
returnedBody, _ := AniListQuery(body, false)
|
||||
|
||||
returnedBody, status := AniListQuery(body, false)
|
||||
if status != "200 OK" {
|
||||
return nil, fmt.Errorf("API search failed with status: %s", status)
|
||||
}
|
||||
var post interface{}
|
||||
err := json.Unmarshal(returnedBody, &post)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return nil, fmt.Errorf("Failed to parse search results")
|
||||
}
|
||||
return post, nil
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList {
|
||||
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (AniListCurrentUserWatchList, error) {
|
||||
user := a.GetAniListLoggedInUser()
|
||||
type Variables struct {
|
||||
Page int `json:"page"`
|
||||
@@ -307,6 +312,7 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
genres
|
||||
tags{
|
||||
id
|
||||
name
|
||||
@@ -401,11 +407,15 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
|
||||
err := json.Unmarshal(returnedBody, &badPost)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return post, fmt.Errorf("API authentication error")
|
||||
}
|
||||
log.Fatal(badPost.Errors[0].Message)
|
||||
return post, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message)
|
||||
}
|
||||
if status != "200 OK" {
|
||||
return post, fmt.Errorf("API request failed with status: %s", status)
|
||||
}
|
||||
|
||||
return post
|
||||
return post, nil
|
||||
}
|
||||
|
||||
func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime {
|
||||
|
||||
@@ -74,6 +74,7 @@ type MediaList struct {
|
||||
TimeUntilAiring int `json:"timeUntilAiring"`
|
||||
Episode int `json:"episode"`
|
||||
} `json:"nextAiringEpisode"`
|
||||
Genres []string `json:"genres"`
|
||||
Tags []struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -11,10 +11,19 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string) {
|
||||
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
req, _ := http.NewRequest(method, malUrl, strings.NewReader(body.Encode()))
|
||||
req, err := http.NewRequest(method, malUrl, strings.NewReader(body.Encode()))
|
||||
|
||||
if err != nil {
|
||||
message, _ := json.Marshal(struct {
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Message: "Failed to create request: " + err.Error(),
|
||||
})
|
||||
return message, "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken)
|
||||
@@ -22,47 +31,57 @@ func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage,
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Errored when sending request to the server")
|
||||
message, _ := json.Marshal(struct {
|
||||
Message string `json:"message" ts_type:"message"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Message: "Errored when sending request to the server" + err.Error(),
|
||||
Message: "Network error: " + err.Error(),
|
||||
})
|
||||
|
||||
return message, resp.Status
|
||||
return message, "", fmt.Errorf("network error: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, resp.Status, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.Status == "401 Unauthorized" {
|
||||
refreshMyAnimeListAuthorizationToken()
|
||||
MALHelper(method, malUrl, body)
|
||||
return MALHelper(method, malUrl, body)
|
||||
}
|
||||
|
||||
return respBody, resp.Status
|
||||
if resp.Status != "200 OK" && resp.Status != "201 Created" && resp.Status != "202 Accepted" {
|
||||
return respBody, resp.Status, fmt.Errorf("API returned status: %s", resp.Status)
|
||||
}
|
||||
return respBody, resp.Status, nil
|
||||
}
|
||||
|
||||
func (a *App) GetMyAnimeList(count int) MALWatchlist {
|
||||
func (a *App) GetMyAnimeList(count int) (MALWatchlist, error) {
|
||||
limit := strconv.Itoa(count)
|
||||
user := a.GetMyAnimeListLoggedInUser()
|
||||
malUrl := "https://api.myanimelist.net/v2/users/" + user.Name + "/animelist?fields=list_status&status=watching&limit=" + limit
|
||||
|
||||
var malList MALWatchlist
|
||||
|
||||
respBody, resStatus := MALHelper("GET", malUrl, nil)
|
||||
respBody, resStatus, err := MALHelper("GET", malUrl, nil)
|
||||
|
||||
if err != nil {
|
||||
return malList, fmt.Errorf("failed to get MAL watchlist: %w", err)
|
||||
}
|
||||
|
||||
if resStatus == "200 OK" {
|
||||
err := json.Unmarshal(respBody, &malList)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal json response, %s\n", err)
|
||||
return malList, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return malList
|
||||
return malList, nil
|
||||
}
|
||||
|
||||
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListStatus {
|
||||
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) (MalListStatus, error) {
|
||||
if update.NumTimesRewatched >= 1 {
|
||||
update.IsRewatching = true
|
||||
} else {
|
||||
@@ -78,39 +97,52 @@ func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListS
|
||||
|
||||
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(anime.Id) + "/my_list_status"
|
||||
var status MalListStatus
|
||||
respBody, respStatus := MALHelper("PATCH", malUrl, body)
|
||||
respBody, respStatus, err := MALHelper("PATCH", malUrl, body)
|
||||
|
||||
if err != nil {
|
||||
return status, fmt.Errorf("failed to update MAL entry: %w", err)
|
||||
}
|
||||
|
||||
if respStatus == "200 OK" {
|
||||
err := json.Unmarshal(respBody, &status)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal json response, %s\n", err)
|
||||
return status, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (a *App) GetMyAnimeListAnime(id int) MALAnime {
|
||||
func (a *App) GetMyAnimeListAnime(id int) (MALAnime, error) {
|
||||
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,genres,created_at,updated_at,media_type,status,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,recommendations,studios,statistics"
|
||||
respBody, respStatus := MALHelper("GET", malUrl, nil)
|
||||
var malAnime MALAnime
|
||||
respBody, respStatus, err := MALHelper("GET", malUrl, nil)
|
||||
|
||||
if err != nil {
|
||||
return malAnime, fmt.Errorf("failed to get MAL anime: %w", err)
|
||||
}
|
||||
|
||||
if respStatus == "200 OK" {
|
||||
err := json.Unmarshal(respBody, &malAnime)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal json response, %s\n", err)
|
||||
return malAnime, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
}
|
||||
return malAnime, nil
|
||||
}
|
||||
|
||||
return malAnime
|
||||
}
|
||||
|
||||
func (a *App) DeleteMyAnimeListEntry(id int) bool {
|
||||
func (a *App) DeleteMyAnimeListEntry(id int) (bool, error) {
|
||||
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "/my_list_status"
|
||||
_, respStatus := MALHelper("DELETE", malUrl, nil)
|
||||
_, respStatus, err := MALHelper("DELETE", malUrl, nil)
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to delete MAL entry: %w", err)
|
||||
}
|
||||
|
||||
if respStatus == "200 OK" {
|
||||
return true
|
||||
return true, nil
|
||||
} else {
|
||||
return false
|
||||
return false, fmt.Errorf("delete failed with status: %s", respStatus)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ func (v *CodeVerifier) CodeChallengeS256() string {
|
||||
}
|
||||
|
||||
func (a *App) CheckIfMyAnimeListLoggedIn() bool {
|
||||
fmt.Println("check function reached")
|
||||
if (MyAnimeListJWT{} == myAnimeListJwt) {
|
||||
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
|
||||
expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn")
|
||||
@@ -96,7 +95,6 @@ func (a *App) CheckIfMyAnimeListLoggedIn() bool {
|
||||
}
|
||||
|
||||
func (a *App) MyAnimeListLogin() {
|
||||
fmt.Println("login function reached")
|
||||
if !a.CheckIfMyAnimeListLoggedIn() {
|
||||
fmt.Println("check logged in function failed")
|
||||
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
|
||||
@@ -178,7 +176,7 @@ func (a *App) handleMyAnimeListCallback(wg *sync.WaitGroup, verifier *CodeVerifi
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("listen: %s\n", err)
|
||||
log.Printf("Server error: %s\n", err)
|
||||
}
|
||||
fmt.Println("Shutting down...")
|
||||
}()
|
||||
@@ -310,7 +308,16 @@ func refreshMyAnimeListAuthorizationToken() {
|
||||
|
||||
func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser {
|
||||
a.MyAnimeListLogin()
|
||||
user := createUser()
|
||||
if user.Name == "" {
|
||||
refreshMyAnimeListAuthorizationToken()
|
||||
user = createUser()
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func createUser() MyAnimeListUser {
|
||||
client := &http.Client{}
|
||||
|
||||
req, _ := http.NewRequest("GET", "https://api.myanimelist.net/v2/users/@me?fields=anime_statistics", nil)
|
||||
@@ -337,6 +344,7 @@ func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser {
|
||||
}
|
||||
|
||||
return user
|
||||
|
||||
}
|
||||
|
||||
func (a *App) LogoutMyAnimeList() string {
|
||||
|
||||
166
RELEASE_NOTES_v0.7.0.md
Normal file
166
RELEASE_NOTES_v0.7.0.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# 🎉 AniTrack v0.7.0 Release Notes
|
||||
|
||||
## 🚨 Major Feature: Comprehensive Error Handling
|
||||
|
||||
This release brings **massive improvements** to application stability and user experience. The app will **no longer crash** when API services are down or experiencing errors!
|
||||
|
||||
---
|
||||
|
||||
## ✨ What's New
|
||||
|
||||
### 🛡️ Graceful API Error Handling
|
||||
- **Application Stability**: App now remains running even when AniList, MAL, or Simkl APIs are down
|
||||
- **Visual Error Feedback**: Beautiful modal dialogs display error details instead of silent crashes
|
||||
- **Manual Retry**: Users can retry failed API connections with a single button click
|
||||
- **Continue Using App**: Dismiss error messages and continue with limited functionality
|
||||
|
||||
### 🔧 Backend Improvements
|
||||
|
||||
#### AniList Integration
|
||||
- Replaced all `log.Fatal()` calls with proper error returns
|
||||
- Added error handling for API failures, network issues, and authentication errors
|
||||
- `AniListSearch` now returns detailed error messages
|
||||
- `GetAniListUserWatchingList` gracefully handles 403/404 responses
|
||||
|
||||
#### MyAnimeList Integration
|
||||
- Updated `MALHelper` to return structured errors
|
||||
- All MAL API functions now return errors instead of crashing:
|
||||
- `GetMyAnimeList`
|
||||
- `MyAnimeListUpdate`
|
||||
- `GetMyAnimeListAnime`
|
||||
- `DeleteMyAnimeListEntry`
|
||||
- Fixed OAuth callback server to prevent shutdown on errors
|
||||
|
||||
#### Simkl Integration
|
||||
- Updated `SimklHelper` with status code validation (200-299 range)
|
||||
- All Simkl API functions now return errors:
|
||||
- `SimklGetUserWatchlist`
|
||||
- `SimklSyncEpisodes`
|
||||
- `SimklSyncRating`
|
||||
- `SimklSyncStatus`
|
||||
- `SimklSearch`
|
||||
- `SimklSyncRemove`
|
||||
- Replaced `log.Fatal()` with proper error logging
|
||||
|
||||
### 💻 Frontend Improvements
|
||||
|
||||
#### New Error Management System
|
||||
- **Centralized Error State**: New reactive stores for API errors
|
||||
- `apiError` - Tracks current error with service, message, and status
|
||||
- `isApiDown` - Quick flag for API availability
|
||||
- **Error Helper Functions**:
|
||||
- `setApiError()` - Set error state from any component
|
||||
- `clearApiError()` - Reset error state
|
||||
|
||||
#### New Error Modal Component
|
||||
- Auto-displays when API errors occur
|
||||
- Shows service name, error message, and HTTP status code
|
||||
- **"Retry Connection"** button - Attempts to reconnect to the failed service
|
||||
- **"Dismiss"** button - Close modal and continue using the app
|
||||
- Beautiful red-themed styling with Tailwind CSS
|
||||
- Supports all three services (AniList, MAL, Simkl)
|
||||
|
||||
#### Updated User Interface
|
||||
- Home page now displays "API Unavailable" state when services are down
|
||||
- Helpful warning messages guide users during outages
|
||||
- App structure remains visible and functional during errors
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
- **Critical**: Fixed app crashes when AniList API returns 403 Forbidden errors
|
||||
- **Critical**: Fixed app crashes when AniList API is down or unreachable
|
||||
- **Critical**: Fixed MAL OAuth server crashing on connection errors
|
||||
- **Critical**: Fixed Simkl JSON marshaling errors crashing the app
|
||||
- **Improved**: Better error messages for all API failures
|
||||
|
||||
---
|
||||
|
||||
## 📊 Technical Details
|
||||
|
||||
### Go Backend Changes
|
||||
- **AniListFunctions.go**: Updated 3 functions with error handling
|
||||
- **MALFunctions.go**: Updated 5 functions with error handling
|
||||
- **MALUserFunctions.go**: Fixed server error handling
|
||||
- **SimklFunctions.go**: Updated 7 functions with error handling
|
||||
- **SimklUserFunctions.go**: Fixed 2 critical error handling issues
|
||||
|
||||
### Frontend Changes
|
||||
- **ErrorModal.svelte**: New component (73 lines)
|
||||
- **GlobalVariablesAndHelperFunctions.svelte**: Added error state system (27 lines)
|
||||
- **App.svelte**: Integrated ErrorModal into main layout
|
||||
- **CheckIfAniListLoggedInAndLoadWatchList.svelte**: Added error handling
|
||||
- **Home.svelte**: Added API down state display
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Impact
|
||||
|
||||
### Before (v0.6.5)
|
||||
- ❌ App crashes to desktop when AniList API is down
|
||||
- ❌ Users see only console error messages
|
||||
- ❌ Must restart app after API failures
|
||||
- ❌ No way to retry failed connections
|
||||
|
||||
### After (v0.7.0)
|
||||
- ✅ App stays open during any API failures
|
||||
- ✅ Beautiful modal dialogs explain what went wrong
|
||||
- ✅ One-click retry to reconnect services
|
||||
- ✅ Continue using app with limited functionality
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Upgrading
|
||||
|
||||
No migration needed! This release is fully backward compatible.
|
||||
|
||||
Simply run:
|
||||
```bash
|
||||
git pull
|
||||
wails build
|
||||
```
|
||||
|
||||
Or download the latest release from the releases page.
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
This release addresses a critical stability issue that was affecting users during API service outages. Special thanks to the community for reporting these crashes and providing detailed error logs.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Full Changelog
|
||||
|
||||
### Added
|
||||
- Error modal component with retry/dismiss functionality
|
||||
- Centralized error state management system
|
||||
- API availability status tracking
|
||||
- Visual feedback for all API failures
|
||||
- Manual retry functionality for all services
|
||||
|
||||
### Changed
|
||||
- AniList API functions to return errors instead of crashing
|
||||
- MAL API functions to return errors instead of crashing
|
||||
- Simkl API functions to return errors instead of crashing
|
||||
- Error handling from console-only to UI modal dialogs
|
||||
- App behavior from crash-on-error to graceful-degradation
|
||||
|
||||
### Fixed
|
||||
- App crashes on AniList 403 errors
|
||||
- App crashes on network failures
|
||||
- MAL OAuth server shutdown on errors
|
||||
- Simkl JSON marshaling crashes
|
||||
- All `log.Fatal()` calls that terminated the app
|
||||
|
||||
### Removed
|
||||
- `alert()` calls in favor of modal system
|
||||
- Vite timestamp file (cleanup)
|
||||
|
||||
---
|
||||
|
||||
**Version**: 0.7.0
|
||||
**Release Date**: March 2026
|
||||
**Upgrade Difficulty**: ⭐ Easy (backward compatible)
|
||||
**Recommended**: ⭐⭐⭐⭐⭐ Highly recommended for all users
|
||||
@@ -14,16 +14,24 @@ import (
|
||||
|
||||
var SimklWatchList SimklWatchListType
|
||||
|
||||
func SimklHelper(method string, url string, body interface{}) json.RawMessage {
|
||||
reader, _ := json.Marshal(body)
|
||||
func SimklHelper(method string, url string, body interface{}) (json.RawMessage, error) {
|
||||
reader, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal body: %w", err)
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
if body != nil {
|
||||
req, _ = http.NewRequest(method, url, bytes.NewBuffer(reader))
|
||||
req, err = http.NewRequest(method, url, bytes.NewBuffer(reader))
|
||||
} else {
|
||||
req, _ = http.NewRequest(method, url, nil)
|
||||
req, err = http.NewRequest(method, url, nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
@@ -32,41 +40,45 @@ func SimklHelper(method string, url string, body interface{}) json.RawMessage {
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println("Errored when sending request to the server")
|
||||
message, _ := json.Marshal(struct {
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Message: "Errored when sending request to the server" + err.Error(),
|
||||
})
|
||||
|
||||
return message
|
||||
return nil, fmt.Errorf("network error: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
|
||||
return respBody
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return respBody, fmt.Errorf("API returned status: %d", resp.StatusCode)
|
||||
}
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklGetUserWatchlist() SimklWatchListType {
|
||||
func (a *App) SimklGetUserWatchlist() (SimklWatchListType, error) {
|
||||
method := "GET"
|
||||
url := "https://api.simkl.com/sync/all-items/anime"
|
||||
|
||||
respBody := SimklHelper(method, url, nil)
|
||||
respBody, err := SimklHelper(method, url, nil)
|
||||
|
||||
if err != nil {
|
||||
return SimklWatchListType{}, fmt.Errorf("failed to get Simkl watchlist: %w", err)
|
||||
}
|
||||
|
||||
var errCheck struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(respBody, &errCheck)
|
||||
err = json.Unmarshal(respBody, &errCheck)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return SimklWatchListType{}, fmt.Errorf("failed to parse error response: %w", err)
|
||||
}
|
||||
|
||||
if errCheck.Error != "" {
|
||||
a.LogoutSimkl()
|
||||
return SimklWatchListType{}
|
||||
return SimklWatchListType{}, fmt.Errorf("Simkl API error: %s", errCheck.Message)
|
||||
}
|
||||
|
||||
var watchlist SimklWatchListType
|
||||
@@ -74,14 +86,15 @@ func (a *App) SimklGetUserWatchlist() SimklWatchListType {
|
||||
err = json.Unmarshal(respBody, &watchlist)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return SimklWatchListType{}, fmt.Errorf("failed to parse watchlist: %w", err)
|
||||
}
|
||||
|
||||
SimklWatchList = watchlist
|
||||
|
||||
return watchlist
|
||||
return watchlist, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
||||
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, error) {
|
||||
var episodes []Episode
|
||||
var url string
|
||||
var shows []SimklPostShow
|
||||
@@ -112,13 +125,19 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
||||
|
||||
simklSync := SimklSyncHistoryType{shows}
|
||||
|
||||
respBody := SimklHelper("POST", url, simklSync)
|
||||
respBody, err := SimklHelper("POST", url, simklSync)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to sync episodes: %s\n", err)
|
||||
return anime, fmt.Errorf("failed to sync episodes: %w", err)
|
||||
}
|
||||
|
||||
var success interface{}
|
||||
|
||||
err := json.Unmarshal(respBody, &success)
|
||||
err = json.Unmarshal(respBody, &success)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return anime, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
for i, simklAnime := range SimklWatchList.Anime {
|
||||
@@ -131,10 +150,10 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
||||
|
||||
WatchListUpdate(anime)
|
||||
|
||||
return anime
|
||||
return anime, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
||||
func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error) {
|
||||
var url string
|
||||
showWithRating := ShowWithRating{
|
||||
Title: anime.Show.Title,
|
||||
@@ -169,13 +188,17 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
||||
Shows []interface{} `json:"shows" ts_type:"shows"`
|
||||
}{shows}
|
||||
|
||||
respBody := SimklHelper("POST", url, simklSync)
|
||||
|
||||
respBody, err := SimklHelper("POST", url, simklSync)
|
||||
if err != nil {
|
||||
log.Printf("Failed to sync rating: %s\n", err)
|
||||
return anime, fmt.Errorf("failed to sync rating: %w", err)
|
||||
}
|
||||
var success interface{}
|
||||
|
||||
err := json.Unmarshal(respBody, &success)
|
||||
err = json.Unmarshal(respBody, &success)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return anime, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
for i, simklAnime := range SimklWatchList.Anime {
|
||||
@@ -188,10 +211,10 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
||||
|
||||
WatchListUpdate(anime)
|
||||
|
||||
return anime
|
||||
return anime, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
||||
func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, error) {
|
||||
url := "https://api.simkl.com/sync/add-to-list"
|
||||
show := SimklShowStatus{
|
||||
Title: anime.Show.Title,
|
||||
@@ -211,13 +234,17 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
||||
Shows []SimklShowStatus `json:"shows" ts_type:"shows"`
|
||||
}{shows}
|
||||
|
||||
respBody := SimklHelper("POST", url, simklSync)
|
||||
|
||||
respBody, err := SimklHelper("POST", url, simklSync)
|
||||
if err != nil {
|
||||
log.Printf("Failed to sync status: %s\n", err)
|
||||
return anime, fmt.Errorf("failed to sync status: %w", err)
|
||||
}
|
||||
var success interface{}
|
||||
|
||||
err := json.Unmarshal(respBody, &success)
|
||||
err = json.Unmarshal(respBody, &success)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return anime, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
for i, simklAnime := range SimklWatchList.Anime {
|
||||
@@ -230,15 +257,20 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
||||
|
||||
WatchListUpdate(anime)
|
||||
|
||||
return anime
|
||||
return anime, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
||||
func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) {
|
||||
var result SimklAnime
|
||||
|
||||
if reflect.DeepEqual(SimklWatchList, SimklWatchListType{}) {
|
||||
fmt.Println("Watchlist empty. Calling...")
|
||||
SimklWatchList = a.SimklGetUserWatchlist()
|
||||
watchlist, err := a.SimklGetUserWatchlist()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get watchlist: %s\n", err)
|
||||
return result, fmt.Errorf("failed to load watchlist for search: %w", err)
|
||||
}
|
||||
SimklWatchList = watchlist
|
||||
}
|
||||
|
||||
for _, anime := range SimklWatchList.Anime {
|
||||
@@ -255,23 +287,30 @@ func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
||||
var anime SimklSearchType
|
||||
url := "https://api.simkl.com/search/id?anilist=" + strconv.Itoa(aniListAnime.Media.ID)
|
||||
|
||||
respBody := SimklHelper("GET", url, nil)
|
||||
|
||||
err := json.Unmarshal(respBody, &anime)
|
||||
respBody, err := SimklHelper("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to search Simkl: %s\n", err)
|
||||
return result, fmt.Errorf("failed to search Simkl by AniList ID: %w", err)
|
||||
}
|
||||
err = json.Unmarshal(respBody, &anime)
|
||||
|
||||
if len(anime) == 0 {
|
||||
url = "https://api.simkl.com/search/id?mal=" + strconv.Itoa(aniListAnime.Media.IDMal)
|
||||
respBody = SimklHelper("GET", url, nil)
|
||||
fmt.Println(string(respBody))
|
||||
respBody, err = SimklHelper("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to search Simkl by MAL ID: %s\n", err)
|
||||
return result, fmt.Errorf("failed to search by MAL ID: %w", err)
|
||||
}
|
||||
err = json.Unmarshal(respBody, &anime)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return result, fmt.Errorf("failed to parse search results: %w", err)
|
||||
}
|
||||
|
||||
if len(anime) == 0 {
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for _, watchListAnime := range SimklWatchList.Anime {
|
||||
@@ -289,10 +328,10 @@ func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSyncRemove(anime SimklAnime) bool {
|
||||
func (a *App) SimklSyncRemove(anime SimklAnime) (bool, error) {
|
||||
url := "https://api.simkl.com/sync/history/remove"
|
||||
var showArray []SimklShowStatus
|
||||
|
||||
@@ -313,25 +352,28 @@ func (a *App) SimklSyncRemove(anime SimklAnime) bool {
|
||||
Shows: showArray,
|
||||
}
|
||||
|
||||
respBody := SimklHelper("POST", url, show)
|
||||
respBody, err := SimklHelper("POST", url, show)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to sync remove: %s\n", err)
|
||||
return false, fmt.Errorf("failed to sync remove: %w", err)
|
||||
}
|
||||
var success SimklDeleteType
|
||||
|
||||
err := json.Unmarshal(respBody, &success)
|
||||
err = json.Unmarshal(respBody, &success)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return false, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
if success.Deleted.Shows >= 1 {
|
||||
for i, simklAnime := range SimklWatchList.Anime {
|
||||
if simklAnime.Show.Ids.Simkl == anime.Show.Ids.Simkl {
|
||||
SimklWatchList.Anime = slices.Delete(SimklWatchList.Anime, i, i+1)
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("no shows were deleted")
|
||||
}
|
||||
|
||||
func WatchListUpdate(anime SimklAnime) {
|
||||
|
||||
@@ -116,7 +116,7 @@ func (a *App) handleSimklCallback(wg *sync.WaitGroup) {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("listen: %s\n", err)
|
||||
log.Printf("Server error: %s\n", err)
|
||||
}
|
||||
fmt.Println("Shutting down...")
|
||||
}()
|
||||
@@ -138,7 +138,8 @@ func getSimklAuthorizationToken(content string) SimklJWT {
|
||||
}
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Printf("Failed to marshal data: %s\n", err)
|
||||
return SimklJWT{}
|
||||
}
|
||||
|
||||
response, err := http.NewRequest("POST", "https://api.simkl.com/oauth/token", bytes.NewBuffer(jsonData))
|
||||
|
||||
15
app.go
15
app.go
@@ -3,9 +3,11 @@ package main
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
@@ -43,3 +45,14 @@ func (a *App) onSecondInstanceLaunch(secondInstanceData options.SecondInstanceDa
|
||||
runtime.Show(*wailsContext)
|
||||
go runtime.EventsEmit(*wailsContext, "launchArgs", secondInstanceArgs)
|
||||
}
|
||||
|
||||
func (a *App) ShowVersion() {
|
||||
version := gjson.Get(wailsJSON, "info.productVersion")
|
||||
_, err := runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{
|
||||
Title: "Version",
|
||||
Message: "AniTrack Version: " + version.String(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ body:graphql {
|
||||
media {
|
||||
id
|
||||
idMal
|
||||
genres
|
||||
tags {
|
||||
id
|
||||
name
|
||||
|
||||
@@ -33,6 +33,7 @@ body:graphql {
|
||||
english
|
||||
native
|
||||
}
|
||||
genre
|
||||
description
|
||||
coverImage {
|
||||
large
|
||||
|
||||
@@ -12,7 +12,7 @@ post {
|
||||
|
||||
headers {
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
@@ -20,7 +20,7 @@ body:form-urlencoded {
|
||||
client_id: {{ANILIST_APP_ID}}
|
||||
client_secret: {{ANILIST_SECRET_TOKEN}}
|
||||
redirect_uri: http://localhost:6734/callback
|
||||
code: {{code}}
|
||||
code: {{ANILIST_CODE}}
|
||||
}
|
||||
|
||||
body:multipart-form {
|
||||
|
||||
@@ -9,7 +9,7 @@ vars {
|
||||
}
|
||||
vars:secret [
|
||||
ANILIST_ACCESS_TOKEN,
|
||||
code,
|
||||
ANILIST_CODE,
|
||||
SIMKL_AUTH_TOKEN,
|
||||
MAL_CODE,
|
||||
MAL_VERIFIER,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
BIN
build/icon/128/AniTrack.png
Normal file
BIN
build/icon/128/AniTrack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
BIN
build/icon/32/AniTrack.png
Normal file
BIN
build/icon/32/AniTrack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
build/icon/48/AniTrack.png
Normal file
BIN
build/icon/48/AniTrack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
build/icon/64/AniTrack.png
Normal file
BIN
build/icon/64/AniTrack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
26
build/install_linux.sh
Executable file
26
build/install_linux.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# copy desktop file
|
||||
if [ -e "~/.local/share/applications/AniTrack.desktop" ]; then
|
||||
if [ -d "~/.local/share/applications/" ]; then
|
||||
cp ./AniTrack.desktop ~/.local/share/applications/
|
||||
else
|
||||
mkdir -p ~/.local/share/applications/
|
||||
cp ./AniTrack.desktop ~/.local/share/applications/
|
||||
fi
|
||||
fi
|
||||
|
||||
# copy icons to xdg folders
|
||||
for size in 32 48 64 128; do
|
||||
xdg-icon-resource install --novendor --context apps --size $size ./icon/$size/AniTrack.png AniTrack
|
||||
done
|
||||
|
||||
# copy AniTrack Binary to $HOME/Applications/
|
||||
if ! [ -d "~/Applications" ]; then
|
||||
mkdir -p ~/Applications
|
||||
cp ./bin/AniTrack ~/Applications/
|
||||
elif ! [[ -e ~/Applications/AniTrack ]]; then
|
||||
cp ./bin/AniTrack ~/Applications/
|
||||
fi
|
||||
|
||||
echo "AniTrack has been successfully installed."
|
||||
5
frontend/.vscode/extensions.json
vendored
5
frontend/.vscode/extensions.json
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"svelte.svelte-vscode"
|
||||
]
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
"postcss": "^8.4.45",
|
||||
"svelte": "^4.0.0",
|
||||
"svelte-check": "^3.4.3",
|
||||
"svelte-headless-table": "^0.18.2",
|
||||
"svelte-headless-table": "^0.18.3",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"svelte-spa-router": "^4.0.1",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
|
||||
@@ -1,44 +1,72 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
aniListAnime,
|
||||
GetAnimeSingleItem,
|
||||
aniListLoggedIn,
|
||||
malLoggedIn,
|
||||
simklLoggedIn,
|
||||
watchlistNeedsRefresh,
|
||||
aniListPrimary,
|
||||
malPrimary,
|
||||
simklPrimary,
|
||||
malWatchList,
|
||||
simklWatchList,
|
||||
} from "./helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import Router from "svelte-spa-router"
|
||||
import Router from "svelte-spa-router";
|
||||
import Home from "./routes/Home.svelte";
|
||||
import { wrap } from "svelte-spa-router/wrap";
|
||||
import Spinner from "./helperComponents/Spinner.svelte";
|
||||
import Header from "./helperComponents/Header.svelte";
|
||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "./helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||
import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte";
|
||||
import {CheckIfSimklLoggedInAndSetUser} from "./helperModules/CheckIsSimklLoggedIn.svelte"
|
||||
import {CheckIfAniListLoggedIn} from "../wailsjs/go/main/App";
|
||||
import {AniListGetSingleAnimeDefaultData} from "./helperDefaults/AniListGetSingleAnime";
|
||||
import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte";
|
||||
import {
|
||||
CheckIfAniListLoggedIn,
|
||||
GetMyAnimeList,
|
||||
SimklGetUserWatchlist,
|
||||
} from "../wailsjs/go/main/App";
|
||||
import { loc } from "svelte-spa-router";
|
||||
import ErrorModal from "./helperComponents/ErrorModal.svelte";
|
||||
|
||||
onMount(async () => {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList()
|
||||
await CheckIfMALLoggedInAndSetUser()
|
||||
await CheckIfSimklLoggedInAndSetUser()
|
||||
})
|
||||
let isAniListLoggedIn: boolean;
|
||||
let isMALLoggedIn: boolean;
|
||||
let isSimklLoggedIn: boolean;
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
|
||||
!isAniListLoggedIn && (await CheckIfAniListLoggedInAndLoadWatchList());
|
||||
!isMALLoggedIn && (await CheckIfMALLoggedInAndSetUser());
|
||||
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
|
||||
});
|
||||
|
||||
$: if ($loc?.location === "/" && $watchlistNeedsRefresh) {
|
||||
(async () => {
|
||||
if ($aniListLoggedIn && $aniListPrimary) {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
}
|
||||
if ($malLoggedIn && $malPrimary) {
|
||||
await GetMyAnimeList(1000).then((w) => malWatchList.set(w));
|
||||
}
|
||||
if ($simklLoggedIn && $simklPrimary) {
|
||||
await SimklGetUserWatchlist().then((w) => simklWatchList.set(w));
|
||||
}
|
||||
|
||||
watchlistNeedsRefresh.set(false);
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
<Router routes={{
|
||||
'/': Home,
|
||||
'/anime/:id': wrap({
|
||||
asyncComponent: () => import('./routes/AnimeRoutePage.svelte'),
|
||||
conditions: [
|
||||
async () => await CheckIfAniListLoggedIn(),
|
||||
async (detail) => {
|
||||
aniListAnime.update(value => {
|
||||
value = AniListGetSingleAnimeDefaultData
|
||||
return value
|
||||
})
|
||||
await GetAnimeSingleItem(Number(detail.params.id), true)
|
||||
return Object.keys($aniListAnime).length!==0
|
||||
},
|
||||
],
|
||||
loadingComponent: Spinner
|
||||
<ErrorModal />
|
||||
<Router
|
||||
routes={{
|
||||
"/": Home,
|
||||
"/anime/:id": wrap({
|
||||
asyncComponent: () => import("./routes/AnimeRoutePage.svelte"),
|
||||
conditions: [async () => await CheckIfAniListLoggedIn()],
|
||||
loadingComponent: Spinner,
|
||||
}),
|
||||
// '*': "Not Found"
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2,79 +2,93 @@ export interface AniListCurrentUserWatchList {
|
||||
data: {
|
||||
Page: {
|
||||
pageInfo: {
|
||||
total: number
|
||||
perPage: number
|
||||
currentPage: number
|
||||
lastPage: number
|
||||
hasNextPage: boolean
|
||||
},
|
||||
mediaList: MediaList[]
|
||||
}
|
||||
}
|
||||
total: number;
|
||||
perPage: number;
|
||||
currentPage: number;
|
||||
lastPage: number;
|
||||
hasNextPage: boolean;
|
||||
};
|
||||
mediaList: MediaList[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface AniListGetSingleAnime {
|
||||
data: {
|
||||
MediaList: MediaList
|
||||
}
|
||||
MediaList: MediaList;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MediaList {
|
||||
id: number
|
||||
mediaId: number
|
||||
userId: number
|
||||
id: number;
|
||||
mediaId: number;
|
||||
userId: number;
|
||||
media: {
|
||||
id: number
|
||||
idMal: number
|
||||
id: number;
|
||||
idMal: number;
|
||||
title: {
|
||||
romaji: string
|
||||
english?: string
|
||||
native: string
|
||||
}
|
||||
description: string
|
||||
romaji: string;
|
||||
english?: string;
|
||||
native: string;
|
||||
};
|
||||
description: string;
|
||||
coverImage: {
|
||||
large: string
|
||||
}
|
||||
season: string
|
||||
seasonYear: number
|
||||
status: string
|
||||
episodes?: number
|
||||
large: string;
|
||||
};
|
||||
season: string;
|
||||
seasonYear: number;
|
||||
status: string;
|
||||
episodes?: number;
|
||||
nextAiringEpisode?: {
|
||||
airingAt: number
|
||||
timeUntilAiring: number
|
||||
episode: number
|
||||
}
|
||||
}
|
||||
status: string
|
||||
airingAt: number;
|
||||
timeUntilAiring: number;
|
||||
episode: number;
|
||||
};
|
||||
genres: string[];
|
||||
tags: [
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
rank: number;
|
||||
isMediaSpoiler: boolean;
|
||||
isAdult: boolean;
|
||||
},
|
||||
];
|
||||
isAdult: boolean;
|
||||
};
|
||||
status: string;
|
||||
startedAt: {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
}
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
};
|
||||
completedAt: {
|
||||
year?: number
|
||||
month?: number
|
||||
day?: number
|
||||
}
|
||||
notes?: string
|
||||
progress: number
|
||||
score: number
|
||||
repeat: number
|
||||
year?: number;
|
||||
month?: number;
|
||||
day?: number;
|
||||
};
|
||||
notes?: string;
|
||||
progress: number;
|
||||
score: number;
|
||||
repeat: number;
|
||||
user: {
|
||||
id: number
|
||||
name: string
|
||||
id: number;
|
||||
name: string;
|
||||
avatar: {
|
||||
large: string
|
||||
medium: string
|
||||
}
|
||||
large: string;
|
||||
medium: string;
|
||||
};
|
||||
statistics: {
|
||||
anime: {
|
||||
count: number
|
||||
statuses: [{
|
||||
status: string
|
||||
count: number
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
count: number;
|
||||
statuses: [
|
||||
{
|
||||
status: string;
|
||||
count: number;
|
||||
},
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -6,12 +6,16 @@
|
||||
malLoggedIn,
|
||||
simklAnime,
|
||||
simklLoggedIn,
|
||||
watchlistNeedsRefresh,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import { push } from "svelte-spa-router";
|
||||
import { Button } from "flowbite-svelte";
|
||||
import WebsiteLink from "./WebsiteLink.svelte";
|
||||
import type { AniListGetSingleAnime } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||
import Rating from "./Rating.svelte";
|
||||
import convertAniListDateToString from "../helperFunctions/convertAniListDateToString";
|
||||
import {
|
||||
convertAniListDateToString,
|
||||
convertAniListDateToDate,
|
||||
} from "../helperFunctions/convertAniListDateIn";
|
||||
import AnimeTable from "./AnimeTable.svelte";
|
||||
import type {
|
||||
MALAnime,
|
||||
@@ -20,12 +24,9 @@
|
||||
} from "../mal/types/MALTypes";
|
||||
import type { SimklAnime } from "../simkl/types/simklTypes";
|
||||
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 convertDateStringToAniList from "../helperFunctions/convertDateStringToAniList";
|
||||
import { convertDateToAniList } from "../helperFunctions/convertDateToAniList";
|
||||
import {
|
||||
AniListDeleteEntry,
|
||||
AniListUpdateEntry,
|
||||
@@ -39,6 +40,8 @@
|
||||
import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte";
|
||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||
import Datepicker from "./Datepicker.svelte";
|
||||
import { Badge, Tooltip } from "flowbite-svelte";
|
||||
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
||||
|
||||
let isAniListLoggedIn: boolean;
|
||||
let isMalLoggedIn: boolean;
|
||||
@@ -76,13 +79,12 @@
|
||||
{ id: 5, aniList: "REPEATING", mal: "rewatching", simkl: "watching" },
|
||||
];
|
||||
let startingAnilistStatusOption: StatusOption = statusOptions.filter(
|
||||
(option) =>
|
||||
currentAniListAnime.data.MediaList.status === option.aniList,
|
||||
(option) => currentAniListAnime.data.MediaList.status === option.aniList,
|
||||
)[0];
|
||||
const startedAtDate = convertAniListDateToString(
|
||||
let startedAtDate: Date | null = convertAniListDateToDate(
|
||||
currentAniListAnime.data.MediaList.startedAt,
|
||||
);
|
||||
const completedAtDate = convertAniListDateToString(
|
||||
let completedAtDate: Date | null = convertAniListDateToDate(
|
||||
currentAniListAnime.data.MediaList.completedAt,
|
||||
);
|
||||
|
||||
@@ -104,19 +106,30 @@
|
||||
notes: currentAniListAnime.data.MediaList.notes,
|
||||
});
|
||||
|
||||
if (isMalLoggedIn)
|
||||
if (isMalLoggedIn) {
|
||||
let startDate = "";
|
||||
let finishDate = "";
|
||||
if (currentMalAnime.my_list_status.start_date !== "") {
|
||||
const startArray = re.exec(currentMalAnime.my_list_status.start_date);
|
||||
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
||||
}
|
||||
if (currentMalAnime.my_list_status.finish_date !== "") {
|
||||
const finishArray = re.exec(currentMalAnime.my_list_status.finish_date);
|
||||
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
||||
}
|
||||
AddAnimeServiceToTable({
|
||||
id: `m-${currentMalAnime.id}`,
|
||||
title: currentMalAnime.title,
|
||||
service: "MyAnimeList",
|
||||
progress: currentMalAnime.my_list_status.num_episodes_watched,
|
||||
status: currentMalAnime.my_list_status.status,
|
||||
startedAt: currentMalAnime.my_list_status.start_date,
|
||||
completedAt: currentMalAnime.my_list_status.finish_date,
|
||||
startedAt: startDate,
|
||||
completedAt: finishDate,
|
||||
score: currentMalAnime.my_list_status.score,
|
||||
repeat: currentMalAnime.my_list_status.num_times_rewatched,
|
||||
notes: currentMalAnime.my_list_status.comments,
|
||||
});
|
||||
}
|
||||
|
||||
if (isSimklLoggedIn && Object.keys(currentSimklAnime).length > 0)
|
||||
AddAnimeServiceToTable({
|
||||
@@ -138,8 +151,8 @@
|
||||
rating: number;
|
||||
episodes: number;
|
||||
status: StatusOption;
|
||||
startedAt: string;
|
||||
completedAt: string;
|
||||
startedAt: Date | null;
|
||||
completedAt: Date | null;
|
||||
repeat: number;
|
||||
notes: string;
|
||||
} = {
|
||||
@@ -151,8 +164,8 @@
|
||||
mal: "",
|
||||
simkl: "",
|
||||
},
|
||||
startedAt: "",
|
||||
completedAt: "",
|
||||
startedAt: null,
|
||||
completedAt: null,
|
||||
repeat: 0,
|
||||
notes: "",
|
||||
};
|
||||
@@ -178,6 +191,7 @@
|
||||
submitData[key] = value;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
isAniListLoggedIn &&
|
||||
currentAniListAnime.data.MediaList.mediaId !== 0
|
||||
@@ -189,14 +203,14 @@
|
||||
score: submitData.rating,
|
||||
repeat: submitData.repeat,
|
||||
notes: submitData.notes,
|
||||
startedAt: convertDateStringToAniList(submitData.startedAt),
|
||||
completedAt: convertDateStringToAniList(submitData.completedAt),
|
||||
startedAt: convertDateToAniList(startedAtDate),
|
||||
completedAt: convertDateToAniList(completedAtDate),
|
||||
};
|
||||
await AniListUpdateEntry(body).then(
|
||||
(value: AniListGetSingleAnime) => {
|
||||
/* TODO in future when you inevitably add tags to typescript, until Anilist fixes the api bug
|
||||
where tags break the SaveMediaListEntry return, you'll want to use this delete line
|
||||
delete value.data.MediaList.media.tags */
|
||||
await AniListUpdateEntry(body).then((value: AniListGetSingleAnime) => {
|
||||
value.data.MediaList.media.tags =
|
||||
currentAniListAnime.data.MediaList.media.tags;
|
||||
value.data.MediaList.media.genres =
|
||||
currentAniListAnime.data.MediaList.media.genres;
|
||||
aniListAnime.update((newValue) => {
|
||||
newValue = value;
|
||||
return newValue;
|
||||
@@ -217,8 +231,7 @@
|
||||
repeat: currentAniListAnime.data.MediaList.repeat,
|
||||
notes: currentAniListAnime.data.MediaList.notes,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (malLoggedIn && currentMalAnime.id !== 0) {
|
||||
@@ -235,8 +248,7 @@
|
||||
(malAnimeReturn: MalListStatus) => {
|
||||
malAnime.update((value) => {
|
||||
value.my_list_status.status = malAnimeReturn.status;
|
||||
value.my_list_status.is_rewatching =
|
||||
malAnimeReturn.is_rewatching;
|
||||
value.my_list_status.is_rewatching = malAnimeReturn.is_rewatching;
|
||||
value.my_list_status.score = malAnimeReturn.score;
|
||||
value.my_list_status.num_episodes_watched =
|
||||
malAnimeReturn.num_episodes_watched;
|
||||
@@ -245,18 +257,30 @@
|
||||
value.my_list_status.comments = malAnimeReturn.comments;
|
||||
return value;
|
||||
});
|
||||
let startDate = "";
|
||||
let finishDate = "";
|
||||
if (currentMalAnime.my_list_status.start_date !== "") {
|
||||
const startArray = re.exec(
|
||||
currentMalAnime.my_list_status.start_date,
|
||||
);
|
||||
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
||||
}
|
||||
if (currentMalAnime.my_list_status.finish_date !== "") {
|
||||
const finishArray = re.exec(
|
||||
currentMalAnime.my_list_status.finish_date,
|
||||
);
|
||||
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
||||
}
|
||||
AddAnimeServiceToTable({
|
||||
id: `m-${currentMalAnime.id}`,
|
||||
title: currentMalAnime.title,
|
||||
service: "MyAnimeList",
|
||||
progress:
|
||||
currentMalAnime.my_list_status.num_episodes_watched,
|
||||
progress: currentMalAnime.my_list_status.num_episodes_watched,
|
||||
status: currentMalAnime.my_list_status.status,
|
||||
startedAt: currentMalAnime.my_list_status.start_date,
|
||||
completedAt: currentMalAnime.my_list_status.finish_date,
|
||||
startedAt: startDate,
|
||||
completedAt: finishDate,
|
||||
score: currentMalAnime.my_list_status.score,
|
||||
repeat: currentMalAnime.my_list_status
|
||||
.num_times_rewatched,
|
||||
repeat: currentMalAnime.my_list_status.num_times_rewatched,
|
||||
notes: currentMalAnime.my_list_status.comments,
|
||||
});
|
||||
},
|
||||
@@ -264,13 +288,9 @@
|
||||
}
|
||||
|
||||
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
|
||||
if (
|
||||
currentSimklAnime.watched_episodes_count !== submitData.episodes
|
||||
) {
|
||||
await SimklSyncEpisodes(
|
||||
currentSimklAnime,
|
||||
submitData.episodes,
|
||||
).then((value: SimklAnime) => {
|
||||
if (currentSimklAnime.watched_episodes_count !== submitData.episodes) {
|
||||
await SimklSyncEpisodes(currentSimklAnime, submitData.episodes).then(
|
||||
(value: SimklAnime) => {
|
||||
AddAnimeServiceToTable({
|
||||
id: `s-${value.show.ids.simkl}`,
|
||||
title: value.show.title,
|
||||
@@ -287,14 +307,13 @@
|
||||
newValue = value;
|
||||
return newValue;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (currentSimklAnime.user_rating !== submitData.rating) {
|
||||
await SimklSyncRating(
|
||||
currentSimklAnime,
|
||||
submitData.rating,
|
||||
).then((value) => {
|
||||
await SimklSyncRating(currentSimklAnime, submitData.rating).then(
|
||||
(value) => {
|
||||
AddAnimeServiceToTable({
|
||||
id: `s-${value.show.ids.simkl}`,
|
||||
title: value.show.title,
|
||||
@@ -311,7 +330,8 @@
|
||||
newValue = value;
|
||||
return newValue;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (currentSimklAnime.status !== submitData.status.simkl) {
|
||||
@@ -338,14 +358,19 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error submitting changes:", error);
|
||||
} finally {
|
||||
submitting.set(false);
|
||||
submitSuccess.set(true);
|
||||
watchlistNeedsRefresh.set(true);
|
||||
setTimeout(() => submitSuccess.set(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteEntries = async () => {
|
||||
submitting.set(true);
|
||||
try {
|
||||
if (
|
||||
isAniListLoggedIn &&
|
||||
currentAniListAnime.data.MediaList.mediaId !== 0
|
||||
@@ -394,9 +419,14 @@
|
||||
notes: "",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting entries:", error);
|
||||
} finally {
|
||||
submitting.set(false);
|
||||
submitSuccess.set(true);
|
||||
watchlistNeedsRefresh.set(true);
|
||||
setTimeout(() => submitSuccess.set(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
let max = 999;
|
||||
@@ -409,12 +439,10 @@
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0
|
||||
) {
|
||||
max =
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode -
|
||||
1;
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode - 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
/B
|
||||
<form on:submit|preventDefault={handleSubmit} class="container pt-3 pb-10">
|
||||
<h1 class="text-white font-bold text-left text-xl pb-3">
|
||||
{title}
|
||||
@@ -444,12 +472,24 @@
|
||||
type="button"
|
||||
id="decrement-button"
|
||||
data-input-counter-decrement="quantity-input"
|
||||
on:click={() =>
|
||||
(currentAniListAnime.data.MediaList.progress -= 1)}
|
||||
class="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-s-lg p-3 h-11 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
on:click={() => {
|
||||
currentAniListAnime.data.MediaList.progress -= 1;
|
||||
if (
|
||||
currentAniListAnime.data.MediaList.progress <
|
||||
currentAniListAnime.data.MediaList.media.episodes
|
||||
) {
|
||||
startingAnilistStatusOption = statusOptions[0];
|
||||
if (currentAniListAnime.data.MediaList.repeat === 0)
|
||||
completedAtDate = null;
|
||||
}
|
||||
}}
|
||||
disabled={currentAniListAnime.data.MediaList.progress <= 0}
|
||||
class={currentAniListAnime.data.MediaList.progress <= 0
|
||||
? "border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-gray-900 dark:text-white"
|
||||
class="w-3 h-3 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -472,34 +512,62 @@
|
||||
id="episodes"
|
||||
class="border border-x-0 p-2.5 h-11 text-center text-sm block w-full placeholder-gray-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
|
||||
{currentAniListAnime.data.MediaList.progress < 0 ||
|
||||
(currentAniListAnime.data.MediaList.media.episodes >
|
||||
0 &&
|
||||
(currentAniListAnime.data.MediaList.media.episodes > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >
|
||||
currentAniListAnime.data.MediaList.media
|
||||
.episodes) ||
|
||||
(currentAniListAnime.data.MediaList.media
|
||||
.nextAiringEpisode.episode > 0 &&
|
||||
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >
|
||||
currentAniListAnime.data.MediaList.media
|
||||
.nextAiringEpisode.episode -
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode -
|
||||
1)
|
||||
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'bg-gray-700 hover:bg-gray-600 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500'} w-24"
|
||||
bind:value={
|
||||
currentAniListAnime.data.MediaList.progress
|
||||
}
|
||||
bind:value={currentAniListAnime.data.MediaList.progress}
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="increment-button"
|
||||
data-input-counter-increment="quantity-input"
|
||||
on:click={() =>
|
||||
(currentAniListAnime.data.MediaList.progress += 1)}
|
||||
class="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-e-lg p-3 h-11 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
on:click={() => {
|
||||
currentAniListAnime.data.MediaList.progress += 1;
|
||||
if (
|
||||
currentAniListAnime.data.MediaList.media.episodes ===
|
||||
currentAniListAnime.data.MediaList.progress
|
||||
) {
|
||||
startingAnilistStatusOption = statusOptions[2];
|
||||
completedAtDate = new Date();
|
||||
}
|
||||
if (currentAniListAnime.data.MediaList.progress - 1 === 0) {
|
||||
startingAnilistStatusOption = statusOptions[0];
|
||||
if (startedAtDate === null) startedAtDate = new Date();
|
||||
}
|
||||
}}
|
||||
disabled={(currentAniListAnime.data.MediaList.media.episodes >
|
||||
0 &&
|
||||
currentAniListAnime.data.MediaList.progress >=
|
||||
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode -
|
||||
2)}
|
||||
class={(currentAniListAnime.data.MediaList.media.episodes > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >=
|
||||
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode -
|
||||
2)
|
||||
? "border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-gray-900 dark:text-white"
|
||||
class="w-3 h-3 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -516,24 +584,22 @@
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
/ {currentAniListAnime.data.MediaList.media
|
||||
.nextAiringEpisode.episode !== 0
|
||||
? currentAniListAnime.data.MediaList.media
|
||||
.nextAiringEpisode.episode - 1
|
||||
/ {currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode !== 0
|
||||
? currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode - 1
|
||||
: currentAniListAnime.data.MediaList.media.episodes}
|
||||
</div>
|
||||
{#if currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0}
|
||||
<div>
|
||||
of {currentAniListAnime.data.MediaList.media
|
||||
.episodes}
|
||||
of {currentAniListAnime.data.MediaList.media.episodes}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="status"
|
||||
class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||
class="text-left block mb-2 text-sm font-medium text-white"
|
||||
>Status</label
|
||||
>
|
||||
<select
|
||||
@@ -559,34 +625,16 @@
|
||||
class="text-left block mb-2 text-sm font-medium text-white"
|
||||
>Date Started</label
|
||||
>
|
||||
<div class="relative max-w-sm">
|
||||
<div
|
||||
class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-400"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"
|
||||
<Datepicker
|
||||
bind:value={startedAtDate}
|
||||
color="slate"
|
||||
dateFormat={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
}}
|
||||
showActionButtons
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="startedAt"
|
||||
type="date"
|
||||
name="startedAt"
|
||||
class="border text-sm rounded-lg
|
||||
focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 bg-gray-700 border-gray-600
|
||||
placeholder-gray-400 text-white"
|
||||
value={startedAtDate}
|
||||
placeholder="Date Started"
|
||||
/>
|
||||
<Datepicker showActionButtons />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
@@ -594,33 +642,16 @@
|
||||
class="text-left block mb-2 text-sm font-medium text-white"
|
||||
>Date Completed</label
|
||||
>
|
||||
<div class="relative max-w-sm">
|
||||
<div
|
||||
class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-400"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"
|
||||
<Datepicker
|
||||
bind:value={completedAtDate}
|
||||
color="slate"
|
||||
dateFormat={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
}}
|
||||
showActionButtons
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="completedAt"
|
||||
type="date"
|
||||
name="completedAt"
|
||||
class="border text-sm rounded-lg
|
||||
block w-full ps-10 p-2.5 bg-gray-700 border-gray-600
|
||||
placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
|
||||
value={completedAtDate}
|
||||
placeholder="Date Completed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
@@ -633,8 +664,7 @@
|
||||
name="repeat"
|
||||
min="0"
|
||||
id="repeat"
|
||||
class="border {currentAniListAnime.data.MediaList
|
||||
.repeat < 0
|
||||
class="border {currentAniListAnime.data.MediaList.repeat < 0
|
||||
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-500 text-white focus:ring-blue-500 focus:border-blue-500'} text-sm rounded-lg block w-24 p-2.5 bg-gray-600 placeholder-gray-400 text-white"
|
||||
bind:value={currentAniListAnime.data.MediaList.repeat}
|
||||
@@ -663,63 +693,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="external-data">
|
||||
<div
|
||||
id="anilist-data"
|
||||
class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-16 lg:gap-x-36 group"
|
||||
>
|
||||
<h2 class="text-left mb-1 text-base font-semibold text-white">
|
||||
AniList
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimeTable />
|
||||
|
||||
<div class="flex rounded-lg shadow max-w-4-4 bg-gray-800">
|
||||
<div
|
||||
class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-start"
|
||||
>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
id="delete-button"
|
||||
class="text-white bg-red-700 {$submitSuccess
|
||||
? 'bg-green-600 dark:bg-green-600 hover:bg-green-700 dark:hover:bg-green-700 focus:ring-4 focus:ring-green-800 dark:focus:ring-green-800'
|
||||
: 'bg-red-600 dark:bg-red-600 hover:bg-red-700 dark:hover:bg-red-700 focus:ring-4 focus:ring-red-800 dark:focus:ring-red-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
on:click={deleteEntries}
|
||||
>
|
||||
<svg
|
||||
id="submit-loader"
|
||||
aria-hidden="true"
|
||||
role="status"
|
||||
class="{isSubmitting
|
||||
? 'inline'
|
||||
: 'hidden'} w-4 h-4 me-3 text-white animate-spin"
|
||||
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="#E5E7EB"
|
||||
/>
|
||||
<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="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
Delete Entries
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex mb-4 rounded-lg shadow max-w-4-4 bg-gray-800">
|
||||
<div
|
||||
class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end"
|
||||
>
|
||||
<Button
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
id="sync-button"
|
||||
class="text-white {$submitSuccess
|
||||
? 'bg-green-600 dark:bg-green-600 hover:bg-green-700 dark:hover:bg-green-700 focus:ring-4 focus:ring-green-800 dark:focus:ring-green-800'
|
||||
: 'bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:ring-4 focus:ring-blue-800 dark:focus:ring-blue-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-4 focus:ring-green-800'
|
||||
: 'bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
<svg
|
||||
@@ -743,23 +726,145 @@
|
||||
/>
|
||||
</svg>
|
||||
Sync Changes
|
||||
</Button>
|
||||
<Button
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-white bg-gray-800 border border-gray-600 focus:outline-none hover:bg-gray-700 focus:ring-4
|
||||
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:text-white
|
||||
dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2
|
||||
hover:border-gray-600"
|
||||
on:click={async () => {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
return push("/");
|
||||
}}
|
||||
>
|
||||
Go Home
|
||||
</Button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<AnimeTable />
|
||||
|
||||
<div class="flex rounded-lg shadow max-w-4-4 bg-gray-800">
|
||||
<div
|
||||
class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-start"
|
||||
>
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
id="delete-button"
|
||||
class="text-white {$submitSuccess
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-4 focus:ring-green-800'
|
||||
: 'bg-red-600 hover:bg-red-700 focus:ring-4 focus:ring-red-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
on:click={deleteEntries}
|
||||
>
|
||||
<svg
|
||||
id="submit-loader"
|
||||
aria-hidden="true"
|
||||
role="status"
|
||||
class="{isSubmitting
|
||||
? 'inline'
|
||||
: 'hidden'} w-4 h-4 me-3 text-white animate-spin"
|
||||
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="#E5E7EB"
|
||||
/>
|
||||
<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="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
Delete Entries
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end"
|
||||
>
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
id="sync-button"
|
||||
class="text-white {$submitSuccess
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-4 focus:ring-green-800'
|
||||
: 'bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
<svg
|
||||
id="submit-loader"
|
||||
aria-hidden="true"
|
||||
role="status"
|
||||
class="{isSubmitting
|
||||
? 'inline'
|
||||
: 'hidden'} w-4 h-4 me-3 text-white animate-spin"
|
||||
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="#E5E7EB"
|
||||
/>
|
||||
<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="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
Sync Changes
|
||||
</button>
|
||||
<button
|
||||
class="text-white bg-gray-800 border border-gray-600 focus:outline-none hover:bg-gray-700 focus:ring-4
|
||||
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2
|
||||
hover:border-gray-600"
|
||||
on:click={async () => {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
return push("/");
|
||||
}}
|
||||
>
|
||||
Go Home
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex m-5">
|
||||
<div>
|
||||
<h3 class="text-2xl">Genres</h3>
|
||||
{#each currentAniListAnime.data.MediaList.media.genres || [] as genre}
|
||||
<div>
|
||||
<Badge large border color="blue" class="m-1 w-52">
|
||||
<div>
|
||||
<WebsiteLink
|
||||
id={genre}
|
||||
url="https://anilist.co/search/anime/{genre}"
|
||||
/>
|
||||
</div>
|
||||
</Badge>
|
||||
<Tooltip>{genre}</Tooltip>
|
||||
</div>
|
||||
{/each}
|
||||
<h3 class="text-2xl">Tags</h3>
|
||||
<div class="mt-2">
|
||||
{#each currentAniListAnime.data.MediaList.media.tags as tag}
|
||||
<div>
|
||||
<Badge large border color="blue" class="m-1 w-52">
|
||||
<div>
|
||||
<WebsiteLink
|
||||
id={tag.name}
|
||||
url="https://anilist.co/search/anime/?genres={tag.name}"
|
||||
/>
|
||||
<span class="text-xs">({tag.rank}%)</span>
|
||||
</div>
|
||||
</Badge>
|
||||
<Tooltip>{tag.description}</Tooltip>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-5">
|
||||
<h3 class="text-2xl">Summary</h3>
|
||||
<p>{@html currentAniListAnime.data.MediaList.media.description}</p>
|
||||
<p class="rounded border border-gray-700 p-2 mt-2">
|
||||
{@html currentAniListAnime.data.MediaList.media.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
createTable,
|
||||
Render,
|
||||
Subscribe,
|
||||
} from "svelte-headless-table"
|
||||
} from "svelte-headless-table";
|
||||
// @ts-ignore
|
||||
import { addSortBy } from "svelte-headless-table/plugins"
|
||||
import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"
|
||||
import WebsiteLink from "./WebsiteLink.svelte"
|
||||
import { addSortBy } from "svelte-headless-table/plugins";
|
||||
import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import WebsiteLink from "./WebsiteLink.svelte";
|
||||
|
||||
//when adding sort here is code { sort: addSortBy() }
|
||||
const table = createTable(tableItems, { sort: addSortBy() })
|
||||
const table = createTable(tableItems, { sort: addSortBy() });
|
||||
|
||||
const columns = table.createColumns([
|
||||
table.column({
|
||||
@@ -55,11 +55,11 @@
|
||||
header: "Notes",
|
||||
accessor: "notes",
|
||||
}),
|
||||
])
|
||||
]);
|
||||
|
||||
//add pluginStates when add sort back
|
||||
const { headerRows, rows, tableAttrs, tableBodyAttrs } =
|
||||
table.createViewModel(columns)
|
||||
table.createViewModel(columns);
|
||||
</script>
|
||||
|
||||
<div class="relative overflow-x-auto rounded-lg mb-5">
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import * as runtime from "../../wailsjs/runtime";
|
||||
import type { MyAnimeListUser } from "../mal/types/MALTypes";
|
||||
import type { SimklUser } from "../simkl/types/simklTypes";
|
||||
import { ShowVersion } from "../../wailsjs/go/main/App";
|
||||
|
||||
let currentAniListUser: AniListUser;
|
||||
let currentMALUser: MyAnimeListUser;
|
||||
@@ -27,8 +28,8 @@
|
||||
let isMALLoggedIn: boolean;
|
||||
|
||||
aniListUser.subscribe((value) => (currentAniListUser = value));
|
||||
malUser.subscribe((value) => (currentMALUser = value))
|
||||
simklUser.subscribe(value => currentSimklUser = value)
|
||||
malUser.subscribe((value) => (currentMALUser = value));
|
||||
simklUser.subscribe((value) => (currentSimklUser = value));
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
|
||||
@@ -36,6 +37,23 @@
|
||||
function dropdownUser(): void {
|
||||
let dropdown = document.querySelector("#userDropdown");
|
||||
dropdown.classList.toggle("hidden");
|
||||
|
||||
if (!dropdown.classList.contains("hidden")) {
|
||||
document.addEventListener("click", clickOutside);
|
||||
}
|
||||
}
|
||||
|
||||
function clickOutside(event: Event): void {
|
||||
let dropdown = document.querySelector("#userDropdown");
|
||||
let toggleBtn = document.querySelector("#userDropdownButton");
|
||||
|
||||
if (
|
||||
!dropdown.contains(event.target as Node) &&
|
||||
!toggleBtn.contains(event.target as Node)
|
||||
) {
|
||||
dropdown.classList.add("hidden");
|
||||
document.removeEventListener("click", clickOutside);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -45,7 +63,9 @@
|
||||
<Avatar
|
||||
src={currentAniListUser.data.Viewer.avatar.medium}
|
||||
class="cursor-pointer"
|
||||
dot={{ color: "green" }}
|
||||
dot={isAniListLoggedIn && isMALLoggedIn && isSimklLoggedIn
|
||||
? { color: "green" }
|
||||
: { color: "yellow" }}
|
||||
/>
|
||||
{:else}
|
||||
<Avatar class="cursor-pointer" dot={{ color: "red" }} />
|
||||
@@ -72,13 +92,19 @@
|
||||
on:click={logoutOfAniList}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-green-800 hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser.data.Viewer.name}
|
||||
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser
|
||||
.data.Viewer.name}
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={loginToAniList}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white">
|
||||
<button
|
||||
on:click={() => {
|
||||
dropdownUser();
|
||||
loginToAniList();
|
||||
}}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg mr-4">A</span>Login to AniList
|
||||
</button>
|
||||
</li>
|
||||
@@ -94,8 +120,13 @@
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={loginToMAL}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white">
|
||||
<button
|
||||
on:click={() => {
|
||||
dropdownUser();
|
||||
loginToMAL();
|
||||
}}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg mr-4">M</span>Login to MyAnimeList
|
||||
</button>
|
||||
</li>
|
||||
@@ -106,19 +137,34 @@
|
||||
on:click={logoutOfSimkl}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-indigo-800 hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg text-indigo-200 mr-4">S</span>Logout {currentSimklUser.user.name}
|
||||
<span class="maple-font text-lg text-indigo-200 mr-4">S</span>Logout {currentSimklUser
|
||||
.user.name}
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={loginToSimkl}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white">
|
||||
<button
|
||||
on:click={() => {
|
||||
dropdownUser();
|
||||
loginToSimkl();
|
||||
}}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg mr-4">S</span>Login to Simkl
|
||||
</button>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
<div class="py-2">
|
||||
<button
|
||||
on:click={() => {
|
||||
dropdownUser();
|
||||
ShowVersion();
|
||||
}}
|
||||
class="block px-4 py-2 w-full text-sm hover:bg-gray-600 text-gray-200 over:text-white"
|
||||
>
|
||||
Version
|
||||
</button>
|
||||
<button
|
||||
on:click={() => runtime.Quit()}
|
||||
class="block px-4 py-2 w-full text-sm hover:bg-gray-600 text-gray-200 over:text-white"
|
||||
@@ -128,3 +174,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -50,17 +50,19 @@
|
||||
function getFocusRingClass(color: Button["color"]): string {
|
||||
switch (color) {
|
||||
case "primary":
|
||||
return "focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400";
|
||||
return "focus:ring-2 focus:ring-primary-400";
|
||||
case "blue":
|
||||
return "focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400";
|
||||
return "focus:ring-2 focus:ring-blue-400";
|
||||
case "red":
|
||||
return "focus:ring-2 focus:ring-red-500 dark:focus:ring-red-400";
|
||||
return "focus:ring-2 focus:ring-red-400";
|
||||
case "green":
|
||||
return "focus:ring-2 focus:ring-green-500 dark:focus:ring-green-400";
|
||||
return "focus:ring-2 focus:ring-green-400";
|
||||
case "yellow":
|
||||
return "focus:ring-2 focus:ring-yellow-500 dark:focus:ring-yellow-400";
|
||||
return "focus:ring-2 focus:ring-yellow-400";
|
||||
case "purple":
|
||||
return "focus:ring-2 focus:ring-purple-500 dark:focus:ring-purple-400";
|
||||
return "focus:ring-2 focus:ring-purple-400";
|
||||
case "slate":
|
||||
return "focus:ring-2 focus:ring-slate-400";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -69,17 +71,19 @@
|
||||
function getRangeBackgroundClass(color: Button["color"]): string {
|
||||
switch (color) {
|
||||
case "primary":
|
||||
return "bg-primary-100 dark:bg-primary-900";
|
||||
return "bg-primary-900";
|
||||
case "blue":
|
||||
return "bg-blue-100 dark:bg-blue-900";
|
||||
return "bg-blue-900";
|
||||
case "red":
|
||||
return "bg-red-100 dark:bg-red-900";
|
||||
return "bg-red-900";
|
||||
case "green":
|
||||
return "bg-green-100 dark:bg-green-900";
|
||||
return "bg-green-900";
|
||||
case "yellow":
|
||||
return "bg-yellow-100 dark:bg-yellow-900";
|
||||
return "bg-yellow-900";
|
||||
case "purple":
|
||||
return "bg-purple-100 dark:bg-purple-900";
|
||||
return "bg-purple-900";
|
||||
case "slate":
|
||||
return "bg-slate-900";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -293,7 +297,7 @@
|
||||
<input
|
||||
bind:this={inputElement}
|
||||
type="text"
|
||||
class="w-full px-4 py-2 text-sm border rounded-md focus:outline-none dark:bg-gray-700 dark:text-white dark:border-gray-600 {getFocusRingClass(
|
||||
class="w-full px-4 py-3 text-sm border rounded-md focus:outline-none bg-gray-700 text-white border-gray-600 {getFocusRingClass(
|
||||
color,
|
||||
)} {inputClass}"
|
||||
{placeholder}
|
||||
@@ -309,13 +313,13 @@
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute inset-y-0 right-0 flex items-center px-3 text-gray-500 dark:text-gray-400 focus:outline-none"
|
||||
class="absolute inset-y-0 right-0 flex items-center px-3 text-gray-400 focus:outline-none"
|
||||
on:click={() => (isOpen = !isOpen)}
|
||||
{disabled}
|
||||
aria-label={isOpen ? "Close date picker" : "Open date picker"}
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-500 dark:text-gray-400"
|
||||
class="w-4 h-4 text-gray-400"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
@@ -335,14 +339,14 @@
|
||||
id="datepicker-dropdown"
|
||||
class="
|
||||
{inline ? '' : 'absolute z-10 mt-1'}
|
||||
bg-white dark:bg-gray-800 rounded-md shadow-lg"
|
||||
bg-gray-800 rounded-md shadow-lg"
|
||||
transition:fade={{ duration: 100 }}
|
||||
role="dialog"
|
||||
aria-label="Calendar"
|
||||
>
|
||||
<div class="p-4" role="application">
|
||||
{#if title}
|
||||
<h2 class="text-lg font-semibold mb-4 dark:text-white">
|
||||
<h2 class="text-lg font-semibold mb-4 text-white">
|
||||
{title}
|
||||
</h2>
|
||||
{/if}
|
||||
@@ -354,7 +358,7 @@
|
||||
aria-label="Previous month"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 rtl:rotate-180 text-white dark:text-white"
|
||||
class="w-3 h-3 rtl:rotate-180 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -369,7 +373,7 @@
|
||||
>
|
||||
</Button>
|
||||
<h3
|
||||
class="text-lg font-semibold dark:text-white"
|
||||
class="text-lg font-semibold text-white"
|
||||
aria-live="polite"
|
||||
>
|
||||
{currentMonth.toLocaleString(locale, {
|
||||
@@ -384,7 +388,7 @@
|
||||
aria-label="Next month"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 rtl:rotate-180 text-white dark:text-white"
|
||||
class="w-3 h-3 rtl:rotate-180 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -402,7 +406,7 @@
|
||||
<div class="grid grid-cols-7 gap-1" role="grid">
|
||||
{#each weekdays as day}
|
||||
<div
|
||||
class="text-center text-sm font-medium text-gray-500 dark:text-gray-400"
|
||||
class="text-center text-sm font-medium text-gray-400"
|
||||
role="columnheader"
|
||||
>
|
||||
{day}
|
||||
@@ -414,7 +418,7 @@
|
||||
size="sm"
|
||||
class="w-full h-8 {day.getMonth() !==
|
||||
currentMonth.getMonth()
|
||||
? 'text-gray-300 dark:text-gray-600'
|
||||
? 'text-gray-600'
|
||||
: ''} {isToday(day)
|
||||
? 'font-bold'
|
||||
: ''} {isInRange(day)
|
||||
|
||||
73
frontend/src/helperComponents/ErrorModal.svelte
Normal file
73
frontend/src/helperComponents/ErrorModal.svelte
Normal file
@@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
apiError,
|
||||
isApiDown,
|
||||
clearApiError,
|
||||
setApiError,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||
import { CheckIfMALLoggedInAndSetUser } from "../helperModules/CheckIfMyAnimeListLoggedIn.svelte";
|
||||
import { CheckIfSimklLoggedInAndSetUser } from "../helperModules/CheckIsSimklLoggedIn.svelte";
|
||||
import { Modal, Button } from "flowbite-svelte";
|
||||
let showModal = false;
|
||||
$: if ($apiError) {
|
||||
showModal = true;
|
||||
}
|
||||
async function handleRetry() {
|
||||
const service = $apiError?.service;
|
||||
clearApiError();
|
||||
|
||||
try {
|
||||
if (service === "anilist") {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
} else if (service === "mal") {
|
||||
await CheckIfMALLoggedInAndSetUser();
|
||||
} else if (service === "simkl") {
|
||||
await CheckIfSimklLoggedInAndSetUser();
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setApiError(
|
||||
service || "unknown",
|
||||
`Retry failed: ${errorMsg}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
function handleDismiss() {
|
||||
clearApiError();
|
||||
showModal = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if showModal && $apiError}
|
||||
<Modal
|
||||
open={showModal}
|
||||
title="{$apiError.service.toUpperCase()} API Error"
|
||||
size="md"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div class="p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p class="text-red-800 font-medium">{$apiError.message}</p>
|
||||
{#if $apiError.statusCode}
|
||||
<p class="text-red-600 text-sm mt-2">
|
||||
Status: {$apiError.statusCode}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-gray-600">
|
||||
The application will remain open. You can retry the connection or
|
||||
dismiss this message to continue with limited functionality.
|
||||
</p>
|
||||
</div>
|
||||
<div slot="footer" class="flex gap-3 justify-end">
|
||||
{#if $apiError.canRetry}
|
||||
<Button on:click={handleRetry} class="bg-blue-600 hover:bg-blue-700">
|
||||
Retry Connection
|
||||
</Button>
|
||||
{/if}
|
||||
<Button on:click={handleDismiss} color="alternative">Dismiss</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Search from "./Search.svelte"
|
||||
import Search from "./Search.svelte";
|
||||
import {
|
||||
aniListLoggedIn,
|
||||
loginToAniList,
|
||||
@@ -7,45 +7,71 @@
|
||||
loginToSimkl,
|
||||
malLoggedIn,
|
||||
simklLoggedIn,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import AvatarMenu from "./AvatarMenu.svelte";
|
||||
import logo from "../assets/images/AniTrackLogo.svg"
|
||||
import logo from "../assets/images/AniTrackLogo.svg";
|
||||
import { link } from "svelte-spa-router";
|
||||
|
||||
let isAniListLoggedIn: boolean
|
||||
let isSimklLoggedIn: boolean
|
||||
let isMALLoggedIn: boolean
|
||||
let isAniListLoggedIn: boolean;
|
||||
let isSimklLoggedIn: boolean;
|
||||
let isMALLoggedIn: boolean;
|
||||
|
||||
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
|
||||
simklLoggedIn.subscribe((value) => isSimklLoggedIn = value)
|
||||
malLoggedIn.subscribe((value) => isMALLoggedIn = value)
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
|
||||
</script>
|
||||
|
||||
<nav class="border-gray-200 bg-gray-900">
|
||||
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
||||
<div
|
||||
class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"
|
||||
>
|
||||
<div class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||
<a href="/"><img src={logo} class="h-8" alt="AniTrack Logo"/></a>
|
||||
<a href="/" use:link
|
||||
><img src={logo} class="h-8" alt="AniTrack Logo" /></a
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center min-[950px]:order-2 space-x-3 min-[950px]:space-x-0 rtl:space-x-reverse">
|
||||
<div
|
||||
class="flex items-center min-[950px]:order-2 space-x-3 min-[950px]:space-x-0 rtl:space-x-reverse"
|
||||
>
|
||||
<div class="min-[950px]:block min-[950px]:mr-4">
|
||||
<Search />
|
||||
</div>
|
||||
<AvatarMenu />
|
||||
<button on:click={() => {
|
||||
let menu = document.querySelector("#navbar-user")
|
||||
menu.classList.toggle("hidden")
|
||||
}} type="button"
|
||||
<button
|
||||
on:click={() => {
|
||||
let menu = document.querySelector("#navbar-user");
|
||||
menu.classList.toggle("hidden");
|
||||
}}
|
||||
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"
|
||||
aria-controls="navbar-user" aria-expanded="false">
|
||||
aria-controls="navbar-user"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 17 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M1 1h15M1 7h15M1 13h15"/>
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 17 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M1 1h15M1 7h15M1 13h15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</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" id="navbar-user">
|
||||
<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">
|
||||
<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"
|
||||
id="navbar-user"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<li>
|
||||
{#if !isAniListLoggedIn}
|
||||
<button on:click={loginToAniList}>
|
||||
|
||||
@@ -1,46 +1,57 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
aniListLoggedIn,
|
||||
aniListSort,
|
||||
aniListWatchlist,
|
||||
animePerPage,
|
||||
watchListPage,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
|
||||
import type {AniListCurrentUserWatchList} from "../anilist/types/AniListCurrentUserWatchListType"
|
||||
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||
import { GetAniListUserWatchingList } from "../../wailsjs/go/main/App";
|
||||
import {MediaListSort} from "../anilist/types/AniListTypes";
|
||||
|
||||
let aniListWatchListLoaded: AniListCurrentUserWatchList
|
||||
let page: number
|
||||
let perPage: number
|
||||
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
||||
let page: number;
|
||||
let perPage: number;
|
||||
let sort: string;
|
||||
|
||||
watchListPage.subscribe(value => page = value)
|
||||
animePerPage.subscribe(value => perPage = value)
|
||||
aniListWatchlist.subscribe((value) => aniListWatchListLoaded = value)
|
||||
watchListPage.subscribe((value) => (page = value));
|
||||
animePerPage.subscribe((value) => (perPage = value));
|
||||
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = value));
|
||||
aniListSort.subscribe((value) => (sort = value));
|
||||
|
||||
const perPageOptions = [10, 20, 50]
|
||||
const perPageOptions = [10, 20, 50];
|
||||
|
||||
function ChangeWatchListPage(newPage: number) {
|
||||
GetAniListUserWatchingList(newPage, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
|
||||
watchListPage.set(newPage)
|
||||
aniListWatchlist.set(result)
|
||||
aniListLoggedIn.set(true)
|
||||
})
|
||||
GetAniListUserWatchingList(newPage, perPage, sort).then((result) => {
|
||||
watchListPage.set(newPage);
|
||||
aniListWatchlist.set(result);
|
||||
aniListLoggedIn.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
function changePage(e): void {
|
||||
if ((e.key === "Enter" || e.key === "Tab") && Number(e.target.value) !== page) ChangeWatchListPage(Number(e.target.value))
|
||||
function changePage(
|
||||
e: KeyboardEvent & { currentTarget: HTMLInputElement },
|
||||
): void {
|
||||
if (
|
||||
(e.key === "Enter" || e.key === "Tab") &&
|
||||
Number(e.currentTarget.value) !== page
|
||||
)
|
||||
ChangeWatchListPage(Number(e.currentTarget.value));
|
||||
}
|
||||
|
||||
function changeCountPerPage(e): void {
|
||||
GetAniListUserWatchingList(1, Number(e.target.value), MediaListSort.UpdatedTimeDesc).then((result) => {
|
||||
animePerPage.set(Number(e.target.value))
|
||||
watchListPage.set(1)
|
||||
aniListWatchlist.set(result)
|
||||
aniListLoggedIn.set(true)
|
||||
})
|
||||
function changeCountPerPage(
|
||||
e: Event & { currentTarget: HTMLSelectElement },
|
||||
): void {
|
||||
GetAniListUserWatchingList(1, Number(e.currentTarget.value), sort).then(
|
||||
(result) => {
|
||||
animePerPage.set(Number(e.currentTarget.value));
|
||||
watchListPage.set(1);
|
||||
aniListWatchlist.set(result);
|
||||
aniListLoggedIn.set(true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="mb-8">
|
||||
@@ -49,15 +60,19 @@
|
||||
<ul class="inline-flex -space-x-px text-base h-10">
|
||||
{#if page === 1}
|
||||
<li>
|
||||
<button disabled
|
||||
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 cursor-default">
|
||||
<button
|
||||
disabled
|
||||
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 cursor-default"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => ChangeWatchListPage(page-1)}
|
||||
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">
|
||||
<button
|
||||
on:click={() => ChangeWatchListPage(page - 1)}
|
||||
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
</li>
|
||||
@@ -65,27 +80,37 @@
|
||||
{#each { length: aniListWatchListLoaded.data.Page.pageInfo.lastPage } as _, i}
|
||||
{#if i + 1 === page}
|
||||
<li>
|
||||
<button on:click={() => ChangeWatchListPage(i+1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border bg-gray-100 border-gray-700 bg-gray-700 text-white">{i + 1}</button>
|
||||
<button
|
||||
on:click={() => ChangeWatchListPage(i + 1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border hover:bg-gray-100 border-gray-700 bg-gray-700 text-white"
|
||||
>{i + 1}</button
|
||||
>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => ChangeWatchListPage(i+1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">{i + 1}</button>
|
||||
<button
|
||||
on:click={() => ChangeWatchListPage(i + 1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
|
||||
>{i + 1}</button
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
<li>
|
||||
<button disabled
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 cursor-default">
|
||||
<button
|
||||
disabled
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 cursor-default"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => ChangeWatchListPage(page+1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">
|
||||
<button
|
||||
on:click={() => ChangeWatchListPage(page + 1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</li>
|
||||
@@ -95,8 +120,12 @@
|
||||
{/if}
|
||||
<div class="flex mt-5">
|
||||
<div class="w-20 mx-auto">
|
||||
<select bind:value={perPage} on:change={(e) => changeCountPerPage(e)} id="countPerPage"
|
||||
class="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500">
|
||||
<select
|
||||
bind:value={perPage}
|
||||
on:change={(e) => changeCountPerPage(e)}
|
||||
id="countPerPage"
|
||||
class="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
{#each perPageOptions as option}
|
||||
<option value={option}>
|
||||
{option}
|
||||
@@ -108,35 +137,81 @@
|
||||
<div>
|
||||
<div>Total Anime: {aniListWatchListLoaded.data.Page.pageInfo.total}</div>
|
||||
{#if aniListWatchListLoaded.data.Page.pageInfo.lastPage <= 12}
|
||||
<div class="md:hidden">Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
|
||||
<div class="md:hidden">
|
||||
Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
</div>
|
||||
{:else}
|
||||
<div>Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
|
||||
<div>
|
||||
Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="max-w-xs mx-auto">
|
||||
<div class="relative flex items-center max-w-[11rem]">
|
||||
<button type="button" id="decrement-button" on:click={() => ChangeWatchListPage(page-1)}
|
||||
class="bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none">
|
||||
<svg class="w-3 h-3 text-white" aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M1 1h16"/>
|
||||
<button
|
||||
type="button"
|
||||
id="decrement-button"
|
||||
on:click={() => ChangeWatchListPage(page - 1)}
|
||||
class={page <= 1
|
||||
? "border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||
disabled={page <= 1}
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 18 2"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M1 1h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<input type="number" min="1" max="{aniListWatchListLoaded.data.Page.pageInfo.lastPage}"
|
||||
on:keydown={changePage} id="page-counter"
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max={aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
on:keydown={changePage}
|
||||
id="page-counter"
|
||||
class="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none border-x-0 h-11 font-medium text-center text-sm block w-full pb-6 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
|
||||
value={page} required/>
|
||||
<div class="absolute bottom-1 start-1/2 -translate-x-1/2 rtl:translate-x-1/2 flex items-center text-xs text-gray-400 space-x-1 rtl:space-x-reverse">
|
||||
value={page}
|
||||
required
|
||||
/>
|
||||
<div
|
||||
class="absolute bottom-1 start-1/2 -translate-x-1/2 rtl:translate-x-1/2 flex items-center text-xs text-gray-400 space-x-1 rtl:space-x-reverse"
|
||||
>
|
||||
<span>Page #</span>
|
||||
</div>
|
||||
<button type="button" id="increment-button" on:click={() => ChangeWatchListPage(page+1)}
|
||||
class="hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none">
|
||||
<svg class="w-3 h-3 text-white" aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 1v16M1 9h16"/>
|
||||
<button
|
||||
type="button"
|
||||
id="increment-button"
|
||||
on:click={() => ChangeWatchListPage(page + 1)}
|
||||
class={page >= aniListWatchListLoaded.data.Page.pageInfo.lastPage
|
||||
? "border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||
disabled={page >= aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 18 18"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 1v16M1 9h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
0
frontend/src/helperComponents/Sort.svelte
Normal file
0
frontend/src/helperComponents/Sort.svelte
Normal file
@@ -6,30 +6,50 @@
|
||||
loading,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import { push } from "svelte-spa-router";
|
||||
import type {AniListCurrentUserWatchList} from "../anilist/types/AniListCurrentUserWatchListType"
|
||||
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||
import { Rating } from "flowbite-svelte";
|
||||
import loader from '../helperFunctions/loader'
|
||||
import loader from "../helperFunctions/loader";
|
||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||
|
||||
let isAniListLoggedIn: boolean;
|
||||
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
||||
|
||||
let isAniListLoggedIn: boolean
|
||||
let aniListWatchListLoaded: AniListCurrentUserWatchList
|
||||
|
||||
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
|
||||
aniListWatchlist.subscribe((value) => aniListWatchListLoaded = value)
|
||||
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = value));
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if isAniListLoggedIn}
|
||||
<div class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center">
|
||||
<h1 class="text-left text-xl font-bold mb-4">Your AniList WatchList</h1>
|
||||
<div
|
||||
class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center"
|
||||
>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h1 class="text-left text-xl font-bold">Your AniList WatchList</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="py-2 px-4 bg-gray-700 rounded-lg"
|
||||
on:click={async () => {
|
||||
loading.set(true);
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
loading.set(false);
|
||||
}}
|
||||
>
|
||||
Refresh WatchList
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||
<div
|
||||
class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8"
|
||||
>
|
||||
{#each aniListWatchListLoaded.data.Page.mediaList as media}
|
||||
<div use:loader={loading} class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7">
|
||||
<div
|
||||
use:loader={loading}
|
||||
class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7"
|
||||
>
|
||||
<div class="flex flex-col items-center group">
|
||||
<button on:click={() => {
|
||||
push(`#/anime/${media.media.id}`)
|
||||
<button
|
||||
on:click={() => {
|
||||
push(`#/anime/${media.media.id}`);
|
||||
// loading.set(true)
|
||||
// GetAniListSingleItem(media.media.id, true).then(() => {
|
||||
// loading.set(false)
|
||||
@@ -37,27 +57,38 @@
|
||||
// })
|
||||
}}
|
||||
>
|
||||
<img class="rounded-lg" src={media.media.coverImage.large} alt={
|
||||
media.media.title.english === "" ?
|
||||
media.media.title.romaji :
|
||||
media.media.title.english
|
||||
}/>
|
||||
<img
|
||||
class="rounded-lg w-[230px] h-[330px] object-cover"
|
||||
src={media.media.coverImage.large}
|
||||
alt={media.media.title.english === ""
|
||||
? media.media.title.romaji
|
||||
: media.media.title.english}
|
||||
/>
|
||||
</button>
|
||||
<Rating id="anime-rating" total={5} size={35} rating={media.score/2.0}/>
|
||||
<button class="mt-4 text-md font-semibold text-white-700"
|
||||
on:click={() => GetAnimeSingleItem(media.media.id, true)}>
|
||||
{
|
||||
media.media.title.english === "" ?
|
||||
media.media.title.romaji :
|
||||
media.media.title.english
|
||||
}
|
||||
<Rating
|
||||
id="anime-rating"
|
||||
total={5}
|
||||
size={35}
|
||||
rating={media.score / 2.0}
|
||||
/>
|
||||
<button
|
||||
class="mt-4 text-md font-semibold text-white-700"
|
||||
on:click={() => GetAnimeSingleItem(media.media.id, true)}
|
||||
>
|
||||
{media.media.title.english === ""
|
||||
? media.media.title.romaji
|
||||
: media.media.title.english}
|
||||
</button>
|
||||
<p class="mt-1 text-lg font-medium text-white-900">{media.progress}
|
||||
/ {media.media.nextAiringEpisode.episode !== 0 ?
|
||||
media.media.nextAiringEpisode.episode - 1 : media.media.episodes}</p>
|
||||
<p class="mt-1 text-lg font-medium text-white-900">
|
||||
{media.progress}
|
||||
/ {media.media.nextAiringEpisode.episode !== 0
|
||||
? media.media.nextAiringEpisode.episode - 1
|
||||
: media.media.episodes}
|
||||
</p>
|
||||
{#if media.media.episodes > 0}
|
||||
<p class="mt-1 text-lg font-medium text-white-900">Total
|
||||
Episodes: {media.media.episodes}</p>
|
||||
<p class="mt-1 text-lg font-medium text-white-900">
|
||||
Total Episodes: {media.media.episodes}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<script lang="ts">
|
||||
import {BrowserOpenURL} from "../../wailsjs/runtime"
|
||||
import { BrowserOpenURL } from "../../wailsjs/runtime";
|
||||
|
||||
export let id: string
|
||||
let url = ""
|
||||
let isAniList = false
|
||||
let isMAL = false
|
||||
let isSimkl = false
|
||||
let newId = id
|
||||
let re = /[ams]?-?(.*)/
|
||||
export let id: string;
|
||||
export let url = "";
|
||||
let isAniList = false;
|
||||
let isMAL = false;
|
||||
let isSimkl = false;
|
||||
let newId = id;
|
||||
let re = /[ams]?-?(.*)/;
|
||||
if (id !== undefined && id.length > 0) {
|
||||
isAniList = id.includes("a-")
|
||||
isMAL = id.includes("m-")
|
||||
isSimkl = id.includes("s-")
|
||||
newId = id.match(re)[1]
|
||||
isAniList = id.includes("a-");
|
||||
isMAL = id.includes("m-");
|
||||
isSimkl = id.includes("s-");
|
||||
if (isAniList || isMAL || isSimkl) newId = id.match(re)[1];
|
||||
else newId = id;
|
||||
}
|
||||
|
||||
|
||||
if (isAniList) url = `https://anilist.co/anime/${newId}`
|
||||
if (isMAL) url = `https://myanimelist.net/anime/${newId}`
|
||||
if (isSimkl) url = `https://simkl.com/anime/${newId}`
|
||||
if (isAniList) url = `https://anilist.co/anime/${newId}`;
|
||||
if (isMAL) url = `https://myanimelist.net/anime/${newId}`;
|
||||
if (isSimkl) url = `https://simkl.com/anime/${newId}`;
|
||||
</script>
|
||||
|
||||
{#if url.length > 0}
|
||||
<button class="underline underline-offset-2 px-4 py-1" on:click={() => BrowserOpenURL(url)}>{newId}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="underline underline-offset-2 px-4 py-1"
|
||||
on:click={() => BrowserOpenURL(url)}>{newId}</button
|
||||
>
|
||||
{:else}
|
||||
{id}
|
||||
{/if}
|
||||
|
||||
37
frontend/src/helperFunctions/convertAniListDateIn.ts
Normal file
37
frontend/src/helperFunctions/convertAniListDateIn.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import moment from "moment";
|
||||
|
||||
const convertAniListDateToString = (date: {
|
||||
year?: number;
|
||||
month?: number;
|
||||
day?: number;
|
||||
}): string => {
|
||||
if (
|
||||
date.year === undefined ||
|
||||
(date.year === 0 && date.month === undefined) ||
|
||||
(date.month === 0 && date.day === undefined) ||
|
||||
date.day === 0
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
const newISODate = new Date(date.year, date.month - 1, date.day);
|
||||
const newMoment = moment(newISODate);
|
||||
return newMoment.format("MM-DD-YYYY");
|
||||
};
|
||||
|
||||
const convertAniListDateToDate = (date: {
|
||||
year?: number;
|
||||
month?: number;
|
||||
day?: number;
|
||||
}): Date | null => {
|
||||
if (
|
||||
date.year === undefined ||
|
||||
(date.year === 0 && date.month === undefined) ||
|
||||
(date.month === 0 && date.day === undefined) ||
|
||||
date.day === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return new Date(date.year, date.month - 1, date.day);
|
||||
};
|
||||
|
||||
export { convertAniListDateToString, convertAniListDateToDate };
|
||||
@@ -1,17 +0,0 @@
|
||||
import moment from "moment";
|
||||
|
||||
export default (date: {
|
||||
year?: number,
|
||||
month?: number,
|
||||
day?: number,
|
||||
}): string => {
|
||||
if (date.year === undefined || date.year === 0
|
||||
&& date.month === undefined || date.month === 0
|
||||
&& date.day === undefined || date.day === 0
|
||||
) {
|
||||
return ""
|
||||
}
|
||||
const newISODate = new Date(date.year, date.month - 1, date.day)
|
||||
const newMoment = moment(newISODate)
|
||||
return newMoment.format('YYYY-MM-DD')
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
type Date = {
|
||||
year: number,
|
||||
month: number,
|
||||
day: number,
|
||||
}
|
||||
|
||||
export default (date: string): Date => {
|
||||
if (date === "") {
|
||||
return {
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
}
|
||||
}
|
||||
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/
|
||||
const newDate = re.exec(date)
|
||||
return {
|
||||
year: Number(newDate[1]),
|
||||
month: Number(newDate[2]),
|
||||
day: Number(newDate[3])
|
||||
}
|
||||
}
|
||||
39
frontend/src/helperFunctions/convertDateToAniList.ts
Normal file
39
frontend/src/helperFunctions/convertDateToAniList.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
type AnilistDate = {
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
};
|
||||
|
||||
const convertDateStringToAniList = (date: string): AnilistDate => {
|
||||
if (date === "") {
|
||||
return {
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
};
|
||||
}
|
||||
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
||||
const newDate = re.exec(date);
|
||||
return {
|
||||
year: Number(newDate[1]),
|
||||
month: Number(newDate[2]),
|
||||
day: Number(newDate[3]),
|
||||
};
|
||||
};
|
||||
|
||||
const convertDateToAniList = (date: Date | null): AnilistDate => {
|
||||
if (date === null) {
|
||||
return {
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
};
|
||||
}
|
||||
return {
|
||||
year: Number(date.getFullYear()),
|
||||
month: Number(date.getMonth()) + 1,
|
||||
day: Number(date.getDate()),
|
||||
};
|
||||
};
|
||||
|
||||
export { convertDateStringToAniList, convertDateToAniList };
|
||||
@@ -1,34 +1,81 @@
|
||||
<script lang="ts" context="module">
|
||||
import {CheckIfAniListLoggedIn, GetAniListLoggedInUser, GetAniListUserWatchingList} from "../../wailsjs/go/main/App";
|
||||
import {MediaListSort} from "../anilist/types/AniListTypes";
|
||||
import { aniListUser, watchListPage, animePerPage, aniListPrimary, aniListLoggedIn, aniListWatchlist } from "./GlobalVariablesAndHelperFunctions.svelte"
|
||||
import {
|
||||
CheckIfAniListLoggedIn,
|
||||
GetAniListLoggedInUser,
|
||||
GetAniListUserWatchingList,
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import {
|
||||
aniListUser,
|
||||
watchListPage,
|
||||
animePerPage,
|
||||
aniListPrimary,
|
||||
aniListLoggedIn,
|
||||
aniListWatchlist,
|
||||
aniListSort,
|
||||
clearApiError,
|
||||
setApiError,
|
||||
} from "./GlobalVariablesAndHelperFunctions.svelte";
|
||||
|
||||
let isAniListPrimary: boolean
|
||||
let page: number
|
||||
let perPage: number
|
||||
let isAniListPrimary: boolean;
|
||||
let page: number;
|
||||
let perPage: number;
|
||||
let sort: string;
|
||||
|
||||
aniListPrimary.subscribe(value => isAniListPrimary = value)
|
||||
watchListPage.subscribe(value => page = value)
|
||||
animePerPage.subscribe(value => perPage = value)
|
||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||
watchListPage.subscribe((value) => (page = value));
|
||||
animePerPage.subscribe((value) => (perPage = value));
|
||||
aniListSort.subscribe((value) => (sort = value));
|
||||
|
||||
export const LoadAniListUser = async () => {
|
||||
await GetAniListLoggedInUser().then(user => {
|
||||
aniListUser.set(user)
|
||||
})
|
||||
try {
|
||||
await GetAniListLoggedInUser().then((user) => {
|
||||
aniListUser.set(user);
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setApiError(
|
||||
"anilist",
|
||||
`Failed to load user: ${errorMsg}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const LoadAniListWatchList = async () => {
|
||||
await GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((watchList) => {
|
||||
aniListWatchlist.set(watchList)
|
||||
})
|
||||
try {
|
||||
const watchList = await GetAniListUserWatchingList(page, perPage, sort);
|
||||
aniListWatchlist.set(watchList);
|
||||
clearApiError();
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setApiError(
|
||||
"anilist",
|
||||
`Failed to load watch list: ${errorMsg}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
|
||||
};
|
||||
export const CheckIfAniListLoggedInAndLoadWatchList = async () => {
|
||||
const loggedIn = await CheckIfAniListLoggedIn()
|
||||
try {
|
||||
const loggedIn = await CheckIfAniListLoggedIn();
|
||||
if (loggedIn) {
|
||||
await LoadAniListUser()
|
||||
if (isAniListPrimary) await LoadAniListWatchList()
|
||||
await LoadAniListUser();
|
||||
if (isAniListPrimary) await LoadAniListWatchList();
|
||||
}
|
||||
aniListLoggedIn.set(loggedIn)
|
||||
aniListLoggedIn.set(loggedIn);
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setApiError(
|
||||
"anilist",
|
||||
`Authentication failed: ${errorMsg}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
aniListLoggedIn.set(false);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -10,153 +10,203 @@
|
||||
LogoutMyAnimeList,
|
||||
LogoutSimkl,
|
||||
SimklGetUserWatchlist,
|
||||
SimklSearch
|
||||
SimklSearch,
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import type {
|
||||
AniListCurrentUserWatchList,
|
||||
AniListGetSingleAnime
|
||||
AniListGetSingleAnime,
|
||||
} from "../anilist/types/AniListCurrentUserWatchListType.js";
|
||||
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, MALWatchlist, MyAnimeListUser} from "../mal/types/MALTypes";
|
||||
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,
|
||||
MALWatchlist,
|
||||
MyAnimeListUser,
|
||||
} from "../mal/types/MALTypes";
|
||||
import type { TableItems } from "../helperTypes/TableTypes";
|
||||
import { AniListGetSingleAnimeDefaultData } from "../helperDefaults/AniListGetSingleAnime";
|
||||
|
||||
export let aniListAnime = writable(AniListGetSingleAnimeDefaultData)
|
||||
export let title = writable("")
|
||||
export let aniListLoggedIn = writable(false)
|
||||
export let simklLoggedIn = writable(false)
|
||||
export let malLoggedIn = writable(false)
|
||||
export let simklWatchList = writable({} as SimklWatchList)
|
||||
export let aniListPrimary = writable(true)
|
||||
export let simklPrimary = writable(false)
|
||||
export let malPrimary = writable(false)
|
||||
export let simklUser = writable({} as SimklUser)
|
||||
export let aniListUser = writable({} as AniListUser)
|
||||
export let malUser = writable({} as MyAnimeListUser)
|
||||
export let aniListWatchlist = writable({} as AniListCurrentUserWatchList)
|
||||
export let malWatchList = writable({} as MALWatchlist)
|
||||
export let malAnime = writable({} as MALAnime)
|
||||
export let simklAnime = writable({} as SimklAnime)
|
||||
export let loading = writable(false)
|
||||
export let tableItems = writable([] as TableItems)
|
||||
export let aniListAnime = writable(AniListGetSingleAnimeDefaultData);
|
||||
export let title = writable("");
|
||||
export let aniListLoggedIn = writable(false);
|
||||
export let simklLoggedIn = writable(false);
|
||||
export let malLoggedIn = writable(false);
|
||||
export let simklWatchList = writable({} as SimklWatchList);
|
||||
export let aniListPrimary = writable(true);
|
||||
export let simklPrimary = writable(false);
|
||||
export let malPrimary = writable(false);
|
||||
export let simklUser = writable({} as SimklUser);
|
||||
export let aniListUser = writable({} as AniListUser);
|
||||
export let malUser = writable({} as MyAnimeListUser);
|
||||
export let aniListWatchlist = writable({} as AniListCurrentUserWatchList);
|
||||
export let malWatchList = writable({} as MALWatchlist);
|
||||
export let malAnime = writable({} as MALAnime);
|
||||
export let simklAnime = writable({} as SimklAnime);
|
||||
export let loading = writable(false);
|
||||
export let tableItems = writable([] as TableItems);
|
||||
export let watchlistNeedsRefresh = writable(false);
|
||||
export let aniListSort = writable(MediaListSort.UpdatedTimeDesc);
|
||||
|
||||
export let watchListPage = writable(1)
|
||||
export let animePerPage = writable(20)
|
||||
export let watchListPage = writable(1);
|
||||
export let animePerPage = writable(20);
|
||||
|
||||
let isAniListPrimary: boolean
|
||||
let page: number
|
||||
let perPage: number
|
||||
let aniWatchlist: AniListCurrentUserWatchList
|
||||
let currentAniListAnime: AniListGetSingleAnime
|
||||
let isAniListPrimary: boolean;
|
||||
let page: number;
|
||||
let perPage: number;
|
||||
let sort: string;
|
||||
let aniWatchlist: AniListCurrentUserWatchList;
|
||||
let currentAniListAnime: AniListGetSingleAnime;
|
||||
|
||||
let isMalLoggedIn: boolean
|
||||
let isSimklLoggedIn: boolean
|
||||
let isMalLoggedIn: boolean;
|
||||
let isSimklLoggedIn: boolean;
|
||||
|
||||
aniListPrimary.subscribe(value => isAniListPrimary = value)
|
||||
watchListPage.subscribe(value => page = value)
|
||||
animePerPage.subscribe(value => perPage = value)
|
||||
aniListWatchlist.subscribe(value => aniWatchlist = value)
|
||||
malLoggedIn.subscribe(value => isMalLoggedIn = value)
|
||||
simklLoggedIn.subscribe(value => isSimklLoggedIn = value)
|
||||
aniListAnime.subscribe(value => currentAniListAnime = value)
|
||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||
watchListPage.subscribe((value) => (page = value));
|
||||
animePerPage.subscribe((value) => (perPage = value));
|
||||
aniListWatchlist.subscribe((value) => (aniWatchlist = value));
|
||||
malLoggedIn.subscribe((value) => (isMalLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
aniListAnime.subscribe((value) => (currentAniListAnime = value));
|
||||
aniListSort.subscribe((value) => (sort = value));
|
||||
|
||||
|
||||
export async function GetAnimeSingleItem(aniId: number, login: boolean): Promise<""> {
|
||||
await GetAniListItem(aniId, login).then(aniListResult => {
|
||||
let finalResult: AniListGetSingleAnime
|
||||
finalResult = aniListResult
|
||||
if (login === false) {
|
||||
finalResult.data.MediaList.status = ""
|
||||
finalResult.data.MediaList.score = 0
|
||||
finalResult.data.MediaList.progress = 0
|
||||
finalResult.data.MediaList.notes = ""
|
||||
finalResult.data.MediaList.repeat = 0
|
||||
finalResult.data.MediaList.startedAt.day = 0
|
||||
finalResult.data.MediaList.startedAt.month = 0
|
||||
finalResult.data.MediaList.startedAt.year = 0
|
||||
finalResult.data.MediaList.completedAt.day = 0
|
||||
finalResult.data.MediaList.completedAt.month = 0
|
||||
finalResult.data.MediaList.completedAt.year = 0
|
||||
export interface ApiError {
|
||||
service: string;
|
||||
message: string;
|
||||
statusCode?: string;
|
||||
canRetry: boolean;
|
||||
}
|
||||
aniListAnime.set(finalResult)
|
||||
title.set(currentAniListAnime.data.MediaList.media.title.english === "" ?
|
||||
currentAniListAnime.data.MediaList.media.title.romaji :
|
||||
currentAniListAnime.data.MediaList.media.title.english)
|
||||
})
|
||||
export const apiError = writable<ApiError | null>(null);
|
||||
export const isApiDown = writable(false);
|
||||
export function setApiError(
|
||||
service: string,
|
||||
message: string,
|
||||
statusCode?: string,
|
||||
canRetry: boolean = true,
|
||||
) {
|
||||
apiError.set({
|
||||
service,
|
||||
message,
|
||||
statusCode,
|
||||
canRetry,
|
||||
});
|
||||
isApiDown.set(true);
|
||||
}
|
||||
export function clearApiError() {
|
||||
apiError.set(null);
|
||||
isApiDown.set(false);
|
||||
}
|
||||
|
||||
export async function GetAnimeSingleItem(
|
||||
aniId: number,
|
||||
login: boolean,
|
||||
): Promise<""> {
|
||||
await GetAniListItem(aniId, login).then((aniListResult) => {
|
||||
let finalResult: AniListGetSingleAnime;
|
||||
finalResult = aniListResult;
|
||||
if (login === false) {
|
||||
finalResult.data.MediaList.status = "";
|
||||
finalResult.data.MediaList.score = 0;
|
||||
finalResult.data.MediaList.progress = 0;
|
||||
finalResult.data.MediaList.notes = "";
|
||||
finalResult.data.MediaList.repeat = 0;
|
||||
finalResult.data.MediaList.startedAt.day = 0;
|
||||
finalResult.data.MediaList.startedAt.month = 0;
|
||||
finalResult.data.MediaList.startedAt.year = 0;
|
||||
finalResult.data.MediaList.completedAt.day = 0;
|
||||
finalResult.data.MediaList.completedAt.month = 0;
|
||||
finalResult.data.MediaList.completedAt.year = 0;
|
||||
}
|
||||
aniListAnime.set(finalResult);
|
||||
title.set(
|
||||
currentAniListAnime.data.MediaList.media.title.english === ""
|
||||
? currentAniListAnime.data.MediaList.media.title.romaji
|
||||
: currentAniListAnime.data.MediaList.media.title.english,
|
||||
);
|
||||
});
|
||||
if (isMalLoggedIn) {
|
||||
await GetMyAnimeListAnime(currentAniListAnime.data.MediaList.media.idMal).then(malResult => {
|
||||
malAnime.set(malResult)
|
||||
})
|
||||
await GetMyAnimeListAnime(
|
||||
currentAniListAnime.data.MediaList.media.idMal,
|
||||
).then((malResult) => {
|
||||
malAnime.set(malResult);
|
||||
});
|
||||
}
|
||||
if (isSimklLoggedIn) {
|
||||
await SimklSearch(currentAniListAnime.data.MediaList).then((value: SimklAnime) => {
|
||||
simklAnime.set(value)
|
||||
})
|
||||
await SimklSearch(currentAniListAnime.data.MediaList).then(
|
||||
(value: SimklAnime) => {
|
||||
simklAnime.set(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
export function loginToSimkl(): void {
|
||||
GetSimklLoggedInUser().then(user => {
|
||||
GetSimklLoggedInUser().then((user) => {
|
||||
if (Object.keys(user).length === 0) {
|
||||
simklLoggedIn.set(false)
|
||||
simklLoggedIn.set(false);
|
||||
} else {
|
||||
simklUser.set(user)
|
||||
SimklGetUserWatchlist().then(result => {
|
||||
simklWatchList.set(result)
|
||||
simklLoggedIn.set(true)
|
||||
})
|
||||
simklUser.set(user);
|
||||
SimklGetUserWatchlist().then((result) => {
|
||||
simklWatchList.set(result);
|
||||
simklLoggedIn.set(true);
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function loginToAniList(): void {
|
||||
GetAniListLoggedInUser().then(result => {
|
||||
aniListUser.set(result)
|
||||
GetAniListLoggedInUser().then((result) => {
|
||||
aniListUser.set(result);
|
||||
if (isAniListPrimary) {
|
||||
GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
|
||||
aniListWatchlist.set(result)
|
||||
aniListLoggedIn.set(true)
|
||||
})
|
||||
GetAniListUserWatchingList(page, perPage, sort).then((result) => {
|
||||
aniListWatchlist.set(result);
|
||||
aniListLoggedIn.set(true);
|
||||
});
|
||||
} else {
|
||||
aniListLoggedIn.set(true)
|
||||
aniListLoggedIn.set(true);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function loginToMAL(): void {
|
||||
GetMyAnimeListLoggedInUser().then(result => {
|
||||
malUser.set(result)
|
||||
malLoggedIn.set(true)
|
||||
})
|
||||
GetMyAnimeListLoggedInUser().then((result) => {
|
||||
malUser.set(result);
|
||||
malLoggedIn.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
export function logoutOfAniList(): void {
|
||||
LogoutAniList().then(result => {
|
||||
console.log(result)
|
||||
LogoutAniList().then((result) => {
|
||||
console.log(result);
|
||||
if (Object.keys(aniWatchlist).length !== 0) {
|
||||
aniListWatchlist.set({} as AniListCurrentUserWatchList)
|
||||
aniListWatchlist.set({} as AniListCurrentUserWatchList);
|
||||
}
|
||||
aniListUser.set({} as AniListUser)
|
||||
aniListLoggedIn.set(false)
|
||||
})
|
||||
aniListUser.set({} as AniListUser);
|
||||
aniListLoggedIn.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
export function logoutOfMAL(): void {
|
||||
LogoutMyAnimeList().then(result => {
|
||||
console.log(result)
|
||||
malUser.set({} as MyAnimeListUser)
|
||||
malLoggedIn.set(false)
|
||||
})
|
||||
LogoutMyAnimeList().then((result) => {
|
||||
console.log(result);
|
||||
malUser.set({} as MyAnimeListUser);
|
||||
malLoggedIn.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
export function logoutOfSimkl(): void {
|
||||
LogoutSimkl().then(result => {
|
||||
console.log(result)
|
||||
simklUser.set({} as SimklUser)
|
||||
simklLoggedIn.set(false)
|
||||
})
|
||||
LogoutSimkl().then((result) => {
|
||||
console.log(result);
|
||||
simklUser.set({} as SimklUser);
|
||||
simklLoggedIn.set(false);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -1,9 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { aniListAnime, GetAnimeSingleItem } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import Anime from "../helperComponents/Anime.svelte"
|
||||
import { AniListGetSingleAnimeDefaultData } from "../helperDefaults/AniListGetSingleAnime";
|
||||
import Spinner from "../helperComponents/Spinner.svelte";
|
||||
|
||||
export let params: Record<string, string>
|
||||
let loadPromise = load(params.id)
|
||||
$: loadPromise = load(params.id)
|
||||
|
||||
async function load(id: string) {
|
||||
aniListAnime.update(() => AniListGetSingleAnimeDefaultData)
|
||||
await GetAnimeSingleItem(Number(id), true)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key params.id}
|
||||
{#await loadPromise}
|
||||
<Spinner />
|
||||
{:then _}
|
||||
<Anime />
|
||||
{/await}
|
||||
{/key}
|
||||
@@ -5,16 +5,46 @@ import {
|
||||
aniListLoggedIn,
|
||||
aniListPrimary,
|
||||
loading,
|
||||
isApiDown,
|
||||
apiError,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import loader from '../helperFunctions/loader'
|
||||
import loader from "../helperFunctions/loader";
|
||||
|
||||
let isAniListPrimary: boolean
|
||||
let isAniListLoggedIn: boolean
|
||||
let isAniListPrimary: boolean;
|
||||
let isAniListLoggedIn: boolean;
|
||||
|
||||
aniListPrimary.subscribe((value) => isAniListPrimary = value)
|
||||
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
|
||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
</script>
|
||||
{#if isAniListLoggedIn && isAniListPrimary}
|
||||
|
||||
{#if $isApiDown}
|
||||
<div class="container py-10">
|
||||
<div
|
||||
class="bg-yellow-50 border border-yellow-200 rounded-lg p-6 text-center"
|
||||
>
|
||||
<svg
|
||||
class="mx-auto h-12 w-12 text-yellow-600 mb-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<h2 class="text-xl font-semibold text-yellow-900 mb-2">
|
||||
API Unavailable
|
||||
</h2>
|
||||
<p class="text-yellow-700 mb-4">
|
||||
The {$apiError?.service || "service"} is currently unavailable. The app will
|
||||
remain open, and you can retry when the service is back online.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if isAniListLoggedIn && isAniListPrimary}
|
||||
<div class="container py-10">
|
||||
<Pagination />
|
||||
<WatchList />
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// vite.config.ts
|
||||
import { defineConfig } from "file:///home/nymusicman/Code/AniTrack/frontend/node_modules/vite/dist/node/index.js";
|
||||
import { svelte } from "file:///home/nymusicman/Code/AniTrack/frontend/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
|
||||
var vite_config_default = defineConfig({
|
||||
plugins: [svelte()]
|
||||
});
|
||||
export {
|
||||
vite_config_default as default
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ueW11c2ljbWFuL0NvZGUvQW5pVHJhY2svZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL255bXVzaWNtYW4vQ29kZS9BbmlUcmFjay9mcm9udGVuZC92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ueW11c2ljbWFuL0NvZGUvQW5pVHJhY2svZnJvbnRlbmQvdml0ZS5jb25maWcudHNcIjtpbXBvcnQge2RlZmluZUNvbmZpZ30gZnJvbSAndml0ZSdcbmltcG9ydCB7c3ZlbHRlfSBmcm9tICdAc3ZlbHRlanMvdml0ZS1wbHVnaW4tc3ZlbHRlJ1xuXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW3N2ZWx0ZSgpXVxufSlcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBdVMsU0FBUSxvQkFBbUI7QUFDbFUsU0FBUSxjQUFhO0FBR3JCLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVMsQ0FBQyxPQUFPLENBQUM7QUFDcEIsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
|
||||
2
frontend/wailsjs/go/main/App.d.ts
vendored
2
frontend/wailsjs/go/main/App.d.ts
vendored
@@ -42,6 +42,8 @@ export function MyAnimeListLogin():Promise<void>;
|
||||
|
||||
export function MyAnimeListUpdate(arg1:main.MALAnime,arg2:main.MALUploadStatus):Promise<main.MalListStatus>;
|
||||
|
||||
export function ShowVersion():Promise<void>;
|
||||
|
||||
export function SimklGetUserWatchlist():Promise<main.SimklWatchListType>;
|
||||
|
||||
export function SimklLogin():Promise<void>;
|
||||
|
||||
@@ -82,6 +82,10 @@ export function MyAnimeListUpdate(arg1, arg2) {
|
||||
return window['go']['main']['App']['MyAnimeListUpdate'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ShowVersion() {
|
||||
return window['go']['main']['App']['ShowVersion']();
|
||||
}
|
||||
|
||||
export function SimklGetUserWatchlist() {
|
||||
return window['go']['main']['App']['SimklGetUserWatchlist']();
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ export namespace main {
|
||||
id: number;
|
||||
mediaId: number;
|
||||
userId: number;
|
||||
// Go type: struct { ID int "json:\"id\""; IDMal int "json:\"idMal\""; Title struct { Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\""; Description string "json:\"description\""; CoverImage struct { Large string "json:\"large\"" } "json:\"coverImage\""; Season string "json:\"season\""; SeasonYear int "json:\"seasonYear\""; Status string "json:\"status\""; Episodes int "json:\"episodes\""; NextAiringEpisode struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" } "json:\"nextAiringEpisode\""; Tags []struct { Id int "json:\"id\""; Name string "json:\"name\""; Description string "json:\"description\""; Rank int "json:\"rank\""; IsMediaSpoiler bool "json:\"isMediaSpoiler\""; IsAdult bool "json:\"isAdult\"" } "json:\"tags\""; IsAdult bool "json:\"isAdult\"" }
|
||||
// Go type: struct { ID int "json:\"id\""; IDMal int "json:\"idMal\""; Title struct { Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\""; Description string "json:\"description\""; CoverImage struct { Large string "json:\"large\"" } "json:\"coverImage\""; Season string "json:\"season\""; SeasonYear int "json:\"seasonYear\""; Status string "json:\"status\""; Episodes int "json:\"episodes\""; NextAiringEpisode struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" } "json:\"nextAiringEpisode\""; Genres []string "json:\"genres\""; Tags []struct { Id int "json:\"id\""; Name string "json:\"name\""; Description string "json:\"description\""; Rank int "json:\"rank\""; IsMediaSpoiler bool "json:\"isMediaSpoiler\""; IsAdult bool "json:\"isAdult\"" } "json:\"tags\""; IsAdult bool "json:\"isAdult\"" }
|
||||
media: any;
|
||||
status: string;
|
||||
// Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" }
|
||||
|
||||
2
frontend/wailsjs/runtime/runtime.d.ts
vendored
2
frontend/wailsjs/runtime/runtime.d.ts
vendored
@@ -134,7 +134,7 @@ export function WindowIsFullscreen(): Promise<boolean>;
|
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): Promise<Size>;
|
||||
export function WindowSetSize(width: number, height: number): void;
|
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
|
||||
16
go.mod
16
go.mod
@@ -1,11 +1,11 @@
|
||||
module AniTrack
|
||||
|
||||
go 1.23
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/99designs/keyring v1.2.2
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/wailsapp/wails/v2 v2.9.2
|
||||
github.com/wailsapp/wails/v2 v2.10.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.47.0 // indirect
|
||||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tkrajina/go-reflector v0.5.8 // indirect
|
||||
@@ -39,11 +39,11 @@ require (
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.19 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
)
|
||||
|
||||
// replace github.com/wailsapp/wails/v2 v2.9.1 => /home/nymusicman/go/pkg/mod
|
||||
|
||||
31
go.sum
31
go.sum
@@ -23,8 +23,9 @@ github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NM
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
@@ -59,8 +60,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
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/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
@@ -82,26 +83,26 @@ github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyT
|
||||
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k=
|
||||
github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
|
||||
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
},
|
||||
"info": {
|
||||
"productName": "AniTrack",
|
||||
"productVersion": "0.1.5"
|
||||
"productVersion": "0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user