Compare commits
25 Commits
3e7f7d1c95
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 335d757255 | |||
| a3b4857175 | |||
| 6e4b9c041a | |||
| ae54fd20dd | |||
| 2c7e2d0eff | |||
| 9a9f055c38 | |||
| bc497521e7 | |||
| 48a6005725 | |||
| 93a94d0797 | |||
| a2576b044c | |||
| 2ee2d85e9e | |||
| 1090f112f3 | |||
| 58c9f449e0 | |||
| f016c90353 | |||
| 6bbe0f0f48 | |||
| d841fee1e7 | |||
| f29d8f378e | |||
| 35e93c0ca9 | |||
| 8c169d549a | |||
| b2a8a504f3 | |||
| c85a53a278 | |||
| 2cf3844e76 | |||
| 6ed5fe8b71 | |||
| 8a8baf7f8f | |||
| ca8c8beaf3 |
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -183,7 +184,7 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
|
|||||||
return post
|
return post
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) AniListSearch(query string) any {
|
func (a *App) AniListSearch(query string) (interface{}, error) {
|
||||||
type Variables struct {
|
type Variables struct {
|
||||||
Search string `json:"search"`
|
Search string `json:"search"`
|
||||||
ListType string `json:"listType"`
|
ListType string `json:"listType"`
|
||||||
@@ -242,18 +243,20 @@ func (a *App) AniListSearch(query string) any {
|
|||||||
ListType: "ANIME",
|
ListType: "ANIME",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
returnedBody, _ := AniListQuery(body, false)
|
returnedBody, status := AniListQuery(body, false)
|
||||||
|
if status != "200 OK" {
|
||||||
|
return nil, fmt.Errorf("API search failed with status: %s", status)
|
||||||
|
}
|
||||||
var post interface{}
|
var post interface{}
|
||||||
err := json.Unmarshal(returnedBody, &post)
|
err := json.Unmarshal(returnedBody, &post)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed at unmarshal, %s\n", err)
|
log.Printf("Failed at unmarshal, %s\n", err)
|
||||||
|
return nil, fmt.Errorf("Failed to parse search results")
|
||||||
}
|
}
|
||||||
|
return post, nil
|
||||||
return post
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList {
|
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (AniListCurrentUserWatchList, error) {
|
||||||
user := a.GetAniListLoggedInUser()
|
user := a.GetAniListLoggedInUser()
|
||||||
type Variables struct {
|
type Variables struct {
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
@@ -404,11 +407,15 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
|
|||||||
err := json.Unmarshal(returnedBody, &badPost)
|
err := json.Unmarshal(returnedBody, &badPost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed at unmarshal, %s\n", err)
|
log.Printf("Failed at unmarshal, %s\n", err)
|
||||||
|
return post, fmt.Errorf("API authentication error")
|
||||||
}
|
}
|
||||||
log.Fatal(badPost.Errors[0].Message)
|
return post, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message)
|
||||||
|
}
|
||||||
|
if status != "200 OK" {
|
||||||
|
return post, fmt.Errorf("API request failed with status: %s", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
return post
|
return post, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime {
|
func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime {
|
||||||
|
|||||||
@@ -11,10 +11,19 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string) {
|
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string, error) {
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
|
|
||||||
req, _ := http.NewRequest(method, malUrl, strings.NewReader(body.Encode()))
|
req, err := http.NewRequest(method, malUrl, strings.NewReader(body.Encode()))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
message, _ := json.Marshal(struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}{
|
||||||
|
Message: "Failed to create request: " + err.Error(),
|
||||||
|
})
|
||||||
|
return message, "", fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken)
|
req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken)
|
||||||
@@ -22,47 +31,57 @@ func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage,
|
|||||||
resp, err := client.Do(req)
|
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" ts_type:"message"`
|
Message string `json:"message"`
|
||||||
}{
|
}{
|
||||||
Message: "Errored when sending request to the server" + err.Error(),
|
Message: "Network error: " + err.Error(),
|
||||||
})
|
})
|
||||||
|
return message, "", fmt.Errorf("network error: %w", err)
|
||||||
return message, resp.Status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
respBody, _ := io.ReadAll(resp.Body)
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp.Status, fmt.Errorf("failed to read response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if resp.Status == "401 Unauthorized" {
|
if resp.Status == "401 Unauthorized" {
|
||||||
refreshMyAnimeListAuthorizationToken()
|
refreshMyAnimeListAuthorizationToken()
|
||||||
MALHelper(method, malUrl, body)
|
return MALHelper(method, malUrl, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
return respBody, resp.Status
|
if resp.Status != "200 OK" && resp.Status != "201 Created" && resp.Status != "202 Accepted" {
|
||||||
|
return respBody, resp.Status, fmt.Errorf("API returned status: %s", resp.Status)
|
||||||
|
}
|
||||||
|
return respBody, resp.Status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetMyAnimeList(count int) MALWatchlist {
|
func (a *App) GetMyAnimeList(count int) (MALWatchlist, error) {
|
||||||
limit := strconv.Itoa(count)
|
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 := MALHelper("GET", malUrl, nil)
|
respBody, resStatus, err := MALHelper("GET", malUrl, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return malList, fmt.Errorf("failed to get MAL watchlist: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if resStatus == "200 OK" {
|
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
|
return malList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListStatus {
|
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) (MalListStatus, error) {
|
||||||
if update.NumTimesRewatched >= 1 {
|
if update.NumTimesRewatched >= 1 {
|
||||||
update.IsRewatching = true
|
update.IsRewatching = true
|
||||||
} else {
|
} else {
|
||||||
@@ -78,39 +97,52 @@ func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListS
|
|||||||
|
|
||||||
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(anime.Id) + "/my_list_status"
|
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(anime.Id) + "/my_list_status"
|
||||||
var status MalListStatus
|
var status MalListStatus
|
||||||
respBody, respStatus := MALHelper("PATCH", malUrl, body)
|
respBody, respStatus, err := MALHelper("PATCH", malUrl, body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return status, fmt.Errorf("failed to update MAL entry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if respStatus == "200 OK" {
|
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 {
|
func (a *App) GetMyAnimeListAnime(id int) (MALAnime, error) {
|
||||||
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,genres,created_at,updated_at,media_type,status,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,recommendations,studios,statistics"
|
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 {
|
func (a *App) DeleteMyAnimeListEntry(id int) (bool, error) {
|
||||||
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 := MALHelper("DELETE", malUrl, nil)
|
_, respStatus, err := MALHelper("DELETE", malUrl, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to delete MAL entry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if respStatus == "200 OK" {
|
if respStatus == "200 OK" {
|
||||||
return true
|
return true, nil
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false, fmt.Errorf("delete failed with status: %s", respStatus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.Fatalf("listen: %s\n", err)
|
log.Printf("Server error: %s\n", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Shutting down...")
|
fmt.Println("Shutting down...")
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -14,16 +14,24 @@ import (
|
|||||||
|
|
||||||
var SimklWatchList SimklWatchListType
|
var SimklWatchList SimklWatchListType
|
||||||
|
|
||||||
func SimklHelper(method string, url string, body interface{}) json.RawMessage {
|
func SimklHelper(method string, url string, body interface{}) (json.RawMessage, error) {
|
||||||
reader, _ := json.Marshal(body)
|
reader, err := 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, _ = http.NewRequest(method, url, bytes.NewBuffer(reader))
|
req, err = http.NewRequest(method, url, bytes.NewBuffer(reader))
|
||||||
} else {
|
} else {
|
||||||
req, _ = http.NewRequest(method, url, nil)
|
req, err = http.NewRequest(method, url, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
@@ -32,41 +40,45 @@ func SimklHelper(method string, url string, body interface{}) json.RawMessage {
|
|||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Errored when sending request to the server")
|
return nil, fmt.Errorf("network error: %w", err)
|
||||||
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, _ := io.ReadAll(resp.Body)
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
return respBody
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return respBody, fmt.Errorf("API returned status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
return respBody, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SimklGetUserWatchlist() SimklWatchListType {
|
func (a *App) SimklGetUserWatchlist() (SimklWatchListType, error) {
|
||||||
method := "GET"
|
method := "GET"
|
||||||
url := "https://api.simkl.com/sync/all-items/anime"
|
url := "https://api.simkl.com/sync/all-items/anime"
|
||||||
|
|
||||||
respBody := SimklHelper(method, url, nil)
|
respBody, err := SimklHelper(method, url, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return SimklWatchListType{}, fmt.Errorf("failed to get Simkl watchlist: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var errCheck struct {
|
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{}
|
return SimklWatchListType{}, fmt.Errorf("Simkl API error: %s", errCheck.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
var watchlist SimklWatchListType
|
var watchlist SimklWatchListType
|
||||||
@@ -74,14 +86,15 @@ func (a *App) SimklGetUserWatchlist() SimklWatchListType {
|
|||||||
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
|
return watchlist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, error) {
|
||||||
var episodes []Episode
|
var episodes []Episode
|
||||||
var url string
|
var url string
|
||||||
var shows []SimklPostShow
|
var shows []SimklPostShow
|
||||||
@@ -112,13 +125,19 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
|||||||
|
|
||||||
simklSync := SimklSyncHistoryType{shows}
|
simklSync := SimklSyncHistoryType{shows}
|
||||||
|
|
||||||
respBody := SimklHelper("POST", url, simklSync)
|
respBody, err := SimklHelper("POST", url, simklSync)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to sync episodes: %s\n", err)
|
||||||
|
return anime, fmt.Errorf("failed to sync episodes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var success interface{}
|
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 {
|
||||||
@@ -131,10 +150,10 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
|||||||
|
|
||||||
WatchListUpdate(anime)
|
WatchListUpdate(anime)
|
||||||
|
|
||||||
return anime
|
return anime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error) {
|
||||||
var url string
|
var url string
|
||||||
showWithRating := ShowWithRating{
|
showWithRating := ShowWithRating{
|
||||||
Title: anime.Show.Title,
|
Title: anime.Show.Title,
|
||||||
@@ -169,13 +188,17 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
|||||||
Shows []interface{} `json:"shows" ts_type:"shows"`
|
Shows []interface{} `json:"shows" ts_type:"shows"`
|
||||||
}{shows}
|
}{shows}
|
||||||
|
|
||||||
respBody := SimklHelper("POST", url, simklSync)
|
respBody, err := SimklHelper("POST", url, simklSync)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to sync rating: %s\n", err)
|
||||||
|
return anime, fmt.Errorf("failed to sync rating: %w", err)
|
||||||
|
}
|
||||||
var success interface{}
|
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 {
|
||||||
@@ -188,10 +211,10 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
|||||||
|
|
||||||
WatchListUpdate(anime)
|
WatchListUpdate(anime)
|
||||||
|
|
||||||
return anime
|
return anime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, error) {
|
||||||
url := "https://api.simkl.com/sync/add-to-list"
|
url := "https://api.simkl.com/sync/add-to-list"
|
||||||
show := SimklShowStatus{
|
show := SimklShowStatus{
|
||||||
Title: anime.Show.Title,
|
Title: anime.Show.Title,
|
||||||
@@ -211,13 +234,17 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
|||||||
Shows []SimklShowStatus `json:"shows" ts_type:"shows"`
|
Shows []SimklShowStatus `json:"shows" ts_type:"shows"`
|
||||||
}{shows}
|
}{shows}
|
||||||
|
|
||||||
respBody := SimklHelper("POST", url, simklSync)
|
respBody, err := SimklHelper("POST", url, simklSync)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to sync status: %s\n", err)
|
||||||
|
return anime, fmt.Errorf("failed to sync status: %w", err)
|
||||||
|
}
|
||||||
var success interface{}
|
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 {
|
||||||
@@ -230,15 +257,20 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
|||||||
|
|
||||||
WatchListUpdate(anime)
|
WatchListUpdate(anime)
|
||||||
|
|
||||||
return anime
|
return anime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) {
|
||||||
var result SimklAnime
|
var result SimklAnime
|
||||||
|
|
||||||
if reflect.DeepEqual(SimklWatchList, SimklWatchListType{}) {
|
if reflect.DeepEqual(SimklWatchList, SimklWatchListType{}) {
|
||||||
fmt.Println("Watchlist empty. Calling...")
|
fmt.Println("Watchlist empty. Calling...")
|
||||||
SimklWatchList = a.SimklGetUserWatchlist()
|
watchlist, err := a.SimklGetUserWatchlist()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to get watchlist: %s\n", err)
|
||||||
|
return result, fmt.Errorf("failed to load watchlist for search: %w", err)
|
||||||
|
}
|
||||||
|
SimklWatchList = watchlist
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, anime := range SimklWatchList.Anime {
|
for _, anime := range SimklWatchList.Anime {
|
||||||
@@ -255,22 +287,30 @@ func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
|||||||
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 := SimklHelper("GET", url, nil)
|
respBody, err := SimklHelper("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
err := json.Unmarshal(respBody, &anime)
|
log.Printf("Failed to search Simkl: %s\n", err)
|
||||||
|
return result, fmt.Errorf("failed to search Simkl by AniList ID: %w", err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(respBody, &anime)
|
||||||
|
|
||||||
if len(anime) == 0 {
|
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 = SimklHelper("GET", url, nil)
|
respBody, err = SimklHelper("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to search Simkl by MAL ID: %s\n", err)
|
||||||
|
return result, fmt.Errorf("failed to search by MAL ID: %w", err)
|
||||||
|
}
|
||||||
err = json.Unmarshal(respBody, &anime)
|
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
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, watchListAnime := range SimklWatchList.Anime {
|
for _, watchListAnime := range SimklWatchList.Anime {
|
||||||
@@ -288,10 +328,10 @@ func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SimklSyncRemove(anime SimklAnime) bool {
|
func (a *App) SimklSyncRemove(anime SimklAnime) (bool, error) {
|
||||||
url := "https://api.simkl.com/sync/history/remove"
|
url := "https://api.simkl.com/sync/history/remove"
|
||||||
var showArray []SimklShowStatus
|
var showArray []SimklShowStatus
|
||||||
|
|
||||||
@@ -312,25 +352,28 @@ func (a *App) SimklSyncRemove(anime SimklAnime) bool {
|
|||||||
Shows: showArray,
|
Shows: showArray,
|
||||||
}
|
}
|
||||||
|
|
||||||
respBody := SimklHelper("POST", url, show)
|
respBody, err := SimklHelper("POST", url, show)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to sync remove: %s\n", err)
|
||||||
|
return false, fmt.Errorf("failed to sync remove: %w", err)
|
||||||
|
}
|
||||||
var success SimklDeleteType
|
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
|
return true, nil
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("no shows were deleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
func WatchListUpdate(anime SimklAnime) {
|
func WatchListUpdate(anime SimklAnime) {
|
||||||
|
|||||||
@@ -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.Fatalf("listen: %s\n", err)
|
log.Printf("Server error: %s\n", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Shutting down...")
|
fmt.Println("Shutting down...")
|
||||||
}()
|
}()
|
||||||
@@ -138,7 +138,8 @@ func getSimklAuthorizationToken(content string) SimklJWT {
|
|||||||
}
|
}
|
||||||
jsonData, err := json.Marshal(data)
|
jsonData, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Printf("Failed to marshal data: %s\n", err)
|
||||||
|
return SimklJWT{}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := http.NewRequest("POST", "https://api.simkl.com/oauth/token", bytes.NewBuffer(jsonData))
|
response, err := http.NewRequest("POST", "https://api.simkl.com/oauth/token", bytes.NewBuffer(jsonData))
|
||||||
|
|||||||
5
frontend/.vscode/extensions.json
vendored
5
frontend/.vscode/extensions.json
vendored
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"svelte.svelte-vscode"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,42 +1,72 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
aniListLoggedIn,
|
aniListLoggedIn,
|
||||||
malLoggedIn,
|
malLoggedIn,
|
||||||
simklLoggedIn,
|
simklLoggedIn,
|
||||||
} from "./helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
watchlistNeedsRefresh,
|
||||||
import {onMount} from "svelte";
|
aniListPrimary,
|
||||||
import Router from "svelte-spa-router"
|
malPrimary,
|
||||||
import Home from "./routes/Home.svelte";
|
simklPrimary,
|
||||||
import {wrap} from "svelte-spa-router/wrap";
|
malWatchList,
|
||||||
import Spinner from "./helperComponents/Spinner.svelte";
|
simklWatchList,
|
||||||
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));
|
||||||
|
|
||||||
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 />
|
||||||
<Router routes={{
|
<ErrorModal />
|
||||||
'/': Home,
|
<Router
|
||||||
'/anime/:id': wrap({
|
routes={{
|
||||||
asyncComponent: () => import('./routes/AnimeRoutePage.svelte'),
|
"/": Home,
|
||||||
conditions: [async () => await CheckIfAniListLoggedIn()],
|
"/anime/:id": wrap({
|
||||||
loadingComponent: Spinner
|
asyncComponent: () => import("./routes/AnimeRoutePage.svelte"),
|
||||||
|
conditions: [async () => await CheckIfAniListLoggedIn()],
|
||||||
|
loadingComponent: Spinner,
|
||||||
}),
|
}),
|
||||||
// '*': "Not Found"
|
// '*': "Not Found"
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
malLoggedIn,
|
malLoggedIn,
|
||||||
simklAnime,
|
simklAnime,
|
||||||
simklLoggedIn,
|
simklLoggedIn,
|
||||||
|
watchlistNeedsRefresh,
|
||||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||||
import { push } from "svelte-spa-router";
|
import { push } from "svelte-spa-router";
|
||||||
import WebsiteLink from "./WebsiteLink.svelte";
|
import WebsiteLink from "./WebsiteLink.svelte";
|
||||||
@@ -362,6 +363,7 @@
|
|||||||
} finally {
|
} finally {
|
||||||
submitting.set(false);
|
submitting.set(false);
|
||||||
submitSuccess.set(true);
|
submitSuccess.set(true);
|
||||||
|
watchlistNeedsRefresh.set(true);
|
||||||
setTimeout(() => submitSuccess.set(false), 2000);
|
setTimeout(() => submitSuccess.set(false), 2000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -422,6 +424,7 @@
|
|||||||
} finally {
|
} finally {
|
||||||
submitting.set(false);
|
submitting.set(false);
|
||||||
submitSuccess.set(true);
|
submitSuccess.set(true);
|
||||||
|
watchlistNeedsRefresh.set(true);
|
||||||
setTimeout(() => submitSuccess.set(false), 2000);
|
setTimeout(() => submitSuccess.set(false), 2000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -480,7 +483,10 @@
|
|||||||
completedAtDate = null;
|
completedAtDate = null;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
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"
|
disabled={currentAniListAnime.data.MediaList.progress <= 0}
|
||||||
|
class={currentAniListAnime.data.MediaList.progress <= 0
|
||||||
|
? "border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||||
|
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="w-3 h-3 text-white"
|
class="w-3 h-3 text-white"
|
||||||
@@ -538,7 +544,27 @@
|
|||||||
if (startedAtDate === null) startedAtDate = new Date();
|
if (startedAtDate === null) startedAtDate = new Date();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
class="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={(currentAniListAnime.data.MediaList.media.episodes >
|
||||||
|
0 &&
|
||||||
|
currentAniListAnime.data.MediaList.progress >=
|
||||||
|
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||||
|
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||||
|
.episode > 0 &&
|
||||||
|
currentAniListAnime.data.MediaList.progress >
|
||||||
|
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||||
|
.episode -
|
||||||
|
2)}
|
||||||
|
class={(currentAniListAnime.data.MediaList.media.episodes > 0 &&
|
||||||
|
currentAniListAnime.data.MediaList.progress >=
|
||||||
|
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||||
|
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||||
|
.episode > 0 &&
|
||||||
|
currentAniListAnime.data.MediaList.progress >
|
||||||
|
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||||
|
.episode -
|
||||||
|
2)
|
||||||
|
? "border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||||
|
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="w-3 h-3 text-white"
|
class="w-3 h-3 text-white"
|
||||||
@@ -702,6 +728,7 @@
|
|||||||
Sync Changes
|
Sync Changes
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
class="text-white bg-gray-800 border border-gray-600 focus:outline-none hover:bg-gray-700 focus:ring-4
|
class="text-white bg-gray-800 border border-gray-600 focus:outline-none hover:bg-gray-700 focus:ring-4
|
||||||
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2
|
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2
|
||||||
hover:border-gray-600"
|
hover:border-gray-600"
|
||||||
|
|||||||
@@ -1,163 +1,177 @@
|
|||||||
<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,
|
||||||
} 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;
|
||||||
|
|
||||||
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));
|
||||||
|
|
||||||
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 (!dropdown.contains(event.target as Node) && !toggleBtn.contains(event.target as Node)) {
|
if (
|
||||||
dropdown.classList.add("hidden")
|
!dropdown.contains(event.target as Node) &&
|
||||||
document.removeEventListener("click", clickOutside)
|
!toggleBtn.contains(event.target as Node)
|
||||||
}
|
) {
|
||||||
|
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={{ color: "green" }}
|
dot={isAniListLoggedIn && isMALLoggedIn && isSimklLoggedIn
|
||||||
/>
|
? { color: "green" }
|
||||||
{:else}
|
: { color: "yellow" }}
|
||||||
<Avatar class="cursor-pointer" dot={{ color: "red" }} />
|
/>
|
||||||
{/if}
|
{:else}
|
||||||
</button>
|
<Avatar class="cursor-pointer" dot={{ color: "red" }} />
|
||||||
<div
|
{/if}
|
||||||
id="userDropdown"
|
</button>
|
||||||
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
|
||||||
>
|
id="userDropdown"
|
||||||
<div class="px-4 py-3 text-sm text-white">
|
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"
|
||||||
{#if isAniListLoggedIn}
|
>
|
||||||
<div>{currentAniListUser.data.Viewer.name}</div>
|
<div class="px-4 py-3 text-sm text-white">
|
||||||
{:else}
|
{#if isAniListLoggedIn}
|
||||||
<div>You are not logged into AniList</div>
|
<div>{currentAniListUser.data.Viewer.name}</div>
|
||||||
{/if}
|
{:else}
|
||||||
</div>
|
<div>You are not logged into AniList</div>
|
||||||
<ul
|
{/if}
|
||||||
class="py-2 text-sm text-gray-200"
|
|
||||||
aria-labelledby="dropdownUserAvatarButton"
|
|
||||||
>
|
|
||||||
{#if isAniListLoggedIn}
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
on:click={logoutOfAniList}
|
|
||||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-green-800 hover:text-white"
|
|
||||||
>
|
|
||||||
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser.data.Viewer.name}
|
|
||||||
</button>
|
|
||||||
</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
|
|
||||||
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>
|
||||||
|
<ul
|
||||||
|
class="py-2 text-sm text-gray-200"
|
||||||
|
aria-labelledby="dropdownUserAvatarButton"
|
||||||
|
>
|
||||||
|
{#if isAniListLoggedIn}
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
on:click={logoutOfAniList}
|
||||||
|
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-green-800 hover:text-white"
|
||||||
|
>
|
||||||
|
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser
|
||||||
|
.data.Viewer.name}
|
||||||
|
</button>
|
||||||
|
</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
|
||||||
|
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>
|
||||||
|
|
||||||
|
|||||||
73
frontend/src/helperComponents/ErrorModal.svelte
Normal file
73
frontend/src/helperComponents/ErrorModal.svelte
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
apiError,
|
||||||
|
isApiDown,
|
||||||
|
clearApiError,
|
||||||
|
setApiError,
|
||||||
|
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||||
|
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||||
|
import { CheckIfMALLoggedInAndSetUser } from "../helperModules/CheckIfMyAnimeListLoggedIn.svelte";
|
||||||
|
import { CheckIfSimklLoggedInAndSetUser } from "../helperModules/CheckIsSimklLoggedIn.svelte";
|
||||||
|
import { Modal, Button } from "flowbite-svelte";
|
||||||
|
let showModal = false;
|
||||||
|
$: if ($apiError) {
|
||||||
|
showModal = true;
|
||||||
|
}
|
||||||
|
async function handleRetry() {
|
||||||
|
const service = $apiError?.service;
|
||||||
|
clearApiError();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (service === "anilist") {
|
||||||
|
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||||
|
} else if (service === "mal") {
|
||||||
|
await CheckIfMALLoggedInAndSetUser();
|
||||||
|
} else if (service === "simkl") {
|
||||||
|
await CheckIfSimklLoggedInAndSetUser();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||||
|
setApiError(
|
||||||
|
service || "unknown",
|
||||||
|
`Retry failed: ${errorMsg}`,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleDismiss() {
|
||||||
|
clearApiError();
|
||||||
|
showModal = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if showModal && $apiError}
|
||||||
|
<Modal
|
||||||
|
open={showModal}
|
||||||
|
title="{$apiError.service.toUpperCase()} API Error"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||||
|
<p class="text-red-800 font-medium">{$apiError.message}</p>
|
||||||
|
{#if $apiError.statusCode}
|
||||||
|
<p class="text-red-600 text-sm mt-2">
|
||||||
|
Status: {$apiError.statusCode}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
The application will remain open. You can retry the connection or
|
||||||
|
dismiss this message to continue with limited functionality.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="flex gap-3 justify-end">
|
||||||
|
{#if $apiError.canRetry}
|
||||||
|
<Button on:click={handleRetry} class="bg-blue-600 hover:bg-blue-700">
|
||||||
|
Retry Connection
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
<Button on:click={handleDismiss} color="alternative">Dismiss</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
||||||
@@ -1,145 +1,220 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
aniListLoggedIn,
|
aniListLoggedIn,
|
||||||
aniListWatchlist,
|
aniListSort,
|
||||||
animePerPage,
|
aniListWatchlist,
|
||||||
watchListPage,
|
animePerPage,
|
||||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
watchListPage,
|
||||||
|
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||||
|
|
||||||
import type {AniListCurrentUserWatchList} from "../anilist/types/AniListCurrentUserWatchListType"
|
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||||
import {GetAniListUserWatchingList} from "../../wailsjs/go/main/App";
|
import { 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, MediaListSort.UpdatedTimeDesc).then((result) => {
|
GetAniListUserWatchingList(newPage, perPage, sort).then((result) => {
|
||||||
watchListPage.set(newPage)
|
watchListPage.set(newPage);
|
||||||
aniListWatchlist.set(result)
|
aniListWatchlist.set(result);
|
||||||
aniListLoggedIn.set(true)
|
aniListLoggedIn.set(true);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePage(e): void {
|
function changePage(
|
||||||
if ((e.key === "Enter" || e.key === "Tab") && Number(e.target.value) !== page) ChangeWatchListPage(Number(e.target.value))
|
e: KeyboardEvent & { currentTarget: HTMLInputElement },
|
||||||
}
|
): void {
|
||||||
|
if (
|
||||||
function changeCountPerPage(e): void {
|
(e.key === "Enter" || e.key === "Tab") &&
|
||||||
GetAniListUserWatchingList(1, Number(e.target.value), MediaListSort.UpdatedTimeDesc).then((result) => {
|
Number(e.currentTarget.value) !== page
|
||||||
animePerPage.set(Number(e.target.value))
|
)
|
||||||
watchListPage.set(1)
|
ChangeWatchListPage(Number(e.currentTarget.value));
|
||||||
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 disabled
|
<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 cursor-default">
|
disabled
|
||||||
Previous
|
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 cursor-default"
|
||||||
</button>
|
>
|
||||||
</li>
|
Previous
|
||||||
{:else}
|
</button>
|
||||||
<li>
|
</li>
|
||||||
<button on:click={() => ChangeWatchListPage(page-1)}
|
{:else}
|
||||||
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>
|
||||||
Previous
|
<button
|
||||||
</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"
|
||||||
{/if}
|
>
|
||||||
{#each {length: aniListWatchListLoaded.data.Page.pageInfo.lastPage} as _, i}
|
Previous
|
||||||
{#if i + 1 === page}
|
</button>
|
||||||
<li>
|
</li>
|
||||||
<button on:click={() => ChangeWatchListPage(i+1)}
|
{/if}
|
||||||
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>
|
{#each { length: aniListWatchListLoaded.data.Page.pageInfo.lastPage } as _, i}
|
||||||
</li>
|
{#if i + 1 === page}
|
||||||
{:else}
|
<li>
|
||||||
<li>
|
<button
|
||||||
<button on:click={() => ChangeWatchListPage(i+1)}
|
on:click={() => ChangeWatchListPage(i + 1)}
|
||||||
class="flex items-center justify-center px-4 h-10 leading-tight border dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">{i + 1}</button>
|
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"
|
||||||
</li>
|
>{i + 1}</button
|
||||||
{/if}
|
>
|
||||||
{/each}
|
</li>
|
||||||
{#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
{:else}
|
||||||
<li>
|
<li>
|
||||||
<button disabled
|
<button
|
||||||
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">
|
on:click={() => ChangeWatchListPage(i + 1)}
|
||||||
Next
|
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"
|
||||||
</button>
|
>{i + 1}</button
|
||||||
</li>
|
>
|
||||||
{:else}
|
</li>
|
||||||
<li>
|
{/if}
|
||||||
<button on:click={() => ChangeWatchListPage(page+1)}
|
{/each}
|
||||||
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">
|
{#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||||
Next
|
<li>
|
||||||
</button>
|
<button
|
||||||
</li>
|
disabled
|
||||||
{/if}
|
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"
|
||||||
</ul>
|
>
|
||||||
</nav>
|
Next
|
||||||
{/if}
|
</button>
|
||||||
<div class="flex mt-5">
|
</li>
|
||||||
<div class="w-20 mx-auto">
|
{:else}
|
||||||
<select bind:value={perPage} on:change={(e) => changeCountPerPage(e)} id="countPerPage"
|
<li>
|
||||||
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">
|
<button
|
||||||
{#each perPageOptions as option}
|
on:click={() => ChangeWatchListPage(page + 1)}
|
||||||
<option value={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}
|
>
|
||||||
</option>
|
Next
|
||||||
{/each}
|
</button>
|
||||||
</select>
|
</li>
|
||||||
</div>
|
{/if}
|
||||||
|
</ul>
|
||||||
<div>
|
</nav>
|
||||||
<div>Total Anime: {aniListWatchListLoaded.data.Page.pageInfo.total}</div>
|
{/if}
|
||||||
{#if aniListWatchListLoaded.data.Page.pageInfo.lastPage <= 12}
|
<div class="flex mt-5">
|
||||||
<div class="md:hidden">Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
|
<div class="w-20 mx-auto">
|
||||||
{:else}
|
<select
|
||||||
<div>Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
|
bind:value={perPage}
|
||||||
{/if}
|
on:change={(e) => changeCountPerPage(e)}
|
||||||
</div>
|
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"
|
||||||
<div class="max-w-xs mx-auto">
|
>
|
||||||
<div class="relative flex items-center max-w-[11rem]">
|
{#each perPageOptions as option}
|
||||||
<button type="button" id="decrement-button" on:click={() => ChangeWatchListPage(page-1)}
|
<option value={option}>
|
||||||
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">
|
{option}
|
||||||
<svg class="w-3 h-3 text-white" aria-hidden="true"
|
</option>
|
||||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
|
{/each}
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
</select>
|
||||||
d="M1 1h16"/>
|
|
||||||
</svg>
|
|
||||||
</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>
|
|
||||||
<button type="button" id="increment-button" on:click={() => ChangeWatchListPage(page+1)}
|
|
||||||
class="hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none">
|
|
||||||
<svg class="w-3 h-3 text-white" aria-hidden="true"
|
|
||||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
|
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
||||||
d="M9 1v16M1 9h16"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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>
|
||||||
|
{:else}
|
||||||
|
<div>
|
||||||
|
Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="max-w-xs mx-auto">
|
||||||
|
<div class="relative flex items-center max-w-[11rem]">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="decrement-button"
|
||||||
|
on:click={() => ChangeWatchListPage(page - 1)}
|
||||||
|
class={page <= 1
|
||||||
|
? "border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||||
|
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||||
|
disabled={page <= 1}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-3 h-3 text-white"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 18 2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M1 1h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max={aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||||
|
on:keydown={changePage}
|
||||||
|
id="page-counter"
|
||||||
|
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>
|
||||||
|
<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>
|
||||||
0
frontend/src/helperComponents/Sort.svelte
Normal file
0
frontend/src/helperComponents/Sort.svelte
Normal file
@@ -1,68 +1,99 @@
|
|||||||
<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";
|
||||||
|
|
||||||
|
let isAniListLoggedIn: boolean;
|
||||||
|
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
||||||
|
|
||||||
let isAniListLoggedIn: boolean
|
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||||
let aniListWatchListLoaded: AniListCurrentUserWatchList
|
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = value));
|
||||||
|
|
||||||
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
|
|
||||||
aniListWatchlist.subscribe((value) => aniListWatchListLoaded = value)
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{#if isAniListLoggedIn}
|
{#if isAniListLoggedIn}
|
||||||
<div class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center">
|
<div
|
||||||
<h1 class="text-left text-xl font-bold mb-4">Your AniList WatchList</h1>
|
class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h1 class="text-left text-xl font-bold">Your AniList WatchList</h1>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="py-2 px-4 bg-gray-700 rounded-lg"
|
||||||
|
on:click={async () => {
|
||||||
|
loading.set(true);
|
||||||
|
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||||
|
loading.set(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refresh WatchList
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
<div
|
||||||
{#each aniListWatchListLoaded.data.Page.mediaList as media}
|
class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8"
|
||||||
<div use:loader={loading} class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7">
|
>
|
||||||
<div class="flex flex-col items-center group">
|
{#each aniListWatchListLoaded.data.Page.mediaList as media}
|
||||||
<button on:click={() => {
|
<div
|
||||||
push(`#/anime/${media.media.id}`)
|
use:loader={loading}
|
||||||
// loading.set(true)
|
class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7"
|
||||||
// GetAniListSingleItem(media.media.id, true).then(() => {
|
>
|
||||||
// loading.set(false)
|
<div class="flex flex-col items-center group">
|
||||||
//
|
<button
|
||||||
// })
|
on:click={() => {
|
||||||
}}
|
push(`#/anime/${media.media.id}`);
|
||||||
>
|
// loading.set(true)
|
||||||
<img class="rounded-lg" src={media.media.coverImage.large} alt={
|
// GetAniListSingleItem(media.media.id, true).then(() => {
|
||||||
media.media.title.english === "" ?
|
// loading.set(false)
|
||||||
media.media.title.romaji :
|
//
|
||||||
media.media.title.english
|
// })
|
||||||
}/>
|
}}
|
||||||
</button>
|
>
|
||||||
<Rating id="anime-rating" total={5} size={35} rating={media.score/2.0}/>
|
<img
|
||||||
<button class="mt-4 text-md font-semibold text-white-700"
|
class="rounded-lg w-[230px] h-[330px] object-cover"
|
||||||
on:click={() => GetAnimeSingleItem(media.media.id, true)}>
|
src={media.media.coverImage.large}
|
||||||
{
|
alt={media.media.title.english === ""
|
||||||
media.media.title.english === "" ?
|
? media.media.title.romaji
|
||||||
media.media.title.romaji :
|
: media.media.title.english}
|
||||||
media.media.title.english
|
/>
|
||||||
}
|
</button>
|
||||||
</button>
|
<Rating
|
||||||
<p class="mt-1 text-lg font-medium text-white-900">{media.progress}
|
id="anime-rating"
|
||||||
/ {media.media.nextAiringEpisode.episode !== 0 ?
|
total={5}
|
||||||
media.media.nextAiringEpisode.episode - 1 : media.media.episodes}</p>
|
size={35}
|
||||||
{#if media.media.episodes > 0}
|
rating={media.score / 2.0}
|
||||||
<p class="mt-1 text-lg font-medium text-white-900">Total
|
/>
|
||||||
Episodes: {media.media.episodes}</p>
|
<button
|
||||||
{/if}
|
class="mt-4 text-md font-semibold text-white-700"
|
||||||
</div>
|
on:click={() => GetAnimeSingleItem(media.media.id, true)}
|
||||||
</div>
|
>
|
||||||
{/each}
|
{media.media.title.english === ""
|
||||||
|
? media.media.title.romaji
|
||||||
|
: media.media.title.english}
|
||||||
|
</button>
|
||||||
|
<p class="mt-1 text-lg font-medium text-white-900">
|
||||||
|
{media.progress}
|
||||||
|
/ {media.media.nextAiringEpisode.episode !== 0
|
||||||
|
? media.media.nextAiringEpisode.episode - 1
|
||||||
|
: media.media.episodes}
|
||||||
|
</p>
|
||||||
|
{#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>
|
||||||
{/if}
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,34 +1,81 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import {CheckIfAniListLoggedIn, GetAniListLoggedInUser, GetAniListUserWatchingList} from "../../wailsjs/go/main/App";
|
import {
|
||||||
import {MediaListSort} from "../anilist/types/AniListTypes";
|
CheckIfAniListLoggedIn,
|
||||||
import { aniListUser, watchListPage, animePerPage, aniListPrimary, aniListLoggedIn, aniListWatchlist } from "./GlobalVariablesAndHelperFunctions.svelte"
|
GetAniListLoggedInUser,
|
||||||
|
GetAniListUserWatchingList,
|
||||||
|
} from "../../wailsjs/go/main/App";
|
||||||
|
import {
|
||||||
|
aniListUser,
|
||||||
|
watchListPage,
|
||||||
|
animePerPage,
|
||||||
|
aniListPrimary,
|
||||||
|
aniListLoggedIn,
|
||||||
|
aniListWatchlist,
|
||||||
|
aniListSort,
|
||||||
|
clearApiError,
|
||||||
|
setApiError,
|
||||||
|
} 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 () => {
|
||||||
await GetAniListLoggedInUser().then(user => {
|
try {
|
||||||
aniListUser.set(user)
|
await GetAniListLoggedInUser().then((user) => {
|
||||||
})
|
aniListUser.set(user);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||||
|
setApiError(
|
||||||
|
"anilist",
|
||||||
|
`Failed to load user: ${errorMsg}`,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const LoadAniListWatchList = async () => {
|
export const LoadAniListWatchList = async () => {
|
||||||
await GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((watchList) => {
|
try {
|
||||||
aniListWatchlist.set(watchList)
|
const watchList = await GetAniListUserWatchingList(page, perPage, sort);
|
||||||
})
|
aniListWatchlist.set(watchList);
|
||||||
|
clearApiError();
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||||
|
setApiError(
|
||||||
|
"anilist",
|
||||||
|
`Failed to load watch list: ${errorMsg}`,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
export const CheckIfAniListLoggedInAndLoadWatchList = async () => {
|
export const CheckIfAniListLoggedInAndLoadWatchList = async () => {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -1,162 +1,212 @@
|
|||||||
<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 {SimklAnime, SimklUser, SimklWatchList} from "../simkl/types/simklTypes";
|
import type {
|
||||||
import {type AniListUser, MediaListSort} from "../anilist/types/AniListTypes";
|
SimklAnime,
|
||||||
import type {MALAnime, MALWatchlist, MyAnimeListUser} from "../mal/types/MALTypes";
|
SimklUser,
|
||||||
import type {TableItems} from "../helperTypes/TableTypes";
|
SimklWatchList,
|
||||||
import {AniListGetSingleAnimeDefaultData} from "../helperDefaults/AniListGetSingleAnime";
|
} from "../simkl/types/simklTypes";
|
||||||
|
import {
|
||||||
|
type AniListUser,
|
||||||
|
MediaListSort,
|
||||||
|
} from "../anilist/types/AniListTypes";
|
||||||
|
import type {
|
||||||
|
MALAnime,
|
||||||
|
MALWatchlist,
|
||||||
|
MyAnimeListUser,
|
||||||
|
} from "../mal/types/MALTypes";
|
||||||
|
import type { TableItems } from "../helperTypes/TableTypes";
|
||||||
|
import { AniListGetSingleAnimeDefaultData } from "../helperDefaults/AniListGetSingleAnime";
|
||||||
|
|
||||||
export let aniListAnime = writable(AniListGetSingleAnimeDefaultData)
|
export let 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 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 aniWatchlist: AniListCurrentUserWatchList
|
let sort: string;
|
||||||
let currentAniListAnime: AniListGetSingleAnime
|
let aniWatchlist: AniListCurrentUserWatchList;
|
||||||
|
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(aniId: number, login: boolean): Promise<""> {
|
export async function GetAnimeSingleItem(
|
||||||
await GetAniListItem(aniId, login).then(aniListResult => {
|
aniId: number,
|
||||||
let finalResult: AniListGetSingleAnime
|
login: boolean,
|
||||||
finalResult = aniListResult
|
): Promise<""> {
|
||||||
if (login === false) {
|
await GetAniListItem(aniId, login).then((aniListResult) => {
|
||||||
finalResult.data.MediaList.status = ""
|
let finalResult: AniListGetSingleAnime;
|
||||||
finalResult.data.MediaList.score = 0
|
finalResult = aniListResult;
|
||||||
finalResult.data.MediaList.progress = 0
|
if (login === false) {
|
||||||
finalResult.data.MediaList.notes = ""
|
finalResult.data.MediaList.status = "";
|
||||||
finalResult.data.MediaList.repeat = 0
|
finalResult.data.MediaList.score = 0;
|
||||||
finalResult.data.MediaList.startedAt.day = 0
|
finalResult.data.MediaList.progress = 0;
|
||||||
finalResult.data.MediaList.startedAt.month = 0
|
finalResult.data.MediaList.notes = "";
|
||||||
finalResult.data.MediaList.startedAt.year = 0
|
finalResult.data.MediaList.repeat = 0;
|
||||||
finalResult.data.MediaList.completedAt.day = 0
|
finalResult.data.MediaList.startedAt.day = 0;
|
||||||
finalResult.data.MediaList.completedAt.month = 0
|
finalResult.data.MediaList.startedAt.month = 0;
|
||||||
finalResult.data.MediaList.completedAt.year = 0
|
finalResult.data.MediaList.startedAt.year = 0;
|
||||||
}
|
finalResult.data.MediaList.completedAt.day = 0;
|
||||||
aniListAnime.set(finalResult)
|
finalResult.data.MediaList.completedAt.month = 0;
|
||||||
title.set(currentAniListAnime.data.MediaList.media.title.english === "" ?
|
finalResult.data.MediaList.completedAt.year = 0;
|
||||||
currentAniListAnime.data.MediaList.media.title.romaji :
|
}
|
||||||
currentAniListAnime.data.MediaList.media.title.english)
|
aniListAnime.set(finalResult);
|
||||||
})
|
title.set(
|
||||||
if (isMalLoggedIn) {
|
currentAniListAnime.data.MediaList.media.title.english === ""
|
||||||
await GetMyAnimeListAnime(currentAniListAnime.data.MediaList.media.idMal).then(malResult => {
|
? currentAniListAnime.data.MediaList.media.title.romaji
|
||||||
malAnime.set(malResult)
|
: currentAniListAnime.data.MediaList.media.title.english,
|
||||||
})
|
);
|
||||||
}
|
});
|
||||||
if (isSimklLoggedIn) {
|
if (isMalLoggedIn) {
|
||||||
await SimklSearch(currentAniListAnime.data.MediaList).then((value: SimklAnime) => {
|
await GetMyAnimeListAnime(
|
||||||
simklAnime.set(value)
|
currentAniListAnime.data.MediaList.media.idMal,
|
||||||
})
|
).then((malResult) => {
|
||||||
}
|
malAnime.set(malResult);
|
||||||
return ""
|
});
|
||||||
}
|
}
|
||||||
|
if (isSimklLoggedIn) {
|
||||||
export function loginToSimkl(): void {
|
await SimklSearch(currentAniListAnime.data.MediaList).then(
|
||||||
GetSimklLoggedInUser().then(user => {
|
(value: SimklAnime) => {
|
||||||
if (Object.keys(user).length === 0) {
|
simklAnime.set(value);
|
||||||
simklLoggedIn.set(false)
|
},
|
||||||
} else {
|
);
|
||||||
simklUser.set(user)
|
|
||||||
SimklGetUserWatchlist().then(result => {
|
|
||||||
simklWatchList.set(result)
|
|
||||||
simklLoggedIn.set(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
export function loginToAniList(): void {
|
export function loginToSimkl(): void {
|
||||||
GetAniListLoggedInUser().then(result => {
|
GetSimklLoggedInUser().then((user) => {
|
||||||
aniListUser.set(result)
|
if (Object.keys(user).length === 0) {
|
||||||
if (isAniListPrimary) {
|
simklLoggedIn.set(false);
|
||||||
GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
|
} else {
|
||||||
aniListWatchlist.set(result)
|
simklUser.set(user);
|
||||||
aniListLoggedIn.set(true)
|
SimklGetUserWatchlist().then((result) => {
|
||||||
})
|
simklWatchList.set(result);
|
||||||
} else {
|
simklLoggedIn.set(true);
|
||||||
aniListLoggedIn.set(true)
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loginToMAL(): void {
|
export function loginToAniList(): void {
|
||||||
GetMyAnimeListLoggedInUser().then(result => {
|
GetAniListLoggedInUser().then((result) => {
|
||||||
malUser.set(result)
|
aniListUser.set(result);
|
||||||
malLoggedIn.set(true)
|
if (isAniListPrimary) {
|
||||||
})
|
GetAniListUserWatchingList(page, perPage, sort).then((result) => {
|
||||||
}
|
aniListWatchlist.set(result);
|
||||||
|
aniListLoggedIn.set(true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
aniListLoggedIn.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function logoutOfAniList(): void {
|
export function loginToMAL(): void {
|
||||||
LogoutAniList().then(result => {
|
GetMyAnimeListLoggedInUser().then((result) => {
|
||||||
console.log(result)
|
malUser.set(result);
|
||||||
if (Object.keys(aniWatchlist).length !== 0) {
|
malLoggedIn.set(true);
|
||||||
aniListWatchlist.set({} as AniListCurrentUserWatchList)
|
});
|
||||||
}
|
}
|
||||||
aniListUser.set({} as AniListUser)
|
|
||||||
aniListLoggedIn.set(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function logoutOfMAL(): void {
|
export function logoutOfAniList(): void {
|
||||||
LogoutMyAnimeList().then(result => {
|
LogoutAniList().then((result) => {
|
||||||
console.log(result)
|
console.log(result);
|
||||||
malUser.set({} as MyAnimeListUser)
|
if (Object.keys(aniWatchlist).length !== 0) {
|
||||||
malLoggedIn.set(false)
|
aniListWatchlist.set({} as AniListCurrentUserWatchList);
|
||||||
})
|
}
|
||||||
}
|
aniListUser.set({} as AniListUser);
|
||||||
|
aniListLoggedIn.set(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function logoutOfSimkl(): void {
|
export function logoutOfMAL(): void {
|
||||||
LogoutSimkl().then(result => {
|
LogoutMyAnimeList().then((result) => {
|
||||||
console.log(result)
|
console.log(result);
|
||||||
simklUser.set({} as SimklUser)
|
malUser.set({} as MyAnimeListUser);
|
||||||
simklLoggedIn.set(false)
|
malLoggedIn.set(false);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logoutOfSimkl(): void {
|
||||||
|
LogoutSimkl().then((result) => {
|
||||||
|
console.log(result);
|
||||||
|
simklUser.set({} as SimklUser);
|
||||||
|
simklLoggedIn.set(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,25 +1,55 @@
|
|||||||
<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 {
|
import {
|
||||||
aniListLoggedIn,
|
aniListLoggedIn,
|
||||||
aniListPrimary,
|
aniListPrimary,
|
||||||
loading,
|
loading,
|
||||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
isApiDown,
|
||||||
import loader from '../helperFunctions/loader'
|
apiError,
|
||||||
|
} 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}
|
|
||||||
<div class="container py-10">
|
{#if $isApiDown}
|
||||||
|
<div class="container py-10">
|
||||||
|
<div
|
||||||
|
class="bg-yellow-50 border border-yellow-200 rounded-lg p-6 text-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="mx-auto h-12 w-12 text-yellow-600 mb-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<h2 class="text-xl font-semibold text-yellow-900 mb-2">
|
||||||
|
API Unavailable
|
||||||
|
</h2>
|
||||||
|
<p class="text-yellow-700 mb-4">
|
||||||
|
The {$apiError?.service || "service"} is currently unavailable. The app will
|
||||||
|
remain open, and you can retry when the service is back online.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else if isAniListLoggedIn && isAniListPrimary}
|
||||||
|
<div class="container py-10">
|
||||||
<Pagination />
|
<Pagination />
|
||||||
<WatchList />
|
<WatchList />
|
||||||
<Pagination />
|
<Pagination />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div use:loader={loading}></div>
|
<div use:loader={loading}></div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// vite.config.ts
|
|
||||||
import { defineConfig } from "file:///home/nymusicman/Code/AniTrack/frontend/node_modules/vite/dist/node/index.js";
|
|
||||||
import { svelte } from "file:///home/nymusicman/Code/AniTrack/frontend/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
|
|
||||||
var vite_config_default = defineConfig({
|
|
||||||
plugins: [svelte()]
|
|
||||||
});
|
|
||||||
export {
|
|
||||||
vite_config_default as default
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ueW11c2ljbWFuL0NvZGUvQW5pVHJhY2svZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL255bXVzaWNtYW4vQ29kZS9BbmlUcmFjay9mcm9udGVuZC92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ueW11c2ljbWFuL0NvZGUvQW5pVHJhY2svZnJvbnRlbmQvdml0ZS5jb25maWcudHNcIjtpbXBvcnQge2RlZmluZUNvbmZpZ30gZnJvbSAndml0ZSdcbmltcG9ydCB7c3ZlbHRlfSBmcm9tICdAc3ZlbHRlanMvdml0ZS1wbHVnaW4tc3ZlbHRlJ1xuXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW3N2ZWx0ZSgpXVxufSlcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBdVMsU0FBUSxvQkFBbUI7QUFDbFUsU0FBUSxjQUFhO0FBR3JCLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVMsQ0FBQyxPQUFPLENBQUM7QUFDcEIsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
|
|
||||||
@@ -12,6 +12,6 @@
|
|||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"productName": "AniTrack",
|
"productName": "AniTrack",
|
||||||
"productVersion": "0.5.2"
|
"productVersion": "0.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user