Compare commits

..
2 Commits
Author SHA1 Message Date
john-okeefe b934276342 chore(version): bump version to 0.5.1
Increment version number from 0.5.0 to 0.5.1 for the upcoming release.
2026-03-20 10:56:39 -04:00
john-okeefe 6860a5541a style(frontend): format Header.svelte with consistent code style
Apply consistent formatting to Header.svelte:
- Add semicolons to all statements
- Improve JSX/HTML attribute formatting for better readability
- Add link import from svelte-spa-router for SPA navigation
- Format multi-line attributes with proper indentation

These changes improve code consistency and maintainability without
altering any functionality.
2026-03-20 10:56:37 -04:00
38 changed files with 1856 additions and 3780 deletions
-5
View File
@@ -33,8 +33,3 @@ environment.go
# REST (http files) # REST (http files)
http-client.private.env.json http-client.private.env.json
# Build artifacts
*.tar
*.tar.gz
/AniTrack
+209 -721
View File
File diff suppressed because it is too large Load Diff
+35 -97
View File
@@ -37,20 +37,6 @@ type AniListCurrentUserWatchList struct {
} `json:"data"` } `json:"data"`
} }
type AniListBrowseList struct {
Data struct {
Page struct {
PageInfo struct {
Total int `json:"total"`
PerPage int `json:"perPage"`
CurrentPage int `json:"currentPage"`
LastPage int `json:"lastPage"`
HasNextPage bool `json:"hasNextPage"`
} `json:"pageInfo"`
Media []Media `json:"mediaList"`
} `json:"Page"`
} `json:"data"`
}
type AniListGetSingleAnime struct { type AniListGetSingleAnime struct {
Data struct { Data struct {
MediaList MediaList `json:"MediaList"` MediaList MediaList `json:"MediaList"`
@@ -63,91 +49,43 @@ type AniListUpdateReturn struct {
} }
} }
type Media struct {
ID int `json:"id"`
IDMal int `json:"idMal"`
Title struct {
UserPreferred string `json:"userPreferred"`
Romaji string `json:"romaji"`
English string `json:"english"`
Native string `json:"native"`
} `json:"title"`
Description string `json:"description"`
CoverImage struct {
ExtraLarge string
Large string `json:"large"`
Medium string
Color string
} `json:"coverImage"`
BannerImage string
Format string
Season string `json:"season"`
SeasonYear int `json:"seasonYear"`
Status string `json:"status"`
Episodes int `json:"episodes"`
Duration int
CountryOfOrigin string
Source string
Synonyms []string
AverageScore int
MeanScore int
Popularity int
Trending int
Favourites int
isFavourite bool
Relations struct {
nodes struct {
id int
Title struct {
UserPreferred string `json:"userPreferred"`
Romaji string `json:"romaji"`
English string `json:"english"`
Native string `json:"native"`
} `json:"title"`
}
}
StartDate struct {
Year int
Month int
Day int
}
EndDate struct {
Year int
Month int
Day int
}
NextAiringEpisode struct {
AiringAt int `json:"airingAt"`
TimeUntilAiring int `json:"timeUntilAiring"`
Episode int `json:"episode"`
} `json:"nextAiringEpisode"`
AiringSchedule struct {
Nodes struct {
Id int
AiringAt int
TimeUntilAiring int
Episode int
MediaId int
}
}
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"`
}
type MediaList struct { type MediaList struct {
ID int `json:"id"` ID int `json:"id"`
MediaID int `json:"mediaId"` MediaID int `json:"mediaId"`
UserID int `json:"userId"` UserID int `json:"userId"`
Media 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"`
} `json:"media"`
Status string `json:"status"` Status string `json:"status"`
Media Media `json:"media"`
StartedAt struct { StartedAt struct {
Year int `json:"year"` Year int `json:"year"`
Month int `json:"month"` Month int `json:"month"`
+25 -72
View File
@@ -11,34 +11,10 @@ import (
"strings" "strings"
) )
// Unmarshalling accidental numbers received from MAL to strings func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string) {
func (f *FlexString) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err == nil {
*f = FlexString(s)
return nil
}
var n json.Number
if err := json.Unmarshal(data, &n); err == nil {
*f = FlexString(string(n))
return nil
}
return fmt.Errorf("FlexString: invalid value")
}
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string, error) {
client := &http.Client{} client := &http.Client{}
req, err := http.NewRequest(method, malUrl, strings.NewReader(body.Encode())) req, _ := 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("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken) req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken)
@@ -46,57 +22,47 @@ func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage,
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
fmt.Println("Errored when sending request to the server")
message, _ := json.Marshal(struct { message, _ := json.Marshal(struct {
Message string `json:"message"` Message string `json:"message" ts_type:"message"`
}{ }{
Message: "Network error: " + err.Error(), Message: "Errored when sending request to the server" + err.Error(),
}) })
return message, "", fmt.Errorf("network error: %w", err)
return message, resp.Status
} }
defer resp.Body.Close() defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) respBody, _ := io.ReadAll(resp.Body)
if err != nil {
return nil, resp.Status, fmt.Errorf("failed to read response: %w", err)
}
if resp.Status == "401 Unauthorized" { if resp.Status == "401 Unauthorized" {
refreshMyAnimeListAuthorizationToken() refreshMyAnimeListAuthorizationToken()
return MALHelper(method, malUrl, body) MALHelper(method, malUrl, body)
} }
if resp.Status != "200 OK" && resp.Status != "201 Created" && resp.Status != "202 Accepted" { return respBody, resp.Status
return respBody, resp.Status, fmt.Errorf("API returned status: %s", resp.Status)
}
return respBody, resp.Status, nil
} }
func (a *App) GetMyAnimeList(count int) (MALWatchlist, error) { func (a *App) GetMyAnimeList(count int) MALWatchlist {
limit := strconv.Itoa(count) limit := strconv.Itoa(count)
user := a.GetMyAnimeListLoggedInUser() user := a.GetMyAnimeListLoggedInUser()
malUrl := "https://api.myanimelist.net/v2/users/" + user.Name + "/animelist?fields=list_status&status=watching&limit=" + limit malUrl := "https://api.myanimelist.net/v2/users/" + user.Name + "/animelist?fields=list_status&status=watching&limit=" + limit
var malList MALWatchlist var malList MALWatchlist
respBody, resStatus, err := MALHelper("GET", malUrl, nil) respBody, resStatus := MALHelper("GET", malUrl, nil)
if err != nil {
return malList, fmt.Errorf("failed to get MAL watchlist: %w", err)
}
if resStatus == "200 OK" { if resStatus == "200 OK" {
err := json.Unmarshal(respBody, &malList) err := json.Unmarshal(respBody, &malList)
if err != nil { if err != nil {
log.Printf("Failed to unmarshal json response, %s\n", err) log.Printf("Failed to unmarshal json response, %s\n", err)
return malList, fmt.Errorf("failed to parse response: %w", err)
} }
} }
return malList, nil return malList
} }
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) (MalListStatus, error) { func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListStatus {
if update.NumTimesRewatched >= 1 { if update.NumTimesRewatched >= 1 {
update.IsRewatching = true update.IsRewatching = true
} else { } else {
@@ -112,52 +78,39 @@ func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) (MalList
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(anime.Id) + "/my_list_status" malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(anime.Id) + "/my_list_status"
var status MalListStatus var status MalListStatus
respBody, respStatus, err := MALHelper("PATCH", malUrl, body) respBody, respStatus := MALHelper("PATCH", malUrl, body)
if err != nil {
return status, fmt.Errorf("failed to update MAL entry: %w", err)
}
if respStatus == "200 OK" { if respStatus == "200 OK" {
err := json.Unmarshal(respBody, &status) err := json.Unmarshal(respBody, &status)
if err != nil { if err != nil {
log.Printf("Failed to unmarshal json response, %s\n", err) 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, error) { func (a *App) GetMyAnimeListAnime(id int) MALAnime {
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" 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 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" { if respStatus == "200 OK" {
err := json.Unmarshal(respBody, &malAnime) err := json.Unmarshal(respBody, &malAnime)
if err != nil { if err != nil {
log.Printf("Failed to unmarshal json response, %s\n", err) 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, error) { func (a *App) DeleteMyAnimeListEntry(id int) bool {
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "/my_list_status" malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "/my_list_status"
_, respStatus, err := MALHelper("DELETE", malUrl, nil) _, respStatus := MALHelper("DELETE", malUrl, nil)
if err != nil {
return false, fmt.Errorf("failed to delete MAL entry: %w", err)
}
if respStatus == "200 OK" { if respStatus == "200 OK" {
return true, nil return true
} else { } else {
return false, fmt.Errorf("delete failed with status: %s", respStatus) return false
} }
} }
+6 -10
View File
@@ -1,10 +1,6 @@
package main package main
import ( import "time"
"time"
)
type FlexString string
type MyAnimeListJWT struct { type MyAnimeListJWT struct {
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
@@ -133,11 +129,11 @@ type MALAnime struct {
Statistics struct { Statistics struct {
NumListUsers int `json:"num_list_users" ts_type:"numListUsers"` NumListUsers int `json:"num_list_users" ts_type:"numListUsers"`
Status struct { Status struct {
Watching FlexString `json:"watching" ts_type:"string"` Watching string `json:"watching" ts_type:"watching"`
Completed FlexString `json:"completed" ts_type:"string"` Completed string `json:"completed" ts_type:"completed"`
OnHold FlexString `json:"on_hold" ts_type:"string"` OnHold string `json:"on_hold" ts_type:"onHold"`
Dropped FlexString `json:"dropped" ts_type:"string"` Dropped string `json:"dropped" ts_type:"dropped"`
PlanToWatch FlexString `json:"plan_to_watch" ts_type:"string"` PlanToWatch string `json:"plan_to_watch" ts_type:"planToWatch"`
} }
} }
} }
+13 -31
View File
@@ -176,7 +176,7 @@ func (a *App) handleMyAnimeListCallback(wg *sync.WaitGroup, verifier *CodeVerifi
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Printf("Server error: %s\n", err) log.Fatalf("listen: %s\n", err)
} }
fmt.Println("Shutting down...") fmt.Println("Shutting down...")
}() }()
@@ -235,7 +235,7 @@ func getMyAnimeListAuthorizationToken(content string, verifier *CodeVerifier) My
return post return post
} }
func refreshMyAnimeListAuthorizationToken() bool { func refreshMyAnimeListAuthorizationToken() {
dataForURLs := struct { dataForURLs := struct {
GrantType string `json:"grant_type"` GrantType string `json:"grant_type"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
@@ -260,15 +260,13 @@ func refreshMyAnimeListAuthorizationToken() bool {
response, err := http.NewRequest("POST", "https://myanimelist.net/v1/oauth2/token", strings.NewReader(data.Encode())) response, err := http.NewRequest("POST", "https://myanimelist.net/v1/oauth2/token", strings.NewReader(data.Encode()))
if err != nil { if err != nil {
log.Printf("Failed at response, %s\n", err) log.Printf("Failed at response, %s\n", err)
return false
} }
response.Header.Add("Content-Type", "application/x-www-form-urlencoded") response.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{} client := &http.Client{}
res, resErr := client.Do(response) res, resErr := client.Do(response)
if resErr != nil { if resErr != nil {
log.Printf("Failed at res, %s\n", resErr) log.Printf("Failed at res, %s\n", err)
return false
} }
defer res.Body.Close() defer res.Body.Close()
@@ -276,22 +274,13 @@ func refreshMyAnimeListAuthorizationToken() bool {
returnedBody, err := io.ReadAll(res.Body) returnedBody, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
log.Printf("Could not read returned body, %s\n", err) log.Printf("Could not read returned body, %s\n", err)
return false
} }
var refreshed MyAnimeListJWT err = json.Unmarshal(returnedBody, &myAnimeListJwt)
err = json.Unmarshal(returnedBody, &refreshed)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
return false
} }
if refreshed.AccessToken == "" {
return false
}
myAnimeListJwt = refreshed
_ = myAnimeListRing.Set(keyring.Item{ _ = myAnimeListRing.Set(keyring.Item{
Key: "MyAnimeListTokenType", Key: "MyAnimeListTokenType",
Data: []byte(myAnimeListJwt.TokenType), Data: []byte(myAnimeListJwt.TokenType),
@@ -308,33 +297,26 @@ func refreshMyAnimeListAuthorizationToken() bool {
Key: "MyAnimeListRefreshToken", Key: "MyAnimeListRefreshToken",
Data: []byte(myAnimeListJwt.RefreshToken), Data: []byte(myAnimeListJwt.RefreshToken),
}) })
return true _, err = runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{
Title: "MyAnimeList Authorization",
Message: "It is now safe to close your browser tab",
})
if err != nil {
fmt.Println(err)
}
} }
func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser { func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser {
a.MyAnimeListLogin() a.MyAnimeListLogin()
user := createUser() user := createUser()
if user.Name == "" && !a.refreshMyAnimeListAndGetUser(&user) { if user.Name == "" {
a.LogoutMyAnimeList() refreshMyAnimeListAuthorizationToken()
a.MyAnimeListLogin()
user = createUser() user = createUser()
} }
return user return user
} }
func (a *App) refreshMyAnimeListAndGetUser(user *MyAnimeListUser) bool {
if !refreshMyAnimeListAuthorizationToken() {
return false
}
freshUser := createUser()
if freshUser.Name == "" {
return false
}
*user = freshUser
return true
}
func createUser() MyAnimeListUser { func createUser() MyAnimeListUser {
client := &http.Client{} client := &http.Client{}
-12
View File
@@ -1,12 +0,0 @@
TAGS := webkit2_41
.PHONY: dev build clean
dev:
wails dev -tags $(TAGS)
build:
wails build -tags $(TAGS)
clean:
rm -rf build/bin/*
+49 -92
View File
@@ -14,24 +14,16 @@ import (
var SimklWatchList SimklWatchListType var SimklWatchList SimklWatchListType
func SimklHelper(method string, url string, body interface{}) (json.RawMessage, error) { func SimklHelper(method string, url string, body interface{}) json.RawMessage {
reader, err := json.Marshal(body) reader, _ := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal body: %w", err)
}
var req *http.Request var req *http.Request
client := &http.Client{} client := &http.Client{}
if body != nil { if body != nil {
req, err = http.NewRequest(method, url, bytes.NewBuffer(reader)) req, _ = http.NewRequest(method, url, bytes.NewBuffer(reader))
} else { } else {
req, err = http.NewRequest(method, url, nil) req, _ = 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") req.Header.Add("Content-Type", "application/json")
@@ -40,45 +32,41 @@ func SimklHelper(method string, url string, body interface{}) (json.RawMessage,
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("network error: %w", err) 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
} }
defer resp.Body.Close() defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) respBody, _ := io.ReadAll(resp.Body)
if err != nil { return respBody
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, error) { func (a *App) SimklGetUserWatchlist() SimklWatchListType {
method := "GET" method := "GET"
url := "https://api.simkl.com/sync/all-items/anime" url := "https://api.simkl.com/sync/all-items/anime"
respBody, err := SimklHelper(method, url, nil) respBody := SimklHelper(method, url, nil)
if err != nil {
return SimklWatchListType{}, fmt.Errorf("failed to get Simkl watchlist: %w", err)
}
var errCheck struct { var errCheck struct {
Error string `json:"error"` Error string `json:"error"`
Message string `json:"message"` Message string `json:"message"`
} }
err = json.Unmarshal(respBody, &errCheck) err := json.Unmarshal(respBody, &errCheck)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
return SimklWatchListType{}, fmt.Errorf("failed to parse error response: %w", err)
} }
if errCheck.Error != "" { if errCheck.Error != "" {
a.LogoutSimkl() a.LogoutSimkl()
return SimklWatchListType{}, fmt.Errorf("Simkl API error: %s", errCheck.Message) return SimklWatchListType{}
} }
var watchlist SimklWatchListType var watchlist SimklWatchListType
@@ -86,15 +74,14 @@ func (a *App) SimklGetUserWatchlist() (SimklWatchListType, error) {
err = json.Unmarshal(respBody, &watchlist) err = json.Unmarshal(respBody, &watchlist)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
return SimklWatchListType{}, fmt.Errorf("failed to parse watchlist: %w", err)
} }
SimklWatchList = watchlist SimklWatchList = watchlist
return watchlist, nil return watchlist
} }
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, error) { func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
var episodes []Episode var episodes []Episode
var url string var url string
var shows []SimklPostShow var shows []SimklPostShow
@@ -125,19 +112,13 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, err
simklSync := SimklSyncHistoryType{shows} simklSync := SimklSyncHistoryType{shows}
respBody, err := SimklHelper("POST", url, simklSync) respBody := 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{} var success interface{}
err = json.Unmarshal(respBody, &success) err := json.Unmarshal(respBody, &success)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
return anime, fmt.Errorf("failed to parse response: %w", err)
} }
for i, simklAnime := range SimklWatchList.Anime { for i, simklAnime := range SimklWatchList.Anime {
@@ -150,10 +131,10 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, err
WatchListUpdate(anime) WatchListUpdate(anime)
return anime, nil return anime
} }
func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error) { func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
var url string var url string
showWithRating := ShowWithRating{ showWithRating := ShowWithRating{
Title: anime.Show.Title, Title: anime.Show.Title,
@@ -188,17 +169,13 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error)
Shows []interface{} `json:"shows" ts_type:"shows"` Shows []interface{} `json:"shows" ts_type:"shows"`
}{shows} }{shows}
respBody, err := SimklHelper("POST", url, simklSync) respBody := 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{} var success interface{}
err = json.Unmarshal(respBody, &success) err := json.Unmarshal(respBody, &success)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
return anime, fmt.Errorf("failed to parse response: %w", err)
} }
for i, simklAnime := range SimklWatchList.Anime { for i, simklAnime := range SimklWatchList.Anime {
@@ -211,10 +188,10 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error)
WatchListUpdate(anime) WatchListUpdate(anime)
return anime, nil return anime
} }
func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, error) { func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
url := "https://api.simkl.com/sync/add-to-list" url := "https://api.simkl.com/sync/add-to-list"
show := SimklShowStatus{ show := SimklShowStatus{
Title: anime.Show.Title, Title: anime.Show.Title,
@@ -234,17 +211,13 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, erro
Shows []SimklShowStatus `json:"shows" ts_type:"shows"` Shows []SimklShowStatus `json:"shows" ts_type:"shows"`
}{shows} }{shows}
respBody, err := SimklHelper("POST", url, simklSync) respBody := 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{} var success interface{}
err = json.Unmarshal(respBody, &success) err := json.Unmarshal(respBody, &success)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
return anime, fmt.Errorf("failed to parse response: %w", err)
} }
for i, simklAnime := range SimklWatchList.Anime { for i, simklAnime := range SimklWatchList.Anime {
@@ -257,20 +230,15 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, erro
WatchListUpdate(anime) WatchListUpdate(anime)
return anime, nil return anime
} }
func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) { func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
var result SimklAnime var result SimklAnime
if reflect.DeepEqual(SimklWatchList, SimklWatchListType{}) { if reflect.DeepEqual(SimklWatchList, SimklWatchListType{}) {
fmt.Println("Watchlist empty. Calling...") fmt.Println("Watchlist empty. Calling...")
watchlist, err := a.SimklGetUserWatchlist() SimklWatchList = 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 { for _, anime := range SimklWatchList.Anime {
@@ -287,30 +255,22 @@ func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) {
var anime SimklSearchType var anime SimklSearchType
url := "https://api.simkl.com/search/id?anilist=" + strconv.Itoa(aniListAnime.Media.ID) url := "https://api.simkl.com/search/id?anilist=" + strconv.Itoa(aniListAnime.Media.ID)
respBody, err := SimklHelper("GET", url, nil) respBody := SimklHelper("GET", url, nil)
if err != nil {
log.Printf("Failed to search Simkl: %s\n", err) err := json.Unmarshal(respBody, &anime)
return result, fmt.Errorf("failed to search Simkl by AniList ID: %w", err)
}
err = json.Unmarshal(respBody, &anime)
if len(anime) == 0 { if len(anime) == 0 {
url = "https://api.simkl.com/search/id?mal=" + strconv.Itoa(aniListAnime.Media.IDMal) url = "https://api.simkl.com/search/id?mal=" + strconv.Itoa(aniListAnime.Media.IDMal)
respBody, err = SimklHelper("GET", url, nil) respBody = 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) err = json.Unmarshal(respBody, &anime)
} }
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
return result, fmt.Errorf("failed to parse search results: %w", err)
} }
if len(anime) == 0 { if len(anime) == 0 {
return result, nil return result
} }
for _, watchListAnime := range SimklWatchList.Anime { for _, watchListAnime := range SimklWatchList.Anime {
@@ -328,10 +288,10 @@ func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) {
} }
} }
return result, nil return result
} }
func (a *App) SimklSyncRemove(anime SimklAnime) (bool, error) { func (a *App) SimklSyncRemove(anime SimklAnime) bool {
url := "https://api.simkl.com/sync/history/remove" url := "https://api.simkl.com/sync/history/remove"
var showArray []SimklShowStatus var showArray []SimklShowStatus
@@ -352,28 +312,25 @@ func (a *App) SimklSyncRemove(anime SimklAnime) (bool, error) {
Shows: showArray, Shows: showArray,
} }
respBody, err := SimklHelper("POST", url, show) respBody := 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 var success SimklDeleteType
err = json.Unmarshal(respBody, &success)
err := json.Unmarshal(respBody, &success)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
return false, fmt.Errorf("failed to parse response: %w", err)
} }
if success.Deleted.Shows >= 1 { if success.Deleted.Shows >= 1 {
for i, simklAnime := range SimklWatchList.Anime { for i, simklAnime := range SimklWatchList.Anime {
if simklAnime.Show.Ids.Simkl == anime.Show.Ids.Simkl { if simklAnime.Show.Ids.Simkl == anime.Show.Ids.Simkl {
SimklWatchList.Anime = slices.Delete(SimklWatchList.Anime, i, i+1) SimklWatchList.Anime = slices.Delete(SimklWatchList.Anime, i, i+1)
} }
} }
return true, nil return true
} else {
return false
} }
return false, fmt.Errorf("no shows were deleted")
} }
func WatchListUpdate(anime SimklAnime) { func WatchListUpdate(anime SimklAnime) {
+2 -3
View File
@@ -116,7 +116,7 @@ func (a *App) handleSimklCallback(wg *sync.WaitGroup) {
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Printf("Server error: %s\n", err) log.Fatalf("listen: %s\n", err)
} }
fmt.Println("Shutting down...") fmt.Println("Shutting down...")
}() }()
@@ -138,8 +138,7 @@ func getSimklAuthorizationToken(content string) SimklJWT {
} }
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
log.Printf("Failed to marshal data: %s\n", err) log.Fatal(err)
return SimklJWT{}
} }
response, err := http.NewRequest("POST", "https://api.simkl.com/oauth/token", bytes.NewBuffer(jsonData)) response, err := http.NewRequest("POST", "https://api.simkl.com/oauth/token", bytes.NewBuffer(jsonData))
@@ -16,6 +16,7 @@ headers {
} }
body:graphql { body:graphql {
# Write your query or mutation here
query ( query (
$page: Int $page: Int
$perPage: Int $perPage: Int
@@ -40,79 +41,23 @@ body:graphql {
id id
idMal idMal
title { title {
userPreferred
romaji romaji
english english
native native
} }
description description
coverImage { coverImage {
extraLarge
large large
medium
color
} }
bannerImage
format
season season
seasonYear seasonYear
status status
episodes episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
startDate {
year
month
day
}
endDate {
year
month
day
}
nextAiringEpisode { nextAiringEpisode {
airingAt airingAt
timeUntilAiring timeUntilAiring
episode episode
} }
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
} }
status status
startedAt { startedAt {
@@ -17,161 +17,63 @@ headers {
} }
body:graphql { body:graphql {
mutation ( mutation(
$mediaId: Int $mediaId:Int,
$progress: Int $progress:Int,
$status: MediaListStatus $status:MediaListStatus,
$score: Float $score:Float,
$repeat: Int $repeat:Int,
$notes: String $notes:String,
$startedAt: FuzzyDateInput $startedAt:FuzzyDateInput,
$completedAt: FuzzyDateInput $completedAt:FuzzyDateInput,
) { ){
SaveMediaListEntry( SaveMediaListEntry(
mediaId: $mediaId mediaId:$mediaId,
progress: $progress progress:$progress,
status: $status status:$status,
score: $score score:$score,
repeat: $repeat repeat:$repeat,
notes: $notes notes:$notes,
startedAt: $startedAt startedAt:$startedAt
completedAt: $completedAt completedAt:$completedAt
) { ){
id mediaId
mediaId progress
userId status
media { score
id repeat
idMal notes
title { startedAt{
userPreferred year
romaji month
english day
native }
} completedAt{
description year
coverImage { month
extraLarge day
large }
medium }
color }
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
startDate {
year
month
day
}
endDate {
year
month
day
}
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
status
startedAt {
year
month
day
}
completedAt {
year
month
day
}
notes
progress
score
repeat
user {
id
name
avatar {
large
medium
}
statistics {
anime {
count
statuses {
status
count
}
}
}
}
}
}
} }
body:graphql:vars { body:graphql:vars {
{ {
"mediaId": 170998, "mediaId":170998,
"progress": 5, "progress":5,
"status": "CURRENT", "status":"CURRENT",
"score": 9, "score":9.0,
"repeat": 0, "repeat":0,
"notes": ",malSync::eyJ1IjoiaHR0cHM6Ly93d3cuY3J1bmNoeXJvbGwuY29tL3Nlcmllcy9HVkRIWDg1Wk4vI3NlYXNvbj1HNjNWQzJHUUsiLCJwIjoiIn0=::", "notes":",malSync::eyJ1IjoiaHR0cHM6Ly93d3cuY3J1bmNoeXJvbGwuY29tL3Nlcmllcy9HVkRIWDg1Wk4vI3NlYXNvbj1HNjNWQzJHUUsiLCJwIjoiIn0=::",
"startedAt": { "startedAt":{
"year": 2024, "year":2024,
"month": 7, "month":7,
"day": 10 "day":10
}, },
"completedAt": { "completedAt":{
"year": 0, "year": 0,
"month": 0, "month":0,
"day": 0 "day":0
} }
} }
} }
@@ -1,191 +0,0 @@
meta {
name: SeasonBasedBrowse
type: graphql
seq: 1
}
post {
url: https://graphql.anilist.co
body: graphql
auth: inherit
}
body:graphql {
query (
$page: Int = 1
$perPage: Int = 20
$id: Int
$type: MediaType
$isAdult: Boolean = false
$search: String
$format: [MediaFormat]
$status: MediaStatus
$countryOfOrigin: CountryCode
$source: MediaSource
$season: MediaSeason
$seasonYear: Int
$year: String
$onList: Boolean
$yearLesser: FuzzyDateInt
$yearGreater: FuzzyDateInt
$episodeLesser: Int
$episodeGreater: Int
$durationLesser: Int
$durationGreater: Int
$chapterLesser: Int
$chapterGreater: Int
$volumeLesser: Int
$volumeGreater: Int
$licensedBy: [Int]
$isLicensed: Boolean
$genres: [String]
$excludedGenres: [String]
$tags: [String]
$excludedTags: [String]
$minimumTagRank: Int
$sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC]
) {
Page(page: $page, perPage: $perPage) {
pageInfo {
total
perPage
currentPage
lastPage
hasNextPage
}
media(
id: $id
type: $type
season: $season
format_in: $format
status: $status
countryOfOrigin: $countryOfOrigin
source: $source
search: $search
onList: $onList
seasonYear: $seasonYear
startDate_like: $year
startDate_lesser: $yearLesser
startDate_greater: $yearGreater
episodes_lesser: $episodeLesser
episodes_greater: $episodeGreater
duration_lesser: $durationLesser
duration_greater: $durationGreater
chapters_lesser: $chapterLesser
chapters_greater: $chapterGreater
volumes_lesser: $volumeLesser
volumes_greater: $volumeGreater
licensedById_in: $licensedBy
isLicensed: $isLicensed
genre_in: $genres
genre_not_in: $excludedGenres
tag_in: $tags
tag_not_in: $excludedTags
minimumTagRank: $minimumTagRank
sort: $sort
isAdult: $isAdult
) {
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
startDate {
year
month
day
}
endDate {
year
month
day
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
}
}
}
body:graphql:vars {
{
"page": 1,
"perPage": 20,
"season": "SUMMER",
"seasonYear": 2026,
"type": "ANIME",
"excludedTags": [
"Ecchi",
"LGBTQ+ Themes",
"Yuri"
],
"excludedGenres": [
"Boy's Love"
]
}
}
settings {
encodeUrl: true
timeout: 0
}
@@ -1,8 +0,0 @@
meta {
name: AniListCalendar
seq: 4
}
auth {
mode: inherit
}
+4 -17
View File
@@ -1,39 +1,26 @@
#!/bin/bash #!/bin/bash
# copy desktop file # copy desktop file
if [ ! -f "$HOME/.local/share/applications/AniTrack.desktop" ]; then if [ -e "~/.local/share/applications/AniTrack.desktop" ]; then
if [ -d "~/.local/share/applications/" ]; then if [ -d "~/.local/share/applications/" ]; then
echo "Copying desktop file..."
cp ./AniTrack.desktop ~/.local/share/applications/ cp ./AniTrack.desktop ~/.local/share/applications/
else else
mkdir -p ~/.local/share/applications/ mkdir -p ~/.local/share/applications/
echo "Copying desktop file..."
cp ./AniTrack.desktop ~/.local/share/applications/ cp ./AniTrack.desktop ~/.local/share/applications/
fi fi
else
echo "Desktop file already installed..."
fi fi
# copy icons to xdg folders # copy icons to xdg folders
for size in 32 48 64 128; do for size in 32 48 64 128; do
if [ ! -f $HOME/.local/share/icons/hicolor/${size}x${size}/apps/AniTrack.png ]; then xdg-icon-resource install --novendor --context apps --size $size ./icon/$size/AniTrack.png AniTrack
echo "Installing ${size} icon size..."
xdg-icon-resource install --novendor --context apps --size $size ./icon/$size/AniTrack.png AniTrack
else
echo "${size} icon size already exists..."
fi
done done
# copy AniTrack Binary to $HOME/Applications/ # copy AniTrack Binary to $HOME/Applications/
if ! [ -d "$HOME/Applications" ]; then if ! [ -d "~/Applications" ]; then
mkdir -p ~/Applications mkdir -p ~/Applications
echo "Installing app to ~/Applications..."
cp ./bin/AniTrack ~/Applications/ cp ./bin/AniTrack ~/Applications/
elif ! [[ -e $HOME/Applications/AniTrack ]]; then elif ! [[ -e ~/Applications/AniTrack ]]; then
echo "Installing app to ~/Applications"
cp ./bin/AniTrack ~/Applications/ cp ./bin/AniTrack ~/Applications/
else
echo "AniTrack already in Applications..."
fi fi
echo "AniTrack has been successfully installed." echo "AniTrack has been successfully installed."
+5
View File
@@ -0,0 +1,5 @@
{
"recommendations": [
"svelte.svelte-vscode"
]
}
+34 -67
View File
@@ -1,75 +1,42 @@
<script lang="ts"> <script lang="ts">
import { import {
aniListLoggedIn, aniListLoggedIn,
malLoggedIn, malLoggedIn,
simklLoggedIn, simklLoggedIn,
watchlistNeedsRefresh, } from "./helperModules/GlobalVariablesAndHelperFunctions.svelte";
aniListPrimary, import {onMount} from "svelte";
malPrimary, import Router from "svelte-spa-router"
simklPrimary, import Home from "./routes/Home.svelte";
malWatchList, import {wrap} from "svelte-spa-router/wrap";
simklWatchList, import Spinner from "./helperComponents/Spinner.svelte";
serviceLoggingIn, import Header from "./helperComponents/Header.svelte";
} from "./helperModules/GlobalVariablesAndHelperFunctions.svelte"; import {CheckIfAniListLoggedInAndLoadWatchList} from "./helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import { onMount } from "svelte"; import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte";
import Router from "svelte-spa-router"; import {CheckIfSimklLoggedInAndSetUser} from "./helperModules/CheckIsSimklLoggedIn.svelte"
import Home from "./routes/Home.svelte"; import {CheckIfAniListLoggedIn} from "../wailsjs/go/main/App";
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,
GetMyAnimeList,
SimklGetUserWatchlist,
} from "../wailsjs/go/main/App";
import { loc } from "svelte-spa-router";
import ErrorModal from "./helperComponents/ErrorModal.svelte";
onMount(async () => { onMount(async () => {
let isAniListLoggedIn: boolean; let isAniListLoggedIn: boolean
let isMALLoggedIn: boolean; let isMALLoggedIn: boolean
let isSimklLoggedIn: boolean; let isSimklLoggedIn: boolean
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value)); aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
malLoggedIn.subscribe((value) => (isMALLoggedIn = value)); malLoggedIn.subscribe((value) => isMALLoggedIn = value)
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value)); simklLoggedIn.subscribe((value) => isSimklLoggedIn = value)
serviceLoggingIn.set(["anilist", "mal", "simkl"]); console.log(isAniListLoggedIn)
!isAniListLoggedIn && await CheckIfAniListLoggedInAndLoadWatchList()
!isAniListLoggedIn && (await CheckIfAniListLoggedInAndLoadWatchList()); !isMALLoggedIn && await CheckIfMALLoggedInAndSetUser()
!isMALLoggedIn && (await CheckIfMALLoggedInAndSetUser()); !isSimklLoggedIn && await CheckIfSimklLoggedInAndSetUser()
!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> </script>
<Header /> <Header />
<ErrorModal /> <Router routes={{
<Router '/': Home,
routes={{ '/anime/:id': wrap({
"/": Home, asyncComponent: () => import('./routes/AnimeRoutePage.svelte'),
"/anime/:id": wrap({ conditions: [async () => await CheckIfAniListLoggedIn()],
asyncComponent: () => import("./routes/AnimeRoutePage.svelte"), loadingComponent: Spinner
conditions: [async () => await CheckIfAniListLoggedIn()],
loadingComponent: Spinner,
}), }),
// '*': "Not Found" // '*': "Not Found"
}} }} />
/>
File diff suppressed because it is too large Load Diff
+143 -186
View File
@@ -1,206 +1,163 @@
<script lang="ts"> <script lang="ts">
import { Avatar } from "flowbite-svelte"; import { Avatar } from "flowbite-svelte";
import type { AniListUser } from "../anilist/types/AniListTypes"; import type { AniListUser } from "../anilist/types/AniListTypes";
import { import {
aniListLoggedIn, aniListLoggedIn,
aniListUser, aniListUser,
malUser, malUser,
simklUser, simklUser,
malLoggedIn, malLoggedIn,
simklLoggedIn, simklLoggedIn,
loginToAniList, loginToAniList,
loginToMAL, loginToMAL,
loginToSimkl, loginToSimkl,
logoutOfAniList, logoutOfAniList,
logoutOfMAL, logoutOfMAL,
logoutOfSimkl, logoutOfSimkl
serviceLoggingIn, } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"; import * as runtime from "../../wailsjs/runtime";
import * as runtime from "../../wailsjs/runtime"; import type {MyAnimeListUser} from "../mal/types/MALTypes";
import type { MyAnimeListUser } from "../mal/types/MALTypes"; import type {SimklUser} from "../simkl/types/simklTypes";
import type { SimklUser } from "../simkl/types/simklTypes"; import { ShowVersion } from "../../wailsjs/go/main/App";
import { ShowVersion } from "../../wailsjs/go/main/App";
let currentAniListUser: AniListUser; let currentAniListUser: AniListUser;
let currentMALUser: MyAnimeListUser; let currentMALUser: MyAnimeListUser;
let currentSimklUser: SimklUser; let currentSimklUser: SimklUser;
let isAniListLoggedIn: boolean; let isAniListLoggedIn: boolean;
let isSimklLoggedIn: boolean; let isSimklLoggedIn: boolean;
let isMALLoggedIn: boolean; let isMALLoggedIn: boolean;
let loggingIn: string[] = [];
aniListUser.subscribe((value) => (currentAniListUser = value)); aniListUser.subscribe((value) => (currentAniListUser = value));
malUser.subscribe((value) => (currentMALUser = value)); malUser.subscribe((value) => (currentMALUser = value))
simklUser.subscribe((value) => (currentSimklUser = value)); simklUser.subscribe(value => currentSimklUser = value)
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value)); aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value)); simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
malLoggedIn.subscribe((value) => (isMALLoggedIn = value)); malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
serviceLoggingIn.subscribe((value) => (loggingIn = value));
function dropdownUser(): void { function dropdownUser(): void {
let dropdown = document.querySelector("#userDropdown"); let dropdown = document.querySelector("#userDropdown");
dropdown.classList.toggle("hidden"); dropdown.classList.toggle("hidden");
if (!dropdown.classList.contains("hidden")) { if (!dropdown.classList.contains("hidden")) {
document.addEventListener("click", clickOutside); document.addEventListener("click", clickOutside)
}
} }
}
function clickOutside(event: Event): void { function clickOutside(event: Event): void {
let dropdown = document.querySelector("#userDropdown"); let dropdown = document.querySelector("#userDropdown")
let toggleBtn = document.querySelector("#userDropdownButton"); let toggleBtn = document.querySelector("#userDropdownButton")
if ( if (!dropdown.contains(event.target as Node) && !toggleBtn.contains(event.target as Node)) {
!dropdown.contains(event.target as Node) && dropdown.classList.add("hidden")
!toggleBtn.contains(event.target as Node) document.removeEventListener("click", clickOutside)
) { }
dropdown.classList.add("hidden");
document.removeEventListener("click", clickOutside);
} }
}
</script> </script>
<div class="relative"> <div class="relative">
<button id="userDropdownButton" on:click={dropdownUser}> <button id="userDropdownButton" on:click={dropdownUser}>
{#if isAniListLoggedIn} {#if isAniListLoggedIn}
<Avatar <Avatar
src={currentAniListUser.data.Viewer.avatar.medium} src={currentAniListUser.data.Viewer.avatar.medium}
class="cursor-pointer" class="cursor-pointer"
dot={isAniListLoggedIn && isMALLoggedIn && isSimklLoggedIn dot={{ color: "green" }}
? { color: "green" } />
: { color: "yellow" }} {:else}
/> <Avatar class="cursor-pointer" dot={{ color: "red" }} />
{:else} {/if}
<Avatar class="cursor-pointer" dot={{ color: "red" }} /> </button>
{/if} <div
</button> id="userDropdown"
<div class="absolute hidden right-0 2xl:left-1/2 2xl:-translate-x-1/2 z-10 divide-y rounded-lg shadow w-44 bg-gray-700 divide-gray-600"
id="userDropdown"
class="absolute hidden right-0 2xl:left-1/2 2xl:-translate-x-1/2 z-10 divide-y rounded-lg shadow w-44 bg-gray-700 divide-gray-600"
>
<div class="px-4 py-3 text-sm text-white">
{#if isAniListLoggedIn}
<div>{currentAniListUser.data.Viewer.name}</div>
{:else}
<div>You are not logged into AniList</div>
{/if}
</div>
<ul
class="py-2 text-sm text-gray-200"
aria-labelledby="dropdownUserAvatarButton"
> >
{#if isAniListLoggedIn} <div class="px-4 py-3 text-sm text-white">
<li> {#if isAniListLoggedIn}
<button <div>{currentAniListUser.data.Viewer.name}</div>
on:click={logoutOfAniList} {:else}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-green-800 hover:text-white" <div>You are not logged into AniList</div>
> {/if}
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser </div>
.data.Viewer.name} <ul
</button> class="py-2 text-sm text-gray-200"
</li> aria-labelledby="dropdownUserAvatarButton"
{:else} >
<li> {#if isAniListLoggedIn}
{#if loggingIn.includes("anilist")} <li>
<span class="flex items-center px-4 py-2 w-full truncate"> <button
<span on:click={logoutOfAniList}
class="inline-block w-4 h-4 mr-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin" class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-green-800 hover:text-white"
></span> >
<span class="maple-font text-lg mr-4">A</span>Checking AniList <span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser.data.Viewer.name}
</span> </button>
{:else} </li>
{:else}
<li>
<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>
{/if}
{#if isMALLoggedIn}
<li>
<button
on:click={logoutOfMAL}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-blue-800 hover:text-white"
>
<span class="maple-font text-lg text-blue-200 mr-4">M</span>Logout {currentMALUser.name}
</button>
</li>
{:else}
<li>
<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>
{/if}
{#if isSimklLoggedIn}
<li>
<button
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}
</button>
</li>
{:else}
<li>
<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 <button
on:click={() => { on:click={() => {
dropdownUser(); dropdownUser()
loginToAniList(); ShowVersion()
}} }}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white" class="block px-4 py-2 w-full text-sm hover:bg-gray-600 text-gray-200 over:text-white"
> >
<span class="maple-font text-lg mr-4">A</span>Login to AniList Version
</button> </button>
{/if}
</li>
{/if}
{#if isMALLoggedIn}
<li>
<button
on:click={logoutOfMAL}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-blue-800 hover:text-white"
>
<span class="maple-font text-lg text-blue-200 mr-4">M</span>Logout {currentMALUser.name}
</button>
</li>
{:else}
<li>
{#if loggingIn.includes("mal")}
<span class="flex items-center px-4 py-2 w-full truncate">
<span
class="inline-block w-4 h-4 mr-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin"
></span>
<span class="maple-font text-lg mr-4">M</span>Checking MyAnimeList
</span>
{:else}
<button <button
on:click={() => { on:click={() => runtime.Quit()}
dropdownUser(); class="block px-4 py-2 w-full text-sm hover:bg-gray-600 text-gray-200 over:text-white"
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 Exit Application
</button> </button>
{/if} </div>
</li>
{/if}
{#if isSimklLoggedIn}
<li>
<button
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}
</button>
</li>
{:else}
<li>
{#if loggingIn.includes("simkl")}
<span class="flex items-center px-4 py-2 w-full truncate">
<span
class="inline-block w-4 h-4 mr-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin"
></span>
<span class="maple-font text-lg mr-4">S</span>Checking Simkl
</span>
{:else}
<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>
{/if}
</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"
>
Exit Application
</button>
</div> </div>
</div> </div>
</div>
@@ -1,73 +0,0 @@
<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}
+9 -39
View File
@@ -7,7 +7,6 @@
loginToSimkl, loginToSimkl,
malLoggedIn, malLoggedIn,
simklLoggedIn, simklLoggedIn,
serviceLoggingIn,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"; } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import AvatarMenu from "./AvatarMenu.svelte"; import AvatarMenu from "./AvatarMenu.svelte";
import logo from "../assets/images/AniTrackLogo.svg"; import logo from "../assets/images/AniTrackLogo.svg";
@@ -16,12 +15,10 @@
let isAniListLoggedIn: boolean; let isAniListLoggedIn: boolean;
let isSimklLoggedIn: boolean; let isSimklLoggedIn: boolean;
let isMALLoggedIn: boolean; let isMALLoggedIn: boolean;
let loggingIn: string[] = [];
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value)); aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value)); simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
malLoggedIn.subscribe((value) => (isMALLoggedIn = value)); malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
serviceLoggingIn.subscribe((value) => (loggingIn = value));
</script> </script>
<nav class="border-gray-200 bg-gray-900"> <nav class="border-gray-200 bg-gray-900">
@@ -77,50 +74,23 @@
> >
<li> <li>
{#if !isAniListLoggedIn} {#if !isAniListLoggedIn}
<button <button on:click={loginToAniList}>
disabled={loggingIn.includes("anilist")} <!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
on:click={loginToAniList} AniList Login
>
{#if loggingIn.includes("anilist")}
<span
class="inline-block w-4 h-4 mr-2 border-2 border-gray-300 border-t-transparent rounded-full animate-spin align-middle"
></span
>Checking AniList
{:else}
AniList Login
{/if}
</button> </button>
{/if} {/if}
{#if !isMALLoggedIn} {#if !isMALLoggedIn}
<button <button on:click={loginToMAL}>
disabled={loggingIn.includes("mal")} <!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded min-[950px]:p-0 text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
on:click={loginToMAL} MyAnimeList Login
>
{#if loggingIn.includes("mal")}
<span
class="inline-block w-4 h-4 mr-2 border-2 border-gray-300 border-t-transparent rounded-full animate-spin align-middle"
></span
>Checking MAL
{:else}
MyAnimeList Login
{/if}
</button> </button>
{/if} {/if}
</li> </li>
<li> <li>
{#if !isSimklLoggedIn} {#if !isSimklLoggedIn}
<button <button on:click={loginToSimkl}>
disabled={loggingIn.includes("simkl")} <!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded min-[950px]:p-0 text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
on:click={loginToSimkl} Simkl Login
>
{#if loggingIn.includes("simkl")}
<span
class="inline-block w-4 h-4 mr-2 border-2 border-gray-300 border-t-transparent rounded-full animate-spin align-middle"
></span
>Checking Simkl
{:else}
Simkl Login
{/if}
</button> </button>
{/if} {/if}
</li> </li>
+128 -203
View File
@@ -1,220 +1,145 @@
<script lang="ts"> <script lang="ts">
import { import {
aniListLoggedIn, aniListLoggedIn,
aniListSort, aniListWatchlist,
aniListWatchlist, animePerPage,
animePerPage, watchListPage,
watchListPage, } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
} 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 {GetAniListUserWatchingList} from "../../wailsjs/go/main/App";
import {MediaListSort} from "../anilist/types/AniListTypes";
let aniListWatchListLoaded: AniListCurrentUserWatchList; let aniListWatchListLoaded: AniListCurrentUserWatchList
let page: number; let page: number
let perPage: number; let perPage: number
let sort: string;
watchListPage.subscribe((value) => (page = value)); watchListPage.subscribe(value => page = value)
animePerPage.subscribe((value) => (perPage = value)); animePerPage.subscribe(value => perPage = value)
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = 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) { function ChangeWatchListPage(newPage: number) {
GetAniListUserWatchingList(newPage, perPage, sort).then((result) => { GetAniListUserWatchingList(newPage, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
watchListPage.set(newPage); watchListPage.set(newPage)
aniListWatchlist.set(result); aniListWatchlist.set(result)
aniListLoggedIn.set(true); aniListLoggedIn.set(true)
}); })
} }
function changePage( function changePage(e): void {
e: KeyboardEvent & { currentTarget: HTMLInputElement }, if ((e.key === "Enter" || e.key === "Tab") && Number(e.target.value) !== page) ChangeWatchListPage(Number(e.target.value))
): void { }
if (
(e.key === "Enter" || e.key === "Tab") && function changeCountPerPage(e): void {
Number(e.currentTarget.value) !== page GetAniListUserWatchingList(1, Number(e.target.value), MediaListSort.UpdatedTimeDesc).then((result) => {
) animePerPage.set(Number(e.target.value))
ChangeWatchListPage(Number(e.currentTarget.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> </script>
<div class="mb-8"> <div class="mb-8">
{#if aniListWatchListLoaded.data.Page.pageInfo.lastPage <= 12} {#if aniListWatchListLoaded.data.Page.pageInfo.lastPage <= 12}
<nav aria-label="Page navigation" class="hidden md:block"> <nav aria-label="Page navigation" class="hidden md:block">
<ul class="inline-flex -space-x-px text-base h-10"> <ul class="inline-flex -space-x-px text-base h-10">
{#if page === 1} {#if page === 1}
<li> <li>
<button <button disabled
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">
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>
Previous </li>
</button> {:else}
</li> <li>
{:else} <button on:click={() => ChangeWatchListPage(page-1)}
<li> 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 Previous
on:click={() => ChangeWatchListPage(page - 1)} </button>
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" </li>
> {/if}
Previous {#each {length: aniListWatchListLoaded.data.Page.pageInfo.lastPage} as _, i}
</button> {#if i + 1 === page}
</li> <li>
{/if} <button on:click={() => ChangeWatchListPage(i+1)}
{#each { length: aniListWatchListLoaded.data.Page.pageInfo.lastPage } as _, i} 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>
{#if i + 1 === page} </li>
<li> {:else}
<button <li>
on:click={() => ChangeWatchListPage(i + 1)} <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" 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>
>{i + 1}</button </li>
> {/if}
</li> {/each}
{:else} {#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage}
<li> <li>
<button <button disabled
on:click={() => ChangeWatchListPage(i + 1)} 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">
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" Next
>{i + 1}</button </button>
> </li>
</li> {:else}
{/if} <li>
{/each} <button on:click={() => ChangeWatchListPage(page+1)}
{#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage} 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">
<li> Next
<button </button>
disabled </li>
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" {/if}
> </ul>
Next </nav>
</button> {/if}
</li> <div class="flex mt-5">
{:else} <div class="w-20 mx-auto">
<li> <select bind:value={perPage} on:change={(e) => changeCountPerPage(e)} id="countPerPage"
<button 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">
on:click={() => ChangeWatchListPage(page + 1)} {#each perPageOptions as option}
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" <option value={option}>
> {option}
Next </option>
</button> {/each}
</li> </select>
{/if}
</ul>
</nav>
{/if}
<div class="flex">
<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"
>
{#each perPageOptions as option}
<option value={option}>
{option}
</option>
{/each}
</select>
</div>
<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>
{:else}
<div> <div>
Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage} <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>
{:else}
<div>Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
{/if}
</div> </div>
{/if}
</div>
<div class="max-w-xs mx-auto"> <div class="max-w-xs mx-auto">
<div class="relative flex items-center max-w-[11rem]"> <div class="relative flex items-center max-w-[11rem]">
<button <button type="button" id="decrement-button" on:click={() => ChangeWatchListPage(page-1)}
type="button" 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">
id="decrement-button" <svg class="w-3 h-3 text-white" aria-hidden="true"
on:click={() => ChangeWatchListPage(page - 1)} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
class={page <= 1 <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
? "border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none" d="M1 1h16"/>
: "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>
disabled={page <= 1} </button>
> <input type="number" min="1" max="{aniListWatchListLoaded.data.Page.pageInfo.lastPage}"
<svg on:keydown={changePage} id="page-counter"
class="w-3 h-3 text-white" 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"
aria-hidden="true" value={page} required/>
xmlns="http://www.w3.org/2000/svg" <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">
fill="none" <span>Page #</span>
viewBox="0 0 18 2" </div>
> <button type="button" id="increment-button" on:click={() => ChangeWatchListPage(page+1)}
<path 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">
stroke="currentColor" <svg class="w-3 h-3 text-white" aria-hidden="true"
stroke-linecap="round" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
stroke-linejoin="round" <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
stroke-width="2" d="M9 1v16M1 9h16"/>
d="M1 1h16" </svg>
/> </button>
</svg> </div>
</button>
<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"
>
<span>Page #</span>
</div> </div>
<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>
</div> </div>
</div> </div>
</div>
@@ -1,18 +0,0 @@
<script lang="ts">
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import { loading } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
</script>
<div class="flex justify-end">
<button
type="button"
class="py-2 px-4 mt-4 mr-4 bg-gray-700 rounded-lg"
on:click={async () => {
loading.set(true);
await CheckIfAniListLoggedInAndLoadWatchList();
loading.set(false);
}}
>
Refresh WatchList
</button>
</div>
-77
View File
@@ -1,77 +0,0 @@
<script lang="ts">
import {
aniListSort,
watchListPage,
animePerPage,
aniListWatchlist,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import { MediaListSort } from "../anilist/types/AniListTypes";
import { GetAniListUserWatchingList } from "../../wailsjs/go/main/App";
const sortTypes = [
{ value: MediaListSort.MediaId, name: "Media Id Asc" },
{ value: MediaListSort.MediaIdDesc, name: "Media Id Desc" },
{ value: MediaListSort.Score, name: "Score Asc" },
{ value: MediaListSort.ScoreDesc, name: "Score Desc" },
{ value: MediaListSort.Status, name: "Status Asc" },
{ value: MediaListSort.StatusDesc, name: "Status Desc" },
{ value: MediaListSort.Progress, name: "Progress Asc" },
{ value: MediaListSort.ProgressDesc, name: "Progress Desc" },
{ value: MediaListSort.ProgressVolumes, name: "Progress Valumes Asc" },
{ value: MediaListSort.ProgressVolumesDesc, name: "Progress Valumes Desc" },
{ value: MediaListSort.Repeat, name: "Repeat Asc" },
{ value: MediaListSort.RepeatDesc, name: "Repeat Desc" },
{ value: MediaListSort.Priority, name: "Priority Asc" },
{ value: MediaListSort.PriorityDesc, name: "Priority Desc" },
{ value: MediaListSort.StartedOn, name: "Started On Asc" },
{ value: MediaListSort.StartedOnDesc, name: "Started On Desc" },
{ value: MediaListSort.FinishedOn, name: "Finished On Asc" },
{ value: MediaListSort.FinishedOnDesc, name: "Finished On Desc" },
{ value: MediaListSort.AddedTime, name: "Added Time Asc" },
{ value: MediaListSort.AddedTimeDesc, name: "Added Time Desc" },
{ value: MediaListSort.UpdatedTime, name: "Updated Time Asc" },
{ value: MediaListSort.UpdatedTimeDesc, name: "Updated Time Desc" },
{ value: MediaListSort.MediaTitleRomaji, name: "Media Title Romaji Asc" },
{
value: MediaListSort.MediaTitleRomajiDesc,
name: "Media Title Romaji Desc",
},
{ value: MediaListSort.MediaTitleEnglish, name: "Media Title English Asc" },
{
value: MediaListSort.MediaTitleEnglishDesc,
name: "Media Title English Desc",
},
{ value: MediaListSort.MediaTitleNative, name: "Media Title Native Asc" },
{
value: MediaListSort.MediaTitleNativeDesc,
name: "Media Title Native Desc",
},
{ value: MediaListSort.MediaPopularity, name: "Media Popularity Asc" },
{ value: MediaListSort.MediaPopularityDesc, name: "Media Popularity Desc" },
];
let sort: string;
aniListSort.subscribe((value) => (sort = value));
console.log(sort);
async function changeWatchListSort() {
const result = await GetAniListUserWatchingList(
$watchListPage,
$animePerPage,
$aniListSort,
);
aniListWatchlist.set(result);
}
</script>
<select
id="sort"
class="border rounded-lg block p-1.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
bind:value={$aniListSort}
on:change={() => changeWatchListSort()}
>
<option value="" disabled>Sort WatchList</option>
{#each sortTypes as sort}
<option value={sort.value}>{sort.name}</option>
{/each}
</select>
+59 -81
View File
@@ -1,90 +1,68 @@
<script lang="ts"> <script lang="ts">
import { import {
aniListLoggedIn, aniListLoggedIn,
aniListWatchlist, aniListWatchlist,
GetAnimeSingleItem, GetAnimeSingleItem,
loading, loading,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"; } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import { push } from "svelte-spa-router"; import {push} from "svelte-spa-router";
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType"; import type {AniListCurrentUserWatchList} from "../anilist/types/AniListCurrentUserWatchListType"
import { Rating } from "flowbite-svelte"; import {Rating} from "flowbite-svelte";
import loader from "../helperFunctions/loader"; import loader from '../helperFunctions/loader'
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import Sort from "../helperComponents/Sort.svelte";
let isAniListLoggedIn: boolean;
let aniListWatchListLoaded: AniListCurrentUserWatchList;
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value)); let isAniListLoggedIn: boolean
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = value)); let aniListWatchListLoaded: AniListCurrentUserWatchList
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
aniListWatchlist.subscribe((value) => aniListWatchListLoaded = value)
</script> </script>
<div> <div>
{#if isAniListLoggedIn} {#if isAniListLoggedIn}
<div <div class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center">
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="flex justify-between items-center mb-4">
<h1 class="text-left text-xl font-bold">Your AniList WatchList</h1>
<Sort />
</div>
<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">
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">
{#each aniListWatchListLoaded.data.Page.mediaList as media} <div class="flex flex-col items-center group">
<div <button on:click={() => {
use:loader={loading} push(`#/anime/${media.media.id}`)
class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7" // loading.set(true)
> // GetAniListSingleItem(media.media.id, true).then(() => {
<div class="flex flex-col items-center group"> // loading.set(false)
<button //
on:click={() => { // })
push(`#/anime/${media.media.id}`); }}
// loading.set(true) >
// GetAniListSingleItem(media.media.id, true).then(() => { <img class="rounded-lg" src={media.media.coverImage.large} alt={
// loading.set(false) media.media.title.english === "" ?
// media.media.title.romaji :
// }) media.media.title.english
}} }/>
> </button>
<img <Rating id="anime-rating" total={5} size={35} rating={media.score/2.0}/>
class="rounded-lg w-[230px] h-[330px] object-cover" <button class="mt-4 text-md font-semibold text-white-700"
src={media.media.coverImage.large} on:click={() => GetAnimeSingleItem(media.media.id, true)}>
alt={media.media.title.english === "" {
? media.media.title.romaji media.media.title.english === "" ?
: media.media.title.english} media.media.title.romaji :
/> media.media.title.english
</button> }
<Rating </button>
id="anime-rating" <p class="mt-1 text-lg font-medium text-white-900">{media.progress}
total={5} / {media.media.nextAiringEpisode.episode !== 0 ?
size={35} media.media.nextAiringEpisode.episode - 1 : media.media.episodes}</p>
rating={media.score / 2.0} {#if media.media.episodes > 0}
/> <p class="mt-1 text-lg font-medium text-white-900">Total
<button Episodes: {media.media.episodes}</p>
class="mt-4 text-md font-semibold text-white-700" {/if}
on:click={() => GetAnimeSingleItem(media.media.id, true)} </div>
> </div>
{media.media.title.english === "" {/each}
? 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>
{#if media.media.episodes > 0}
<p class="mt-1 text-lg font-medium text-white-900">
Total Episodes: {media.media.episodes}
</p>
{/if}
</div> </div>
</div> </div>
{/each} {/if}
</div>
</div>
{/if}
</div> </div>
@@ -1,85 +1,34 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import { import {CheckIfAniListLoggedIn, GetAniListLoggedInUser, GetAniListUserWatchingList} from "../../wailsjs/go/main/App";
CheckIfAniListLoggedIn, import {MediaListSort} from "../anilist/types/AniListTypes";
GetAniListLoggedInUser, import { aniListUser, watchListPage, animePerPage, aniListPrimary, aniListLoggedIn, aniListWatchlist } from "./GlobalVariablesAndHelperFunctions.svelte"
GetAniListUserWatchingList,
} from "../../wailsjs/go/main/App";
import {
aniListUser,
watchListPage,
animePerPage,
aniListPrimary,
aniListLoggedIn,
aniListWatchlist,
aniListSort,
clearApiError,
setApiError,
serviceLoggingIn,
} from "./GlobalVariablesAndHelperFunctions.svelte";
let isAniListPrimary: boolean; let isAniListPrimary: boolean
let page: number; let page: number
let perPage: number; let perPage: number
let sort: string;
aniListPrimary.subscribe((value) => (isAniListPrimary = value)); aniListPrimary.subscribe(value => isAniListPrimary = value)
watchListPage.subscribe((value) => (page = value)); watchListPage.subscribe(value => page = value)
animePerPage.subscribe((value) => (perPage = value)); animePerPage.subscribe(value => perPage = value)
aniListSort.subscribe((value) => (sort = value));
export const LoadAniListUser = async () => { export const LoadAniListUser = async () => {
try { await GetAniListLoggedInUser().then(user => {
await GetAniListLoggedInUser().then((user) => { aniListUser.set(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 () => { export const LoadAniListWatchList = async () => {
try { await GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((watchList) => {
const watchList = await GetAniListUserWatchingList(page, perPage, sort); aniListWatchlist.set(watchList)
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 () => { export const CheckIfAniListLoggedInAndLoadWatchList = async () => {
serviceLoggingIn.update((s) => [...s, "anilist"]); const loggedIn = await CheckIfAniListLoggedIn()
try { if (loggedIn) {
const loggedIn = await CheckIfAniListLoggedIn(); await LoadAniListUser()
if (loggedIn) { 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);
} finally {
serviceLoggingIn.update((s) => s.filter((item) => item !== "anilist"));
} }
}; </script>
</script>
@@ -1,21 +1,14 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import {CheckIfMyAnimeListLoggedIn, GetMyAnimeList, GetMyAnimeListLoggedInUser} from "../../wailsjs/go/main/App"; import {CheckIfMyAnimeListLoggedIn, GetMyAnimeList, GetMyAnimeListLoggedInUser} from "../../wailsjs/go/main/App";
import {malUser, malPrimary, malWatchList, malLoggedIn, serviceLoggingIn} from "./GlobalVariablesAndHelperFunctions.svelte" import {malUser, malPrimary, malWatchList, malLoggedIn} from "./GlobalVariablesAndHelperFunctions.svelte"
import type { MyAnimeListUser } from "../mal/types/MALTypes";
let isMalPrimary: boolean let isMalPrimary: boolean
malPrimary.subscribe(value => isMalPrimary = value) malPrimary.subscribe(value => isMalPrimary = value)
export const CheckIfMALLoggedInAndSetUser = async () => { export const CheckIfMALLoggedInAndSetUser = async () => {
serviceLoggingIn.update((s) => [...s, "mal"])
await CheckIfMyAnimeListLoggedIn().then(loggedIn => { await CheckIfMyAnimeListLoggedIn().then(loggedIn => {
if (loggedIn) { if (loggedIn) {
GetMyAnimeListLoggedInUser().then(user => { GetMyAnimeListLoggedInUser().then(user => {
if (!user.name) {
malUser.set({} as MyAnimeListUser)
malLoggedIn.set(false)
return
}
malUser.set(user) malUser.set(user)
if (isMalPrimary) { if (isMalPrimary) {
GetMyAnimeList(1000).then(watchList => { GetMyAnimeList(1000).then(watchList => {
@@ -27,8 +20,6 @@
} }
}) })
} }
}).finally(() => {
serviceLoggingIn.update((s) => s.filter(item => item !== "mal"))
}) })
} }
</script> </script>
@@ -1,12 +1,11 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import {CheckIfSimklLoggedIn, GetSimklLoggedInUser, SimklGetUserWatchlist} from "../../wailsjs/go/main/App"; import {CheckIfSimklLoggedIn, GetSimklLoggedInUser, SimklGetUserWatchlist} from "../../wailsjs/go/main/App";
import { simklLoggedIn, simklUser, simklPrimary, simklWatchList, serviceLoggingIn } from "./GlobalVariablesAndHelperFunctions.svelte"; import { simklLoggedIn, simklUser, simklPrimary, simklWatchList } from "./GlobalVariablesAndHelperFunctions.svelte";
let isSimklPrimary: boolean let isSimklPrimary: boolean
simklPrimary.subscribe(value => isSimklPrimary = value) simklPrimary.subscribe(value => isSimklPrimary = value)
export const CheckIfSimklLoggedInAndSetUser = async () => { export const CheckIfSimklLoggedInAndSetUser = async () => {
serviceLoggingIn.update((s) => [...s, "simkl"])
await CheckIfSimklLoggedIn().then(loggedIn => { await CheckIfSimklLoggedIn().then(loggedIn => {
if (loggedIn) { if (loggedIn) {
GetSimklLoggedInUser().then(user => { GetSimklLoggedInUser().then(user => {
@@ -25,8 +24,6 @@
} }
}) })
} }
}).finally(() => {
serviceLoggingIn.update((s) => s.filter(item => item !== "simkl"))
}) })
} }
</script> </script>
@@ -1,244 +1,162 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import { import {
GetAniListItem, GetAniListItem,
GetAniListLoggedInUser, GetAniListLoggedInUser,
GetAniListUserWatchingList, GetAniListUserWatchingList,
GetMyAnimeListAnime, GetMyAnimeListAnime,
GetMyAnimeListLoggedInUser, GetMyAnimeListLoggedInUser,
GetSimklLoggedInUser, GetSimklLoggedInUser,
LogoutAniList, LogoutAniList,
LogoutMyAnimeList, LogoutMyAnimeList,
LogoutSimkl, LogoutSimkl,
SimklGetUserWatchlist, SimklGetUserWatchlist,
SimklSearch, SimklSearch
} from "../../wailsjs/go/main/App"; } from "../../wailsjs/go/main/App";
import type { import type {
AniListCurrentUserWatchList, AniListCurrentUserWatchList,
AniListGetSingleAnime, AniListGetSingleAnime
} from "../anilist/types/AniListCurrentUserWatchListType.js"; } from "../anilist/types/AniListCurrentUserWatchListType.js";
import { writable } from "svelte/store"; import {writable} from 'svelte/store'
import type { import type {SimklAnime, SimklUser, SimklWatchList} from "../simkl/types/simklTypes";
SimklAnime, import {type AniListUser, MediaListSort} from "../anilist/types/AniListTypes";
SimklUser, import type {MALAnime, MALWatchlist, MyAnimeListUser} from "../mal/types/MALTypes";
SimklWatchList, import type {TableItems} from "../helperTypes/TableTypes";
} from "../simkl/types/simklTypes"; import {AniListGetSingleAnimeDefaultData} from "../helperDefaults/AniListGetSingleAnime";
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 aniListAnime = writable(AniListGetSingleAnimeDefaultData)
export let title = writable(""); export let title = writable("")
export let aniListLoggedIn = writable(false); export let aniListLoggedIn = writable(false)
export let simklLoggedIn = writable(false); export let simklLoggedIn = writable(false)
export let malLoggedIn = writable(false); export let malLoggedIn = writable(false)
export const serviceLoggingIn = writable([] as string[]); export let simklWatchList = writable({} as SimklWatchList)
export let simklWatchList = writable({} as SimklWatchList); export let aniListPrimary = writable(true)
export let aniListPrimary = writable(true); export let simklPrimary = writable(false)
export let simklPrimary = writable(false); export let malPrimary = writable(false)
export let malPrimary = writable(false); export let simklUser = writable({} as SimklUser)
export let simklUser = writable({} as SimklUser); export let aniListUser = writable({} as AniListUser)
export let aniListUser = writable({} as AniListUser); export let malUser = writable({} as MyAnimeListUser)
export let malUser = writable({} as MyAnimeListUser); export let aniListWatchlist = writable({} as AniListCurrentUserWatchList)
export let aniListWatchlist = writable({} as AniListCurrentUserWatchList); export let malWatchList = writable({} as MALWatchlist)
export let malWatchList = writable({} as MALWatchlist); export let malAnime = writable({} as MALAnime)
export let malAnime = writable({} as MALAnime); export let simklAnime = writable({} as SimklAnime)
export let simklAnime = writable({} as SimklAnime); export let loading = writable(false)
export let loading = writable(false); export let tableItems = writable([] as TableItems)
export let tableItems = writable([] as TableItems);
export let watchlistNeedsRefresh = writable(false);
export let aniListSort = writable(MediaListSort.UpdatedTimeDesc);
export let watchListPage = writable(1); export let watchListPage = writable(1)
export let animePerPage = writable(20); export let animePerPage = writable(20)
let isAniListPrimary: boolean; let isAniListPrimary: boolean
let page: number; let page: number
let perPage: number; let perPage: number
let sort: string; let aniWatchlist: AniListCurrentUserWatchList
let aniWatchlist: AniListCurrentUserWatchList; let currentAniListAnime: AniListGetSingleAnime
let currentAniListAnime: AniListGetSingleAnime;
let isMalLoggedIn: boolean; let isMalLoggedIn: boolean
let isSimklLoggedIn: boolean; let isSimklLoggedIn: boolean
aniListPrimary.subscribe((value) => (isAniListPrimary = value)); aniListPrimary.subscribe(value => isAniListPrimary = value)
watchListPage.subscribe((value) => (page = value)); watchListPage.subscribe(value => page = value)
animePerPage.subscribe((value) => (perPage = value)); animePerPage.subscribe(value => perPage = value)
aniListWatchlist.subscribe((value) => (aniWatchlist = value)); aniListWatchlist.subscribe(value => aniWatchlist = value)
malLoggedIn.subscribe((value) => (isMalLoggedIn = value)); malLoggedIn.subscribe(value => isMalLoggedIn = value)
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value)); simklLoggedIn.subscribe(value => isSimklLoggedIn = value)
aniListAnime.subscribe((value) => (currentAniListAnime = value)); aniListAnime.subscribe(value => currentAniListAnime = value)
aniListSort.subscribe((value) => (sort = value));
export interface ApiError {
service: string;
message: string;
statusCode?: string;
canRetry: boolean;
}
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( export async function GetAnimeSingleItem(aniId: number, login: boolean): Promise<""> {
aniId: number, await GetAniListItem(aniId, login).then(aniListResult => {
login: boolean, let finalResult: AniListGetSingleAnime
): Promise<""> { finalResult = aniListResult
await GetAniListItem(aniId, login).then((aniListResult) => { if (login === false) {
let finalResult: AniListGetSingleAnime; finalResult.data.MediaList.status = ""
finalResult = aniListResult; finalResult.data.MediaList.score = 0
if (login === false) { finalResult.data.MediaList.progress = 0
finalResult.data.MediaList.status = ""; finalResult.data.MediaList.notes = ""
finalResult.data.MediaList.score = 0; finalResult.data.MediaList.repeat = 0
finalResult.data.MediaList.progress = 0; finalResult.data.MediaList.startedAt.day = 0
finalResult.data.MediaList.notes = ""; finalResult.data.MediaList.startedAt.month = 0
finalResult.data.MediaList.repeat = 0; finalResult.data.MediaList.startedAt.year = 0
finalResult.data.MediaList.startedAt.day = 0; finalResult.data.MediaList.completedAt.day = 0
finalResult.data.MediaList.startedAt.month = 0; finalResult.data.MediaList.completedAt.month = 0
finalResult.data.MediaList.startedAt.year = 0; finalResult.data.MediaList.completedAt.year = 0
finalResult.data.MediaList.completedAt.day = 0; }
finalResult.data.MediaList.completedAt.month = 0; aniListAnime.set(finalResult)
finalResult.data.MediaList.completedAt.year = 0; title.set(currentAniListAnime.data.MediaList.media.title.english === "" ?
} currentAniListAnime.data.MediaList.media.title.romaji :
aniListAnime.set(finalResult); currentAniListAnime.data.MediaList.media.title.english)
title.set( })
currentAniListAnime.data.MediaList.media.title.english === "" if (isMalLoggedIn) {
? currentAniListAnime.data.MediaList.media.title.romaji await GetMyAnimeListAnime(currentAniListAnime.data.MediaList.media.idMal).then(malResult => {
: currentAniListAnime.data.MediaList.media.title.english, malAnime.set(malResult)
); })
}); }
if (isMalLoggedIn) { if (isSimklLoggedIn) {
await GetMyAnimeListAnime( await SimklSearch(currentAniListAnime.data.MediaList).then((value: SimklAnime) => {
currentAniListAnime.data.MediaList.media.idMal, simklAnime.set(value)
).then((malResult) => { })
malAnime.set(malResult); }
}); return ""
} }
if (isSimklLoggedIn) {
await SimklSearch(currentAniListAnime.data.MediaList).then( export function loginToSimkl(): void {
(value: SimklAnime) => { GetSimklLoggedInUser().then(user => {
simklAnime.set(value); if (Object.keys(user).length === 0) {
}, simklLoggedIn.set(false)
); } else {
simklUser.set(user)
SimklGetUserWatchlist().then(result => {
simklWatchList.set(result)
simklLoggedIn.set(true)
})
}
})
} }
return "";
}
export function setServiceLoggingIn(service: string, isLoggingIn: boolean): void { export function loginToAniList(): void {
serviceLoggingIn.update((services) => { GetAniListLoggedInUser().then(result => {
if (isLoggingIn) { aniListUser.set(result)
return services.includes(service) ? services : [...services, service]; if (isAniListPrimary) {
} GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
return services.filter((s) => s !== service); aniListWatchlist.set(result)
}); aniListLoggedIn.set(true)
} })
} else {
aniListLoggedIn.set(true)
}
})
}
export function isServiceLoggingIn(service: string): boolean { export function loginToMAL(): void {
let loggingIn = false; GetMyAnimeListLoggedInUser().then(result => {
serviceLoggingIn.subscribe((services) => { malUser.set(result)
loggingIn = services.includes(service); malLoggedIn.set(true)
})(); })
return loggingIn; }
}
export function loginToSimkl(): void { export function logoutOfAniList(): void {
setServiceLoggingIn("simkl", true); LogoutAniList().then(result => {
GetSimklLoggedInUser() console.log(result)
.then((user) => { if (Object.keys(aniWatchlist).length !== 0) {
if (Object.keys(user).length === 0) { aniListWatchlist.set({} as AniListCurrentUserWatchList)
simklLoggedIn.set(false); }
} else { aniListUser.set({} as AniListUser)
simklUser.set(user); aniListLoggedIn.set(false)
SimklGetUserWatchlist().then((result) => { })
simklWatchList.set(result); }
simklLoggedIn.set(true);
});
}
})
.finally(() => setServiceLoggingIn("simkl", false));
}
export function loginToAniList(): void { export function logoutOfMAL(): void {
setServiceLoggingIn("anilist", true); LogoutMyAnimeList().then(result => {
GetAniListLoggedInUser() console.log(result)
.then((result) => { malUser.set({} as MyAnimeListUser)
aniListUser.set(result); malLoggedIn.set(false)
if (isAniListPrimary) { })
GetAniListUserWatchingList(page, perPage, sort).then((result) => { }
aniListWatchlist.set(result);
aniListLoggedIn.set(true);
});
} else {
aniListLoggedIn.set(true);
}
})
.finally(() => setServiceLoggingIn("anilist", false));
}
export function loginToMAL(): void { export function logoutOfSimkl(): void {
setServiceLoggingIn("mal", true); LogoutSimkl().then(result => {
GetMyAnimeListLoggedInUser() console.log(result)
.then((result) => { simklUser.set({} as SimklUser)
if (!result.name) { simklLoggedIn.set(false)
malUser.set({} as MyAnimeListUser); })
malLoggedIn.set(false); }
return; </script>
}
malUser.set(result);
malLoggedIn.set(true);
})
.finally(() => setServiceLoggingIn("mal", false));
}
export function logoutOfAniList(): void {
LogoutAniList().then((result) => {
console.log(result);
if (Object.keys(aniWatchlist).length !== 0) {
aniListWatchlist.set({} as AniListCurrentUserWatchList);
}
aniListUser.set({} as AniListUser);
aniListLoggedIn.set(false);
});
}
export function logoutOfMAL(): void {
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);
});
}
</script>
+14 -46
View File
@@ -1,57 +1,25 @@
<script lang="ts"> <script lang="ts">
import Pagination from "../helperComponents/Pagination.svelte"; import Pagination from "../helperComponents/Pagination.svelte";
import WatchList from "../helperComponents/WatchList.svelte"; import WatchList from "../helperComponents/WatchList.svelte";
import RefreshWatchListButton from "../helperComponents/RefreshWatchListButton.svelte"; import {
import {
aniListLoggedIn, aniListLoggedIn,
aniListPrimary, aniListPrimary,
loading, loading,
isApiDown, } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
apiError, import loader from '../helperFunctions/loader'
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import loader from "../helperFunctions/loader";
let isAniListPrimary: boolean; let isAniListPrimary: boolean
let isAniListLoggedIn: boolean; let isAniListLoggedIn: boolean
aniListPrimary.subscribe((value) => (isAniListPrimary = value)); aniListPrimary.subscribe((value) => isAniListPrimary = value)
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value)); aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
</script> </script>
{#if isAniListLoggedIn && isAniListPrimary}
{#if $isApiDown} <div class="container py-10">
<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}
<RefreshWatchListButton />
<div class="container py-10">
<Pagination /> <Pagination />
<WatchList /> <WatchList />
<Pagination /> <Pagination />
</div> </div>
{:else} {:else}
<div use:loader={loading}></div> <div use:loader={loading}></div>
{/if} {/if}
@@ -0,0 +1,10 @@
// 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
View File
@@ -2,8 +2,6 @@
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
import {main} from '../models'; import {main} from '../models';
export function AniListBrowse(arg1:number,arg2:number,arg3:number,arg4:boolean,arg5:string,arg6:Array<string>,arg7:string,arg8:string,arg9:string,arg10:string,arg11:number,arg12:string,arg13:boolean,arg14:number,arg15:number,arg16:number,arg17:number,arg18:number,arg19:number,arg20:number,arg21:number,arg22:number,arg23:number,arg24:Array<number>,arg25:boolean,arg26:Array<string>,arg27:Array<string>,arg28:Array<string>,arg29:Array<string>,arg30:number,arg31:Array<string>):Promise<main.AniListCurrentUserWatchList>;
export function AniListDeleteEntry(arg1:number):Promise<main.DeleteAniListReturn>; export function AniListDeleteEntry(arg1:number):Promise<main.DeleteAniListReturn>;
export function AniListLogin():Promise<void>; export function AniListLogin():Promise<void>;
-4
View File
@@ -2,10 +2,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
export function AniListBrowse(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31) {
return window['go']['main']['App']['AniListBrowse'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31);
}
export function AniListDeleteEntry(arg1) { export function AniListDeleteEntry(arg1) {
return window['go']['main']['App']['AniListDeleteEntry'](arg1); return window['go']['main']['App']['AniListDeleteEntry'](arg1);
} }
+5 -178
View File
@@ -230,7 +230,8 @@ export namespace main {
background: background; background: background;
related_anime: relatedAnime; related_anime: relatedAnime;
recommendations: recommendations; recommendations: recommendations;
Statistics: struct { NumListUsers int "json:\"num_list_users\" ts_type:\"numListUsers\""; Status struct { Watching main.; // Go type: struct { NumListUsers int "json:\"num_list_users\" ts_type:\"numListUsers\""; Status struct { Watching string "json:\"watching\" ts_type:\"watching\""; Completed string "json:\"completed\" ts_type:\"completed\""; OnHold string "json:\"on_hold\" ts_type:\"onHold\""; Dropped string "json:\"dropped\" ts_type:\"dropped\""; PlanToWatch string "json:\"plan_to_watch\" ts_type:\"planToWatch\"" } }
Statistics: any;
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
return new MALAnime(source); return new MALAnime(source);
@@ -359,125 +360,13 @@ export namespace main {
this.updated_at = source["updated_at"]; this.updated_at = source["updated_at"];
} }
} }
export class {
id: number;
name: string;
description: string;
rank: number;
isMediaSpoiler: boolean;
isAdult: boolean;
static createFrom(source: any = {}) {
return new (source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.name = source["name"];
this.description = source["description"];
this.rank = source["rank"];
this.isMediaSpoiler = source["isMediaSpoiler"];
this.isAdult = source["isAdult"];
}
}
export class Media {
id: number;
idMal: number;
// Go type: struct { UserPreferred string "json:\"userPreferred\""; Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" }
title: any;
description: string;
// Go type: struct { ExtraLarge string; Large string "json:\"large\""; Medium string; Color string }
coverImage: any;
BannerImage: string;
Format: string;
season: string;
seasonYear: number;
status: string;
episodes: number;
Duration: number;
CountryOfOrigin: string;
Source: string;
Synonyms: string[];
AverageScore: number;
MeanScore: number;
Popularity: number;
Trending: number;
Favourites: number;
// Go type: struct { nodes struct { id int; Title struct { UserPreferred string "json:\"userPreferred\""; Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\"" } }
Relations: any;
// Go type: struct { Year int; Month int; Day int }
StartDate: any;
// Go type: struct { Year int; Month int; Day int }
EndDate: any;
// Go type: struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" }
nextAiringEpisode: any;
// Go type: struct { Nodes struct { Id int; AiringAt int; TimeUntilAiring int; Episode int; MediaId int } }
AiringSchedule: any;
genres: string[];
tags: [];
isAdult: boolean;
static createFrom(source: any = {}) {
return new Media(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.idMal = source["idMal"];
this.title = this.convertValues(source["title"], Object);
this.description = source["description"];
this.coverImage = this.convertValues(source["coverImage"], Object);
this.BannerImage = source["BannerImage"];
this.Format = source["Format"];
this.season = source["season"];
this.seasonYear = source["seasonYear"];
this.status = source["status"];
this.episodes = source["episodes"];
this.Duration = source["Duration"];
this.CountryOfOrigin = source["CountryOfOrigin"];
this.Source = source["Source"];
this.Synonyms = source["Synonyms"];
this.AverageScore = source["AverageScore"];
this.MeanScore = source["MeanScore"];
this.Popularity = source["Popularity"];
this.Trending = source["Trending"];
this.Favourites = source["Favourites"];
this.Relations = this.convertValues(source["Relations"], Object);
this.StartDate = this.convertValues(source["StartDate"], Object);
this.EndDate = this.convertValues(source["EndDate"], Object);
this.nextAiringEpisode = this.convertValues(source["nextAiringEpisode"], Object);
this.AiringSchedule = this.convertValues(source["AiringSchedule"], Object);
this.genres = source["genres"];
this.tags = this.convertValues(source["tags"], );
this.isAdult = source["isAdult"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class MediaList { export class MediaList {
id: number; id: number;
mediaId: number; mediaId: number;
userId: 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\""; 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; status: string;
media: Media;
// Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" } // Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" }
startedAt: any; startedAt: any;
// Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" } // Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" }
@@ -498,8 +387,8 @@ export namespace main {
this.id = source["id"]; this.id = source["id"];
this.mediaId = source["mediaId"]; this.mediaId = source["mediaId"];
this.userId = source["userId"]; this.userId = source["userId"];
this.media = this.convertValues(source["media"], Object);
this.status = source["status"]; this.status = source["status"];
this.media = this.convertValues(source["media"], Media);
this.startedAt = this.convertValues(source["startedAt"], Object); this.startedAt = this.convertValues(source["startedAt"], Object);
this.completedAt = this.convertValues(source["completedAt"], Object); this.completedAt = this.convertValues(source["completedAt"], Object);
this.notes = source["notes"]; this.notes = source["notes"];
@@ -735,43 +624,6 @@ export namespace struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Tit
} }
export namespace struct { NumListUsers int "json:\"num_list_users\" ts_type:\"numListUsers\""; Status struct { Watching main {
export class {
num_list_users: numListUsers;
Status: struct { Watching main.;
static createFrom(source: any = {}) {
return new (source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.num_list_users = source["num_list_users"];
this.Status = this.convertValues(source["Status"], Object);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time { export namespace struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time {
export class { export class {
@@ -801,28 +653,3 @@ export namespace struct { Status string "json:\"status\" ts_type:\"status\""; Sc
} }
export namespace struct { Watching main {
export class {
watching: string;
completed: string;
on_hold: string;
dropped: string;
plan_to_watch: string;
static createFrom(source: any = {}) {
return new (source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.watching = source["watching"];
this.completed = source["completed"];
this.on_hold = source["on_hold"];
this.dropped = source["dropped"];
this.plan_to_watch = source["plan_to_watch"];
}
}
}
+1 -82
View File
@@ -246,85 +246,4 @@ export function OnFileDropOff() :void
export function CanResolveFilePaths(): boolean; export function CanResolveFilePaths(): boolean;
// Resolves file paths for an array of files // Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void export function ResolveFilePaths(files: File[]): void
// Notification types
export interface NotificationOptions {
id: string;
title: string;
subtitle?: string; // macOS and Linux only
body?: string;
categoryId?: string;
data?: { [key: string]: any };
}
export interface NotificationAction {
id?: string;
title?: string;
destructive?: boolean; // macOS-specific
}
export interface NotificationCategory {
id?: string;
actions?: NotificationAction[];
hasReplyField?: boolean;
replyPlaceholder?: string;
replyButtonTitle?: string;
}
// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
// Initializes the notification service for the application.
// This must be called before sending any notifications.
export function InitializeNotifications(): Promise<void>;
// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
// Cleans up notification resources and releases any held connections.
export function CleanupNotifications(): Promise<void>;
// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
// Checks if notifications are available on the current platform.
export function IsNotificationAvailable(): Promise<boolean>;
// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
// Requests notification authorization from the user (macOS only).
export function RequestNotificationAuthorization(): Promise<boolean>;
// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
// Checks the current notification authorization status (macOS only).
export function CheckNotificationAuthorization(): Promise<boolean>;
// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
// Sends a basic notification with the given options.
export function SendNotification(options: NotificationOptions): Promise<void>;
// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
// Sends a notification with action buttons. Requires a registered category.
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;
// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
// Registers a notification category that can be used with SendNotificationWithActions.
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;
// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
// Removes a previously registered notification category.
export function RemoveNotificationCategory(categoryId: string): Promise<void>;
// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
// Removes all pending notifications from the notification center.
export function RemoveAllPendingNotifications(): Promise<void>;
// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
// Removes a specific pending notification by its identifier.
export function RemovePendingNotification(identifier: string): Promise<void>;
// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
// Removes all delivered notifications from the notification center.
export function RemoveAllDeliveredNotifications(): Promise<void>;
// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
// Removes a specific delivered notification by its identifier.
export function RemoveDeliveredNotification(identifier: string): Promise<void>;
// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
// Removes a notification by its identifier (cross-platform convenience function).
export function RemoveNotification(identifier: string): Promise<void>;
-60
View File
@@ -48,10 +48,6 @@ export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames); return window.runtime.EventsOff(eventName, ...additionalEventNames);
} }
export function EventsOffAll() {
return window.runtime.EventsOffAll();
}
export function EventsOnce(eventName, callback) { export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1); return EventsOnMultiple(eventName, callback, 1);
} }
@@ -239,60 +235,4 @@ export function CanResolveFilePaths() {
export function ResolveFilePaths(files) { export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files); return window.runtime.ResolveFilePaths(files);
}
export function InitializeNotifications() {
return window.runtime.InitializeNotifications();
}
export function CleanupNotifications() {
return window.runtime.CleanupNotifications();
}
export function IsNotificationAvailable() {
return window.runtime.IsNotificationAvailable();
}
export function RequestNotificationAuthorization() {
return window.runtime.RequestNotificationAuthorization();
}
export function CheckNotificationAuthorization() {
return window.runtime.CheckNotificationAuthorization();
}
export function SendNotification(options) {
return window.runtime.SendNotification(options);
}
export function SendNotificationWithActions(options) {
return window.runtime.SendNotificationWithActions(options);
}
export function RegisterNotificationCategory(category) {
return window.runtime.RegisterNotificationCategory(category);
}
export function RemoveNotificationCategory(categoryId) {
return window.runtime.RemoveNotificationCategory(categoryId);
}
export function RemoveAllPendingNotifications() {
return window.runtime.RemoveAllPendingNotifications();
}
export function RemovePendingNotification(identifier) {
return window.runtime.RemovePendingNotification(identifier);
}
export function RemoveAllDeliveredNotifications() {
return window.runtime.RemoveAllDeliveredNotifications();
}
export function RemoveDeliveredNotification(identifier) {
return window.runtime.RemoveDeliveredNotification(identifier);
}
export function RemoveNotification(identifier) {
return window.runtime.RemoveNotification(identifier);
} }
+17 -19
View File
@@ -1,51 +1,49 @@
module AniTrack module AniTrack
go 1.25.0 go 1.24.0
require ( require (
github.com/99designs/keyring v1.2.2 github.com/99designs/keyring v1.2.2
github.com/tidwall/gjson v1.19.0 github.com/tidwall/gjson v1.18.0
github.com/wailsapp/wails/v2 v2.15.0 github.com/wailsapp/wails/v2 v2.10.1
) )
require ( require (
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/bep/debounce v1.2.1 // indirect github.com/bep/debounce v1.2.1 // indirect
github.com/danieljoos/wincred v1.2.3 // indirect github.com/danieljoos/wincred v1.2.2 // indirect
github.com/dvsekhvalnov/jose2go v1.8.0 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/labstack/echo/v4 v4.15.2 // indirect github.com/labstack/echo/v4 v4.13.3 // indirect
github.com/labstack/gommon v0.5.0 // indirect github.com/labstack/gommon v0.4.2 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.4 // indirect github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect github.com/leaanthony/u v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mtibben/percent v0.2.1 // indirect github.com/mtibben/percent v0.2.1 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.53.0 // indirect github.com/samber/lo v1.49.1 // indirect
github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.23 // indirect github.com/wailsapp/go-webview2 v1.0.19 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.53.0 // indirect golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.56.0 // indirect golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.46.0 // indirect golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.44.0 // indirect golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect
) )
// replace github.com/wailsapp/wails/v2 v2.9.1 => /home/nymusicman/go/pkg/mod // replace github.com/wailsapp/wails/v2 v2.9.1 => /home/nymusicman/go/pkg/mod
+38 -39
View File
@@ -1,13 +1,11 @@
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvsekhvalnov/jose2go v1.8.0 h1:LqkkVKAlHFfH9LOEl5fe4p/zL02OhWE7pCufMBG2jLA= github.com/dvsekhvalnov/jose2go v1.8.0 h1:LqkkVKAlHFfH9LOEl5fe4p/zL02OhWE7pCufMBG2jLA=
@@ -16,23 +14,22 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= 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/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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.15.2 h1:nnh2sCzGCVYnU+wCisMPiYapEg/QVo/gcI9ePKg5/T4= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/labstack/echo/v4 v4.15.2/go.mod h1:Xzp1Ns1RA2c9fY7nSgUJkpkUZGNbEIVHZbtbOMPktBI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0= github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
@@ -48,8 +45,8 @@ github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
@@ -63,16 +60,17 @@ 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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= 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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
@@ -81,29 +79,30 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= 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 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.15.0 h1:u7cHK+UesZOlYxyJxfYLteaCPhws6UsZoDdqUejuX6Q= github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
github.com/wailsapp/wails/v2 v2.15.0/go.mod h1:scxrgwfsv6yR6fE6cCF+Flfl+JeU+SR87T9x4kILJ6M= github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= 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.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= 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-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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= 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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= 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= 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 h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+2 -2
View File
@@ -8,10 +8,10 @@
"frontend:dev:serverUrl": "auto", "frontend:dev:serverUrl": "auto",
"author": { "author": {
"name": "John O'Keefe", "name": "John O'Keefe",
"email": "admin@linuxhg.com" "email": "jokeefe@fastmail.com"
}, },
"info": { "info": {
"productName": "AniTrack", "productName": "AniTrack",
"productVersion": "1.5.5" "productVersion": "0.5.1"
} }
} }