2 Commits

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

These changes improve code consistency and maintainability without
altering any functionality.
2026-03-20 10:56:37 -04:00
19 changed files with 1476 additions and 1896 deletions

3
.gitignore vendored
View File

@@ -33,6 +33,3 @@ environment.go
# REST (http files)
http-client.private.env.json
# Build artifacts
build/*.tar.gz

View File

@@ -3,7 +3,6 @@ package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
@@ -184,7 +183,7 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
return post
}
func (a *App) AniListSearch(query string) (interface{}, error) {
func (a *App) AniListSearch(query string) any {
type Variables struct {
Search string `json:"search"`
ListType string `json:"listType"`
@@ -243,20 +242,18 @@ func (a *App) AniListSearch(query string) (interface{}, error) {
ListType: "ANIME",
},
}
returnedBody, status := AniListQuery(body, false)
if status != "200 OK" {
return nil, fmt.Errorf("API search failed with status: %s", status)
}
returnedBody, _ := AniListQuery(body, false)
var post interface{}
err := json.Unmarshal(returnedBody, &post)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return nil, fmt.Errorf("Failed to parse search results")
}
return post, nil
}
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (AniListCurrentUserWatchList, error) {
return post
}
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList {
user := a.GetAniListLoggedInUser()
type Variables struct {
Page int `json:"page"`
@@ -407,15 +404,11 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (An
err := json.Unmarshal(returnedBody, &badPost)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return post, fmt.Errorf("API authentication error")
}
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)
log.Fatal(badPost.Errors[0].Message)
}
return post, nil
return post
}
func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime {

View File

@@ -11,19 +11,10 @@ import (
"strings"
)
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string, error) {
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string) {
client := &http.Client{}
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, _ := http.NewRequest(method, malUrl, strings.NewReader(body.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken)
@@ -31,57 +22,47 @@ func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage,
resp, err := client.Do(req)
if err != nil {
fmt.Println("Errored when sending request to the server")
message, _ := json.Marshal(struct {
Message string `json:"message"`
Message string `json:"message" ts_type:"message"`
}{
Message: "Network error: " + err.Error(),
Message: "Errored when sending request to the server" + err.Error(),
})
return message, "", fmt.Errorf("network error: %w", err)
return message, resp.Status
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, resp.Status, fmt.Errorf("failed to read response: %w", err)
}
respBody, _ := io.ReadAll(resp.Body)
if resp.Status == "401 Unauthorized" {
refreshMyAnimeListAuthorizationToken()
return MALHelper(method, malUrl, body)
MALHelper(method, malUrl, body)
}
if resp.Status != "200 OK" && resp.Status != "201 Created" && resp.Status != "202 Accepted" {
return respBody, resp.Status, fmt.Errorf("API returned status: %s", resp.Status)
}
return respBody, resp.Status, nil
return respBody, resp.Status
}
func (a *App) GetMyAnimeList(count int) (MALWatchlist, error) {
func (a *App) GetMyAnimeList(count int) MALWatchlist {
limit := strconv.Itoa(count)
user := a.GetMyAnimeListLoggedInUser()
malUrl := "https://api.myanimelist.net/v2/users/" + user.Name + "/animelist?fields=list_status&status=watching&limit=" + limit
var malList MALWatchlist
respBody, resStatus, err := MALHelper("GET", malUrl, nil)
if err != nil {
return malList, fmt.Errorf("failed to get MAL watchlist: %w", err)
}
respBody, resStatus := MALHelper("GET", malUrl, nil)
if resStatus == "200 OK" {
err := json.Unmarshal(respBody, &malList)
if err != nil {
log.Printf("Failed to unmarshal json response, %s\n", err)
return malList, fmt.Errorf("failed to parse response: %w", err)
}
}
return malList, nil
return malList
}
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) (MalListStatus, error) {
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListStatus {
if update.NumTimesRewatched >= 1 {
update.IsRewatching = true
} else {
@@ -97,52 +78,39 @@ func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) (MalList
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(anime.Id) + "/my_list_status"
var status MalListStatus
respBody, respStatus, err := MALHelper("PATCH", malUrl, body)
if err != nil {
return status, fmt.Errorf("failed to update MAL entry: %w", err)
}
respBody, respStatus := MALHelper("PATCH", malUrl, body)
if respStatus == "200 OK" {
err := json.Unmarshal(respBody, &status)
if err != nil {
log.Printf("Failed to unmarshal json response, %s\n", err)
return status, fmt.Errorf("failed to parse response: %w", err)
}
}
return status, nil
}
func (a *App) GetMyAnimeListAnime(id int) (MALAnime, error) {
return status
}
func (a *App) GetMyAnimeListAnime(id int) MALAnime {
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,genres,created_at,updated_at,media_type,status,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,recommendations,studios,statistics"
respBody, respStatus := MALHelper("GET", malUrl, nil)
var malAnime MALAnime
respBody, respStatus, err := MALHelper("GET", malUrl, nil)
if err != nil {
return malAnime, fmt.Errorf("failed to get MAL anime: %w", err)
}
if respStatus == "200 OK" {
err := json.Unmarshal(respBody, &malAnime)
if err != nil {
log.Printf("Failed to unmarshal json response, %s\n", err)
return malAnime, fmt.Errorf("failed to parse response: %w", err)
}
}
return malAnime, nil
}
func (a *App) DeleteMyAnimeListEntry(id int) (bool, error) {
return malAnime
}
func (a *App) DeleteMyAnimeListEntry(id int) bool {
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "/my_list_status"
_, respStatus, err := MALHelper("DELETE", malUrl, nil)
if err != nil {
return false, fmt.Errorf("failed to delete MAL entry: %w", err)
}
_, respStatus := MALHelper("DELETE", malUrl, nil)
if respStatus == "200 OK" {
return true, nil
return true
} else {
return false, fmt.Errorf("delete failed with status: %s", respStatus)
return false
}
}

View File

@@ -176,7 +176,7 @@ func (a *App) handleMyAnimeListCallback(wg *sync.WaitGroup, verifier *CodeVerifi
go func() {
defer wg.Done()
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Printf("Server error: %s\n", err)
log.Fatalf("listen: %s\n", err)
}
fmt.Println("Shutting down...")
}()

View File

@@ -14,24 +14,16 @@ import (
var SimklWatchList SimklWatchListType
func SimklHelper(method string, url string, body interface{}) (json.RawMessage, error) {
reader, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal body: %w", err)
}
func SimklHelper(method string, url string, body interface{}) json.RawMessage {
reader, _ := json.Marshal(body)
var req *http.Request
client := &http.Client{}
if body != nil {
req, err = http.NewRequest(method, url, bytes.NewBuffer(reader))
req, _ = http.NewRequest(method, url, bytes.NewBuffer(reader))
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
req, _ = http.NewRequest(method, url, nil)
}
req.Header.Add("Content-Type", "application/json")
@@ -40,45 +32,41 @@ func SimklHelper(method string, url string, body interface{}) (json.RawMessage,
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("network error: %w", err)
fmt.Println("Errored when sending request to the server")
message, _ := json.Marshal(struct {
Message string `json:"message"`
}{
Message: "Errored when sending request to the server" + err.Error(),
})
return message
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
respBody, _ := io.ReadAll(resp.Body)
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
return respBody
}
func (a *App) SimklGetUserWatchlist() (SimklWatchListType, error) {
func (a *App) SimklGetUserWatchlist() SimklWatchListType {
method := "GET"
url := "https://api.simkl.com/sync/all-items/anime"
respBody, err := SimklHelper(method, url, nil)
if err != nil {
return SimklWatchListType{}, fmt.Errorf("failed to get Simkl watchlist: %w", err)
}
respBody := SimklHelper(method, url, nil)
var errCheck struct {
Error string `json:"error"`
Message string `json:"message"`
}
err = json.Unmarshal(respBody, &errCheck)
err := json.Unmarshal(respBody, &errCheck)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return SimklWatchListType{}, fmt.Errorf("failed to parse error response: %w", err)
}
if errCheck.Error != "" {
a.LogoutSimkl()
return SimklWatchListType{}, fmt.Errorf("Simkl API error: %s", errCheck.Message)
return SimklWatchListType{}
}
var watchlist SimklWatchListType
@@ -86,15 +74,14 @@ func (a *App) SimklGetUserWatchlist() (SimklWatchListType, error) {
err = json.Unmarshal(respBody, &watchlist)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return SimklWatchListType{}, fmt.Errorf("failed to parse watchlist: %w", err)
}
SimklWatchList = watchlist
return watchlist, nil
return watchlist
}
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, error) {
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
var episodes []Episode
var url string
var shows []SimklPostShow
@@ -125,19 +112,13 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, err
simklSync := SimklSyncHistoryType{shows}
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)
}
respBody := SimklHelper("POST", url, simklSync)
var success interface{}
err = json.Unmarshal(respBody, &success)
err := json.Unmarshal(respBody, &success)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return anime, fmt.Errorf("failed to parse response: %w", err)
}
for i, simklAnime := range SimklWatchList.Anime {
@@ -150,10 +131,10 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, err
WatchListUpdate(anime)
return anime, nil
return anime
}
func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error) {
func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
var url string
showWithRating := ShowWithRating{
Title: anime.Show.Title,
@@ -188,17 +169,13 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error)
Shows []interface{} `json:"shows" ts_type:"shows"`
}{shows}
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)
}
respBody := SimklHelper("POST", url, simklSync)
var success interface{}
err = json.Unmarshal(respBody, &success)
err := json.Unmarshal(respBody, &success)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return anime, fmt.Errorf("failed to parse response: %w", err)
}
for i, simklAnime := range SimklWatchList.Anime {
@@ -211,10 +188,10 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error)
WatchListUpdate(anime)
return anime, nil
return anime
}
func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, error) {
func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
url := "https://api.simkl.com/sync/add-to-list"
show := SimklShowStatus{
Title: anime.Show.Title,
@@ -234,17 +211,13 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, erro
Shows []SimklShowStatus `json:"shows" ts_type:"shows"`
}{shows}
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)
}
respBody := SimklHelper("POST", url, simklSync)
var success interface{}
err = json.Unmarshal(respBody, &success)
err := json.Unmarshal(respBody, &success)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return anime, fmt.Errorf("failed to parse response: %w", err)
}
for i, simklAnime := range SimklWatchList.Anime {
@@ -257,20 +230,15 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, erro
WatchListUpdate(anime)
return anime, nil
return anime
}
func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) {
func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
var result SimklAnime
if reflect.DeepEqual(SimklWatchList, SimklWatchListType{}) {
fmt.Println("Watchlist empty. Calling...")
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
SimklWatchList = a.SimklGetUserWatchlist()
}
for _, anime := range SimklWatchList.Anime {
@@ -287,30 +255,22 @@ func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) {
var anime SimklSearchType
url := "https://api.simkl.com/search/id?anilist=" + strconv.Itoa(aniListAnime.Media.ID)
respBody, err := SimklHelper("GET", url, nil)
if err != nil {
log.Printf("Failed to search Simkl: %s\n", err)
return result, fmt.Errorf("failed to search Simkl by AniList ID: %w", err)
}
err = json.Unmarshal(respBody, &anime)
respBody := SimklHelper("GET", url, nil)
err := json.Unmarshal(respBody, &anime)
if len(anime) == 0 {
url = "https://api.simkl.com/search/id?mal=" + strconv.Itoa(aniListAnime.Media.IDMal)
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)
}
respBody = SimklHelper("GET", url, nil)
err = json.Unmarshal(respBody, &anime)
}
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return result, fmt.Errorf("failed to parse search results: %w", err)
}
if len(anime) == 0 {
return result, nil
return result
}
for _, watchListAnime := range SimklWatchList.Anime {
@@ -328,10 +288,10 @@ func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) {
}
}
return result, nil
return result
}
func (a *App) SimklSyncRemove(anime SimklAnime) (bool, error) {
func (a *App) SimklSyncRemove(anime SimklAnime) bool {
url := "https://api.simkl.com/sync/history/remove"
var showArray []SimklShowStatus
@@ -352,28 +312,25 @@ func (a *App) SimklSyncRemove(anime SimklAnime) (bool, error) {
Shows: showArray,
}
respBody, err := SimklHelper("POST", url, show)
respBody := SimklHelper("POST", url, show)
if err != nil {
log.Printf("Failed to sync remove: %s\n", err)
return false, fmt.Errorf("failed to sync remove: %w", err)
}
var success SimklDeleteType
err = json.Unmarshal(respBody, &success)
err := json.Unmarshal(respBody, &success)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return false, fmt.Errorf("failed to parse response: %w", err)
}
if success.Deleted.Shows >= 1 {
for i, simklAnime := range SimklWatchList.Anime {
if simklAnime.Show.Ids.Simkl == anime.Show.Ids.Simkl {
SimklWatchList.Anime = slices.Delete(SimklWatchList.Anime, i, i+1)
}
}
return true, nil
return true
} else {
return false
}
return false, fmt.Errorf("no shows were deleted")
}
func WatchListUpdate(anime SimklAnime) {

View File

@@ -116,7 +116,7 @@ func (a *App) handleSimklCallback(wg *sync.WaitGroup) {
go func() {
defer wg.Done()
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Printf("Server error: %s\n", err)
log.Fatalf("listen: %s\n", err)
}
fmt.Println("Shutting down...")
}()
@@ -138,8 +138,7 @@ func getSimklAuthorizationToken(content string) SimklJWT {
}
jsonData, err := json.Marshal(data)
if err != nil {
log.Printf("Failed to marshal data: %s\n", err)
return SimklJWT{}
log.Fatal(err)
}
response, err := http.NewRequest("POST", "https://api.simkl.com/oauth/token", bytes.NewBuffer(jsonData))

5
frontend/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"recommendations": [
"svelte.svelte-vscode"
]
}

View File

@@ -3,70 +3,40 @@
aniListLoggedIn,
malLoggedIn,
simklLoggedIn,
watchlistNeedsRefresh,
aniListPrimary,
malPrimary,
simklPrimary,
malWatchList,
simklWatchList,
} from "./helperModules/GlobalVariablesAndHelperFunctions.svelte";
import {onMount} from "svelte";
import Router from "svelte-spa-router";
import Router from "svelte-spa-router"
import Home from "./routes/Home.svelte";
import {wrap} from "svelte-spa-router/wrap";
import Spinner from "./helperComponents/Spinner.svelte";
import Header from "./helperComponents/Header.svelte";
import {CheckIfAniListLoggedInAndLoadWatchList} from "./helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte";
import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte";
import {
CheckIfAniListLoggedIn,
GetMyAnimeList,
SimklGetUserWatchlist,
} from "../wailsjs/go/main/App";
import { loc } from "svelte-spa-router";
import ErrorModal from "./helperComponents/ErrorModal.svelte";
import {CheckIfSimklLoggedInAndSetUser} from "./helperModules/CheckIsSimklLoggedIn.svelte"
import {CheckIfAniListLoggedIn} from "../wailsjs/go/main/App";
onMount(async () => {
let isAniListLoggedIn: boolean;
let isMALLoggedIn: boolean;
let isSimklLoggedIn: boolean;
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
let isAniListLoggedIn: boolean
let isMALLoggedIn: boolean
let isSimklLoggedIn: boolean
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
malLoggedIn.subscribe((value) => isMALLoggedIn = value)
simklLoggedIn.subscribe((value) => isSimklLoggedIn = value)
!isAniListLoggedIn && (await CheckIfAniListLoggedInAndLoadWatchList());
!isMALLoggedIn && (await CheckIfMALLoggedInAndSetUser());
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
});
$: if ($loc?.location === "/" && $watchlistNeedsRefresh) {
(async () => {
if ($aniListLoggedIn && $aniListPrimary) {
await CheckIfAniListLoggedInAndLoadWatchList();
}
if ($malLoggedIn && $malPrimary) {
await GetMyAnimeList(1000).then((w) => malWatchList.set(w));
}
if ($simklLoggedIn && $simklPrimary) {
await SimklGetUserWatchlist().then((w) => simklWatchList.set(w));
}
watchlistNeedsRefresh.set(false);
})();
}
console.log(isAniListLoggedIn)
!isAniListLoggedIn && await CheckIfAniListLoggedInAndLoadWatchList()
!isMALLoggedIn && await CheckIfMALLoggedInAndSetUser()
!isSimklLoggedIn && await CheckIfSimklLoggedInAndSetUser()
})
</script>
<Header />
<ErrorModal />
<Router
routes={{
"/": Home,
"/anime/:id": wrap({
asyncComponent: () => import("./routes/AnimeRoutePage.svelte"),
<Router routes={{
'/': Home,
'/anime/:id': wrap({
asyncComponent: () => import('./routes/AnimeRoutePage.svelte'),
conditions: [async () => await CheckIfAniListLoggedIn()],
loadingComponent: Spinner,
loadingComponent: Spinner
}),
// '*': "Not Found"
}}
/>
}} />

View File

@@ -6,7 +6,6 @@
malLoggedIn,
simklAnime,
simklLoggedIn,
watchlistNeedsRefresh,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import { push } from "svelte-spa-router";
import WebsiteLink from "./WebsiteLink.svelte";
@@ -24,7 +23,10 @@
} from "../mal/types/MALTypes";
import type { SimklAnime } from "../simkl/types/simklTypes";
import { writable } from "svelte/store";
import type { StatusOption, StatusOptions } from "../helperTypes/StatusTypes";
import type {
StatusOption,
StatusOptions,
} from "../helperTypes/StatusTypes";
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
import { convertDateToAniList } from "../helperFunctions/convertDateToAniList";
import {
@@ -79,7 +81,8 @@
{ id: 5, aniList: "REPEATING", mal: "rewatching", simkl: "watching" },
];
let startingAnilistStatusOption: StatusOption = statusOptions.filter(
(option) => currentAniListAnime.data.MediaList.status === option.aniList,
(option) =>
currentAniListAnime.data.MediaList.status === option.aniList,
)[0];
let startedAtDate: Date | null = convertAniListDateToDate(
currentAniListAnime.data.MediaList.startedAt,
@@ -110,11 +113,15 @@
let startDate = "";
let finishDate = "";
if (currentMalAnime.my_list_status.start_date !== "") {
const startArray = re.exec(currentMalAnime.my_list_status.start_date);
const startArray = re.exec(
currentMalAnime.my_list_status.start_date,
);
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
}
if (currentMalAnime.my_list_status.finish_date !== "") {
const finishArray = re.exec(currentMalAnime.my_list_status.finish_date);
const finishArray = re.exec(
currentMalAnime.my_list_status.finish_date,
);
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
}
AddAnimeServiceToTable({
@@ -191,7 +198,6 @@
submitData[key] = value;
}
try {
if (
isAniListLoggedIn &&
currentAniListAnime.data.MediaList.mediaId !== 0
@@ -206,11 +212,10 @@
startedAt: convertDateToAniList(startedAtDate),
completedAt: convertDateToAniList(completedAtDate),
};
await AniListUpdateEntry(body).then((value: AniListGetSingleAnime) => {
await AniListUpdateEntry(body).then(
(value: AniListGetSingleAnime) => {
value.data.MediaList.media.tags =
currentAniListAnime.data.MediaList.media.tags;
value.data.MediaList.media.genres =
currentAniListAnime.data.MediaList.media.genres;
aniListAnime.update((newValue) => {
newValue = value;
return newValue;
@@ -231,7 +236,8 @@
repeat: currentAniListAnime.data.MediaList.repeat,
notes: currentAniListAnime.data.MediaList.notes,
});
});
},
);
}
if (malLoggedIn && currentMalAnime.id !== 0) {
@@ -248,7 +254,8 @@
(malAnimeReturn: MalListStatus) => {
malAnime.update((value) => {
value.my_list_status.status = malAnimeReturn.status;
value.my_list_status.is_rewatching = malAnimeReturn.is_rewatching;
value.my_list_status.is_rewatching =
malAnimeReturn.is_rewatching;
value.my_list_status.score = malAnimeReturn.score;
value.my_list_status.num_episodes_watched =
malAnimeReturn.num_episodes_watched;
@@ -275,12 +282,14 @@
id: `m-${currentMalAnime.id}`,
title: currentMalAnime.title,
service: "MyAnimeList",
progress: currentMalAnime.my_list_status.num_episodes_watched,
progress:
currentMalAnime.my_list_status.num_episodes_watched,
status: currentMalAnime.my_list_status.status,
startedAt: startDate,
completedAt: finishDate,
score: currentMalAnime.my_list_status.score,
repeat: currentMalAnime.my_list_status.num_times_rewatched,
repeat: currentMalAnime.my_list_status
.num_times_rewatched,
notes: currentMalAnime.my_list_status.comments,
});
},
@@ -288,9 +297,13 @@
}
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
if (currentSimklAnime.watched_episodes_count !== submitData.episodes) {
await SimklSyncEpisodes(currentSimklAnime, submitData.episodes).then(
(value: SimklAnime) => {
if (
currentSimklAnime.watched_episodes_count !== submitData.episodes
) {
await SimklSyncEpisodes(
currentSimklAnime,
submitData.episodes,
).then((value: SimklAnime) => {
AddAnimeServiceToTable({
id: `s-${value.show.ids.simkl}`,
title: value.show.title,
@@ -307,13 +320,14 @@
newValue = value;
return newValue;
});
},
);
});
}
if (currentSimklAnime.user_rating !== submitData.rating) {
await SimklSyncRating(currentSimklAnime, submitData.rating).then(
(value) => {
await SimklSyncRating(
currentSimklAnime,
submitData.rating,
).then((value) => {
AddAnimeServiceToTable({
id: `s-${value.show.ids.simkl}`,
title: value.show.title,
@@ -330,8 +344,7 @@
newValue = value;
return newValue;
});
},
);
});
}
if (currentSimklAnime.status !== submitData.status.simkl) {
@@ -358,19 +371,14 @@
});
}
}
} catch (error) {
console.error("Error submitting changes:", error);
} finally {
submitting.set(false);
submitSuccess.set(true);
watchlistNeedsRefresh.set(true);
setTimeout(() => submitSuccess.set(false), 2000);
}
};
const deleteEntries = async () => {
submitting.set(true);
try {
if (
isAniListLoggedIn &&
currentAniListAnime.data.MediaList.mediaId !== 0
@@ -419,14 +427,9 @@
notes: "",
});
}
} catch (error) {
console.error("Error deleting entries:", error);
} finally {
submitting.set(false);
submitSuccess.set(true);
watchlistNeedsRefresh.set(true);
setTimeout(() => submitSuccess.set(false), 2000);
}
};
let max = 999;
@@ -439,7 +442,8 @@
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0
) {
max =
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode - 1;
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode -
1;
}
</script>
@@ -475,18 +479,21 @@
on:click={() => {
currentAniListAnime.data.MediaList.progress -= 1;
if (
currentAniListAnime.data.MediaList.progress <
currentAniListAnime.data.MediaList.media.episodes
currentAniListAnime.data.MediaList
.progress <
currentAniListAnime.data.MediaList.media
.episodes
) {
startingAnilistStatusOption = statusOptions[0];
if (currentAniListAnime.data.MediaList.repeat === 0)
startingAnilistStatusOption =
statusOptions[0];
if (
currentAniListAnime.data.MediaList
.repeat === 0
)
completedAtDate = null;
}
}}
disabled={currentAniListAnime.data.MediaList.progress <= 0}
class={currentAniListAnime.data.MediaList.progress <= 0
? "border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
class="bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
>
<svg
class="w-3 h-3 text-white"
@@ -512,18 +519,22 @@
id="episodes"
class="border border-x-0 p-2.5 h-11 text-center text-sm block w-full placeholder-gray-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
{currentAniListAnime.data.MediaList.progress < 0 ||
(currentAniListAnime.data.MediaList.media.episodes > 0 &&
(currentAniListAnime.data.MediaList.media.episodes >
0 &&
currentAniListAnime.data.MediaList.progress >
currentAniListAnime.data.MediaList.media.episodes) ||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode > 0 &&
currentAniListAnime.data.MediaList.media
.episodes) ||
(currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode > 0 &&
currentAniListAnime.data.MediaList.progress >
currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode -
currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode -
1)
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
: 'bg-gray-700 hover:bg-gray-600 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500'} w-24"
bind:value={currentAniListAnime.data.MediaList.progress}
bind:value={
currentAniListAnime.data.MediaList.progress
}
required
/>
<button
@@ -533,38 +544,27 @@
on:click={() => {
currentAniListAnime.data.MediaList.progress += 1;
if (
currentAniListAnime.data.MediaList.media.episodes ===
currentAniListAnime.data.MediaList.media
.episodes ===
currentAniListAnime.data.MediaList.progress
) {
startingAnilistStatusOption = statusOptions[2];
startingAnilistStatusOption =
statusOptions[2];
completedAtDate = new Date();
}
if (currentAniListAnime.data.MediaList.progress - 1 === 0) {
startingAnilistStatusOption = statusOptions[0];
if (startedAtDate === null) startedAtDate = new Date();
if (
currentAniListAnime.data.MediaList
.progress -
1 ===
0
) {
startingAnilistStatusOption =
statusOptions[0];
if (startedAtDate === null)
startedAtDate = new Date();
}
}}
disabled={(currentAniListAnime.data.MediaList.media.episodes >
0 &&
currentAniListAnime.data.MediaList.progress >=
currentAniListAnime.data.MediaList.media.episodes) ||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode > 0 &&
currentAniListAnime.data.MediaList.progress >
currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode -
2)}
class={(currentAniListAnime.data.MediaList.media.episodes > 0 &&
currentAniListAnime.data.MediaList.progress >=
currentAniListAnime.data.MediaList.media.episodes) ||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode > 0 &&
currentAniListAnime.data.MediaList.progress >
currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode -
2)
? "border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
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"
>
<svg
class="w-3 h-3 text-white"
@@ -584,15 +584,16 @@
</button>
</div>
<div>
/ {currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode !== 0
? currentAniListAnime.data.MediaList.media.nextAiringEpisode
.episode - 1
/ {currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode !== 0
? currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode - 1
: currentAniListAnime.data.MediaList.media.episodes}
</div>
{#if currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0}
<div>
of {currentAniListAnime.data.MediaList.media.episodes}
of {currentAniListAnime.data.MediaList.media
.episodes}
</div>
{/if}
</div>
@@ -664,7 +665,8 @@
name="repeat"
min="0"
id="repeat"
class="border {currentAniListAnime.data.MediaList.repeat < 0
class="border {currentAniListAnime.data.MediaList
.repeat < 0
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
: 'border-gray-500 text-white focus:ring-blue-500 focus:border-blue-500'} text-sm rounded-lg block w-24 p-2.5 bg-gray-600 placeholder-gray-400 text-white"
bind:value={currentAniListAnime.data.MediaList.repeat}
@@ -728,7 +730,6 @@
Sync Changes
</button>
<button
type="button"
class="text-white bg-gray-800 border border-gray-600 focus:outline-none hover:bg-gray-700 focus:ring-4
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2
hover:border-gray-600"
@@ -828,7 +829,7 @@
<div class="flex m-5">
<div>
<h3 class="text-2xl">Genres</h3>
{#each currentAniListAnime.data.MediaList.media.genres || [] as genre}
{#each currentAniListAnime.data.MediaList.media.genres as genre}
<div>
<Badge large border color="blue" class="m-1 w-52">
<div>

View File

@@ -13,7 +13,7 @@
loginToSimkl,
logoutOfAniList,
logoutOfMAL,
logoutOfSimkl,
logoutOfSimkl
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import * as runtime from "../../wailsjs/runtime";
import type {MyAnimeListUser} from "../mal/types/MALTypes";
@@ -28,8 +28,8 @@
let isMALLoggedIn: boolean;
aniListUser.subscribe((value) => (currentAniListUser = value));
malUser.subscribe((value) => (currentMALUser = value));
simklUser.subscribe((value) => (currentSimklUser = value));
malUser.subscribe((value) => (currentMALUser = value))
simklUser.subscribe(value => currentSimklUser = value)
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
@@ -39,20 +39,17 @@
dropdown.classList.toggle("hidden");
if (!dropdown.classList.contains("hidden")) {
document.addEventListener("click", clickOutside);
document.addEventListener("click", clickOutside)
}
}
function clickOutside(event: Event): void {
let dropdown = document.querySelector("#userDropdown");
let toggleBtn = document.querySelector("#userDropdownButton");
let dropdown = document.querySelector("#userDropdown")
let toggleBtn = document.querySelector("#userDropdownButton")
if (
!dropdown.contains(event.target as Node) &&
!toggleBtn.contains(event.target as Node)
) {
dropdown.classList.add("hidden");
document.removeEventListener("click", clickOutside);
if (!dropdown.contains(event.target as Node) && !toggleBtn.contains(event.target as Node)) {
dropdown.classList.add("hidden")
document.removeEventListener("click", clickOutside)
}
}
</script>
@@ -63,9 +60,7 @@
<Avatar
src={currentAniListUser.data.Viewer.avatar.medium}
class="cursor-pointer"
dot={isAniListLoggedIn && isMALLoggedIn && isSimklLoggedIn
? { color: "green" }
: { color: "yellow" }}
dot={{ color: "green" }}
/>
{:else}
<Avatar class="cursor-pointer" dot={{ color: "red" }} />
@@ -92,19 +87,16 @@
on:click={logoutOfAniList}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-green-800 hover:text-white"
>
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser
.data.Viewer.name}
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser.data.Viewer.name}
</button>
</li>
{:else}
<li>
<button
on:click={() => {
dropdownUser();
loginToAniList();
<button on:click={() => {
dropdownUser()
loginToAniList()
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
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>
@@ -120,13 +112,11 @@
</li>
{:else}
<li>
<button
on:click={() => {
dropdownUser();
loginToMAL();
<button on:click={() => {
dropdownUser()
loginToMAL()
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
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>
@@ -137,19 +127,16 @@
on:click={logoutOfSimkl}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-indigo-800 hover:text-white"
>
<span class="maple-font text-lg text-indigo-200 mr-4">S</span>Logout {currentSimklUser
.user.name}
<span class="maple-font text-lg text-indigo-200 mr-4">S</span>Logout {currentSimklUser.user.name}
</button>
</li>
{:else}
<li>
<button
on:click={() => {
dropdownUser();
loginToSimkl();
<button on:click={() => {
dropdownUser()
loginToSimkl()
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
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>
@@ -158,8 +145,8 @@
<div class="py-2">
<button
on:click={() => {
dropdownUser();
ShowVersion();
dropdownUser()
ShowVersion()
}}
class="block px-4 py-2 w-full text-sm hover:bg-gray-600 text-gray-200 over:text-white"
>
@@ -174,4 +161,3 @@
</div>
</div>
</div>

View File

@@ -1,73 +0,0 @@
<script lang="ts">
import {
apiError,
isApiDown,
clearApiError,
setApiError,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import { CheckIfMALLoggedInAndSetUser } from "../helperModules/CheckIfMyAnimeListLoggedIn.svelte";
import { CheckIfSimklLoggedInAndSetUser } from "../helperModules/CheckIsSimklLoggedIn.svelte";
import { Modal, Button } from "flowbite-svelte";
let showModal = false;
$: if ($apiError) {
showModal = true;
}
async function handleRetry() {
const service = $apiError?.service;
clearApiError();
try {
if (service === "anilist") {
await CheckIfAniListLoggedInAndLoadWatchList();
} else if (service === "mal") {
await CheckIfMALLoggedInAndSetUser();
} else if (service === "simkl") {
await CheckIfSimklLoggedInAndSetUser();
}
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
setApiError(
service || "unknown",
`Retry failed: ${errorMsg}`,
undefined,
true,
);
}
}
function handleDismiss() {
clearApiError();
showModal = false;
}
</script>
{#if showModal && $apiError}
<Modal
open={showModal}
title="{$apiError.service.toUpperCase()} API Error"
size="md"
>
<div class="space-y-4">
<div class="p-4 bg-red-50 border border-red-200 rounded-lg">
<p class="text-red-800 font-medium">{$apiError.message}</p>
{#if $apiError.statusCode}
<p class="text-red-600 text-sm mt-2">
Status: {$apiError.statusCode}
</p>
{/if}
</div>
<p class="text-gray-600">
The application will remain open. You can retry the connection or
dismiss this message to continue with limited functionality.
</p>
</div>
<div slot="footer" class="flex gap-3 justify-end">
{#if $apiError.canRetry}
<Button on:click={handleRetry} class="bg-blue-600 hover:bg-blue-700">
Retry Connection
</Button>
{/if}
<Button on:click={handleDismiss} color="alternative">Dismiss</Button>
</div>
</Modal>
{/if}

View File

@@ -1,57 +1,46 @@
<script lang="ts">
import {
aniListLoggedIn,
aniListSort,
aniListWatchlist,
animePerPage,
watchListPage,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
import type {AniListCurrentUserWatchList} from "../anilist/types/AniListCurrentUserWatchListType"
import {GetAniListUserWatchingList} from "../../wailsjs/go/main/App";
import {MediaListSort} from "../anilist/types/AniListTypes";
let aniListWatchListLoaded: AniListCurrentUserWatchList;
let page: number;
let perPage: number;
let sort: string;
let aniListWatchListLoaded: AniListCurrentUserWatchList
let page: number
let perPage: number
watchListPage.subscribe((value) => (page = value));
animePerPage.subscribe((value) => (perPage = value));
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = value));
aniListSort.subscribe((value) => (sort = value));
watchListPage.subscribe(value => page = value)
animePerPage.subscribe(value => perPage = value)
aniListWatchlist.subscribe((value) => aniListWatchListLoaded = value)
const perPageOptions = [10, 20, 50];
const perPageOptions = [10, 20, 50]
function ChangeWatchListPage(newPage: number) {
GetAniListUserWatchingList(newPage, perPage, sort).then((result) => {
watchListPage.set(newPage);
aniListWatchlist.set(result);
aniListLoggedIn.set(true);
});
GetAniListUserWatchingList(newPage, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
watchListPage.set(newPage)
aniListWatchlist.set(result)
aniListLoggedIn.set(true)
})
}
function changePage(
e: KeyboardEvent & { currentTarget: HTMLInputElement },
): void {
if (
(e.key === "Enter" || e.key === "Tab") &&
Number(e.currentTarget.value) !== page
)
ChangeWatchListPage(Number(e.currentTarget.value));
function changePage(e): void {
if ((e.key === "Enter" || e.key === "Tab") && Number(e.target.value) !== page) ChangeWatchListPage(Number(e.target.value))
}
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);
},
);
function changeCountPerPage(e): void {
GetAniListUserWatchingList(1, Number(e.target.value), MediaListSort.UpdatedTimeDesc).then((result) => {
animePerPage.set(Number(e.target.value))
watchListPage.set(1)
aniListWatchlist.set(result)
aniListLoggedIn.set(true)
})
}
</script>
<div class="mb-8">
@@ -60,19 +49,15 @@
<ul class="inline-flex -space-x-px text-base h-10">
{#if page === 1}
<li>
<button
disabled
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 cursor-default"
>
<button disabled
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 cursor-default">
Previous
</button>
</li>
{:else}
<li>
<button
on:click={() => ChangeWatchListPage(page - 1)}
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
>
<button on:click={() => ChangeWatchListPage(page-1)}
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">
Previous
</button>
</li>
@@ -80,37 +65,27 @@
{#each {length: aniListWatchListLoaded.data.Page.pageInfo.lastPage} as _, i}
{#if i + 1 === page}
<li>
<button
on:click={() => ChangeWatchListPage(i + 1)}
class="flex items-center justify-center px-4 h-10 leading-tight border hover:bg-gray-100 border-gray-700 bg-gray-700 text-white"
>{i + 1}</button
>
<button on:click={() => ChangeWatchListPage(i+1)}
class="flex items-center justify-center px-4 h-10 leading-tight border bg-gray-100 border-gray-700 bg-gray-700 text-white">{i + 1}</button>
</li>
{:else}
<li>
<button
on:click={() => ChangeWatchListPage(i + 1)}
class="flex items-center justify-center px-4 h-10 leading-tight border dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
>{i + 1}</button
>
<button on:click={() => ChangeWatchListPage(i+1)}
class="flex items-center justify-center px-4 h-10 leading-tight border dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">{i + 1}</button>
</li>
{/if}
{/each}
{#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage}
<li>
<button
disabled
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 cursor-default"
>
<button disabled
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 cursor-default">
Next
</button>
</li>
{:else}
<li>
<button
on:click={() => ChangeWatchListPage(page + 1)}
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
>
<button on:click={() => ChangeWatchListPage(page+1)}
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">
Next
</button>
</li>
@@ -120,12 +95,8 @@
{/if}
<div class="flex mt-5">
<div class="w-20 mx-auto">
<select
bind:value={perPage}
on:change={(e) => changeCountPerPage(e)}
id="countPerPage"
class="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
>
<select bind:value={perPage} on:change={(e) => changeCountPerPage(e)} id="countPerPage"
class="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500">
{#each perPageOptions as option}
<option value={option}>
{option}
@@ -137,81 +108,35 @@
<div>
<div>Total Anime: {aniListWatchListLoaded.data.Page.pageInfo.total}</div>
{#if aniListWatchListLoaded.data.Page.pageInfo.lastPage <= 12}
<div class="md:hidden">
Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}
</div>
<div class="md:hidden">Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
{:else}
<div>
Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}
</div>
<div>Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
{/if}
</div>
<div class="max-w-xs mx-auto">
<div class="relative flex items-center max-w-[11rem]">
<button
type="button"
id="decrement-button"
on:click={() => ChangeWatchListPage(page - 1)}
class={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"
/>
<button type="button" id="decrement-button" on:click={() => ChangeWatchListPage(page-1)}
class="bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none">
<svg class="w-3 h-3 text-white" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M1 1h16"/>
</svg>
</button>
<input
type="number"
min="1"
max={aniListWatchListLoaded.data.Page.pageInfo.lastPage}
on:keydown={changePage}
id="page-counter"
<input type="number" min="1" max="{aniListWatchListLoaded.data.Page.pageInfo.lastPage}"
on:keydown={changePage} id="page-counter"
class="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none border-x-0 h-11 font-medium text-center text-sm block w-full pb-6 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
value={page}
required
/>
<div
class="absolute bottom-1 start-1/2 -translate-x-1/2 rtl:translate-x-1/2 flex items-center text-xs text-gray-400 space-x-1 rtl:space-x-reverse"
>
value={page} required/>
<div class="absolute bottom-1 start-1/2 -translate-x-1/2 rtl:translate-x-1/2 flex items-center text-xs text-gray-400 space-x-1 rtl:space-x-reverse">
<span>Page #</span>
</div>
<button
type="button"
id="increment-button"
on:click={() => ChangeWatchListPage(page + 1)}
class={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"
/>
<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>

View File

@@ -6,50 +6,30 @@
loading,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import {push} from "svelte-spa-router";
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
import type {AniListCurrentUserWatchList} from "../anilist/types/AniListCurrentUserWatchListType"
import {Rating} from "flowbite-svelte";
import loader from "../helperFunctions/loader";
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import loader from '../helperFunctions/loader'
let isAniListLoggedIn: boolean;
let aniListWatchListLoaded: AniListCurrentUserWatchList;
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = value));
let isAniListLoggedIn: boolean
let aniListWatchListLoaded: AniListCurrentUserWatchList
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
aniListWatchlist.subscribe((value) => aniListWatchListLoaded = value)
</script>
<div>
{#if isAniListLoggedIn}
<div
class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center"
>
<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="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center">
<h1 class="text-left text-xl font-bold mb-4">Your AniList WatchList</h1>
<div
class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8"
>
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
{#each aniListWatchListLoaded.data.Page.mediaList as media}
<div
use:loader={loading}
class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7"
>
<div use:loader={loading} class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7">
<div class="flex flex-col items-center group">
<button
on:click={() => {
push(`#/anime/${media.media.id}`);
<button on:click={() => {
push(`#/anime/${media.media.id}`)
// loading.set(true)
// GetAniListSingleItem(media.media.id, true).then(() => {
// loading.set(false)
@@ -57,38 +37,27 @@
// })
}}
>
<img
class="rounded-lg w-[230px] h-[330px] object-cover"
src={media.media.coverImage.large}
alt={media.media.title.english === ""
? media.media.title.romaji
: media.media.title.english}
/>
<img class="rounded-lg" src={media.media.coverImage.large} alt={
media.media.title.english === "" ?
media.media.title.romaji :
media.media.title.english
}/>
</button>
<Rating
id="anime-rating"
total={5}
size={35}
rating={media.score / 2.0}
/>
<button
class="mt-4 text-md font-semibold text-white-700"
on:click={() => GetAnimeSingleItem(media.media.id, true)}
>
{media.media.title.english === ""
? media.media.title.romaji
: media.media.title.english}
<Rating id="anime-rating" total={5} size={35} rating={media.score/2.0}/>
<button class="mt-4 text-md font-semibold text-white-700"
on:click={() => GetAnimeSingleItem(media.media.id, true)}>
{
media.media.title.english === "" ?
media.media.title.romaji :
media.media.title.english
}
</button>
<p class="mt-1 text-lg font-medium text-white-900">
{media.progress}
/ {media.media.nextAiringEpisode.episode !== 0
? media.media.nextAiringEpisode.episode - 1
: media.media.episodes}
</p>
<p class="mt-1 text-lg font-medium text-white-900">{media.progress}
/ {media.media.nextAiringEpisode.episode !== 0 ?
media.media.nextAiringEpisode.episode - 1 : media.media.episodes}</p>
{#if media.media.episodes > 0}
<p class="mt-1 text-lg font-medium text-white-900">
Total Episodes: {media.media.episodes}
</p>
<p class="mt-1 text-lg font-medium text-white-900">Total
Episodes: {media.media.episodes}</p>
{/if}
</div>
</div>

View File

@@ -1,81 +1,34 @@
<script lang="ts" context="module">
import {
CheckIfAniListLoggedIn,
GetAniListLoggedInUser,
GetAniListUserWatchingList,
} from "../../wailsjs/go/main/App";
import {
aniListUser,
watchListPage,
animePerPage,
aniListPrimary,
aniListLoggedIn,
aniListWatchlist,
aniListSort,
clearApiError,
setApiError,
} from "./GlobalVariablesAndHelperFunctions.svelte";
import {CheckIfAniListLoggedIn, GetAniListLoggedInUser, GetAniListUserWatchingList} from "../../wailsjs/go/main/App";
import {MediaListSort} from "../anilist/types/AniListTypes";
import { aniListUser, watchListPage, animePerPage, aniListPrimary, aniListLoggedIn, aniListWatchlist } from "./GlobalVariablesAndHelperFunctions.svelte"
let isAniListPrimary: boolean;
let page: number;
let perPage: number;
let sort: string;
let isAniListPrimary: boolean
let page: number
let perPage: number
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
watchListPage.subscribe((value) => (page = value));
animePerPage.subscribe((value) => (perPage = value));
aniListSort.subscribe((value) => (sort = value));
aniListPrimary.subscribe(value => isAniListPrimary = value)
watchListPage.subscribe(value => page = value)
animePerPage.subscribe(value => perPage = value)
export const LoadAniListUser = async () => {
try {
await GetAniListLoggedInUser().then((user) => {
aniListUser.set(user);
});
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
setApiError(
"anilist",
`Failed to load user: ${errorMsg}`,
undefined,
true,
);
throw err;
await GetAniListLoggedInUser().then(user => {
aniListUser.set(user)
})
}
};
export const LoadAniListWatchList = async () => {
try {
const watchList = await GetAniListUserWatchingList(page, perPage, sort);
aniListWatchlist.set(watchList);
clearApiError();
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
setApiError(
"anilist",
`Failed to load watch list: ${errorMsg}`,
undefined,
true,
);
throw err;
await GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((watchList) => {
aniListWatchlist.set(watchList)
})
}
};
export const CheckIfAniListLoggedInAndLoadWatchList = async () => {
try {
const loggedIn = await CheckIfAniListLoggedIn();
const loggedIn = await CheckIfAniListLoggedIn()
if (loggedIn) {
await LoadAniListUser();
if (isAniListPrimary) await LoadAniListWatchList();
await LoadAniListUser()
if (isAniListPrimary) await LoadAniListWatchList()
}
aniListLoggedIn.set(loggedIn);
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
setApiError(
"anilist",
`Authentication failed: ${errorMsg}`,
undefined,
true,
);
aniListLoggedIn.set(false);
aniListLoggedIn.set(loggedIn)
}
};
</script>

View File

@@ -10,203 +10,153 @@
LogoutMyAnimeList,
LogoutSimkl,
SimklGetUserWatchlist,
SimklSearch,
SimklSearch
} from "../../wailsjs/go/main/App";
import type {
AniListCurrentUserWatchList,
AniListGetSingleAnime,
AniListGetSingleAnime
} from "../anilist/types/AniListCurrentUserWatchListType.js";
import { writable } from "svelte/store";
import type {
SimklAnime,
SimklUser,
SimklWatchList,
} from "../simkl/types/simklTypes";
import {
type AniListUser,
MediaListSort,
} from "../anilist/types/AniListTypes";
import type {
MALAnime,
MALWatchlist,
MyAnimeListUser,
} from "../mal/types/MALTypes";
import {writable} from 'svelte/store'
import type {SimklAnime, SimklUser, SimklWatchList} from "../simkl/types/simklTypes";
import {type AniListUser, MediaListSort} from "../anilist/types/AniListTypes";
import type {MALAnime, MALWatchlist, MyAnimeListUser} from "../mal/types/MALTypes";
import type {TableItems} from "../helperTypes/TableTypes";
import {AniListGetSingleAnimeDefaultData} from "../helperDefaults/AniListGetSingleAnime";
export let aniListAnime = writable(AniListGetSingleAnimeDefaultData);
export let title = writable("");
export let aniListLoggedIn = writable(false);
export let simklLoggedIn = writable(false);
export let malLoggedIn = writable(false);
export let simklWatchList = writable({} as SimklWatchList);
export let aniListPrimary = writable(true);
export let simklPrimary = writable(false);
export let malPrimary = writable(false);
export let simklUser = writable({} as SimklUser);
export let aniListUser = writable({} as AniListUser);
export let malUser = writable({} as MyAnimeListUser);
export let aniListWatchlist = writable({} as AniListCurrentUserWatchList);
export let malWatchList = writable({} as MALWatchlist);
export let malAnime = writable({} as MALAnime);
export let simklAnime = writable({} as SimklAnime);
export let loading = writable(false);
export let tableItems = writable([] as TableItems);
export let watchlistNeedsRefresh = writable(false);
export let aniListSort = writable(MediaListSort.UpdatedTimeDesc);
export let aniListAnime = writable(AniListGetSingleAnimeDefaultData)
export let title = writable("")
export let aniListLoggedIn = writable(false)
export let simklLoggedIn = writable(false)
export let malLoggedIn = writable(false)
export let simklWatchList = writable({} as SimklWatchList)
export let aniListPrimary = writable(true)
export let simklPrimary = writable(false)
export let malPrimary = writable(false)
export let simklUser = writable({} as SimklUser)
export let aniListUser = writable({} as AniListUser)
export let malUser = writable({} as MyAnimeListUser)
export let aniListWatchlist = writable({} as AniListCurrentUserWatchList)
export let malWatchList = writable({} as MALWatchlist)
export let malAnime = writable({} as MALAnime)
export let simklAnime = writable({} as SimklAnime)
export let loading = writable(false)
export let tableItems = writable([] as TableItems)
export let watchListPage = writable(1);
export let animePerPage = writable(20);
export let watchListPage = writable(1)
export let animePerPage = writable(20)
let isAniListPrimary: boolean;
let page: number;
let perPage: number;
let sort: string;
let aniWatchlist: AniListCurrentUserWatchList;
let currentAniListAnime: AniListGetSingleAnime;
let isAniListPrimary: boolean
let page: number
let perPage: number
let aniWatchlist: AniListCurrentUserWatchList
let currentAniListAnime: AniListGetSingleAnime
let isMalLoggedIn: boolean;
let isSimklLoggedIn: boolean;
let isMalLoggedIn: boolean
let isSimklLoggedIn: boolean
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
watchListPage.subscribe((value) => (page = value));
animePerPage.subscribe((value) => (perPage = value));
aniListWatchlist.subscribe((value) => (aniWatchlist = value));
malLoggedIn.subscribe((value) => (isMalLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
aniListAnime.subscribe((value) => (currentAniListAnime = value));
aniListSort.subscribe((value) => (sort = value));
aniListPrimary.subscribe(value => isAniListPrimary = value)
watchListPage.subscribe(value => page = value)
animePerPage.subscribe(value => perPage = value)
aniListWatchlist.subscribe(value => aniWatchlist = value)
malLoggedIn.subscribe(value => isMalLoggedIn = value)
simklLoggedIn.subscribe(value => isSimklLoggedIn = value)
aniListAnime.subscribe(value => currentAniListAnime = value)
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<""> {
await GetAniListItem(aniId, login).then((aniListResult) => {
let finalResult: AniListGetSingleAnime;
finalResult = aniListResult;
export async function GetAnimeSingleItem(aniId: number, login: boolean): Promise<""> {
await GetAniListItem(aniId, login).then(aniListResult => {
let finalResult: AniListGetSingleAnime
finalResult = aniListResult
if (login === false) {
finalResult.data.MediaList.status = "";
finalResult.data.MediaList.score = 0;
finalResult.data.MediaList.progress = 0;
finalResult.data.MediaList.notes = "";
finalResult.data.MediaList.repeat = 0;
finalResult.data.MediaList.startedAt.day = 0;
finalResult.data.MediaList.startedAt.month = 0;
finalResult.data.MediaList.startedAt.year = 0;
finalResult.data.MediaList.completedAt.day = 0;
finalResult.data.MediaList.completedAt.month = 0;
finalResult.data.MediaList.completedAt.year = 0;
finalResult.data.MediaList.status = ""
finalResult.data.MediaList.score = 0
finalResult.data.MediaList.progress = 0
finalResult.data.MediaList.notes = ""
finalResult.data.MediaList.repeat = 0
finalResult.data.MediaList.startedAt.day = 0
finalResult.data.MediaList.startedAt.month = 0
finalResult.data.MediaList.startedAt.year = 0
finalResult.data.MediaList.completedAt.day = 0
finalResult.data.MediaList.completedAt.month = 0
finalResult.data.MediaList.completedAt.year = 0
}
aniListAnime.set(finalResult);
title.set(
currentAniListAnime.data.MediaList.media.title.english === ""
? currentAniListAnime.data.MediaList.media.title.romaji
: currentAniListAnime.data.MediaList.media.title.english,
);
});
aniListAnime.set(finalResult)
title.set(currentAniListAnime.data.MediaList.media.title.english === "" ?
currentAniListAnime.data.MediaList.media.title.romaji :
currentAniListAnime.data.MediaList.media.title.english)
})
if (isMalLoggedIn) {
await GetMyAnimeListAnime(
currentAniListAnime.data.MediaList.media.idMal,
).then((malResult) => {
malAnime.set(malResult);
});
await GetMyAnimeListAnime(currentAniListAnime.data.MediaList.media.idMal).then(malResult => {
malAnime.set(malResult)
})
}
if (isSimklLoggedIn) {
await SimklSearch(currentAniListAnime.data.MediaList).then(
(value: SimklAnime) => {
simklAnime.set(value);
},
);
await SimklSearch(currentAniListAnime.data.MediaList).then((value: SimklAnime) => {
simklAnime.set(value)
})
}
return "";
return ""
}
export function loginToSimkl(): void {
GetSimklLoggedInUser().then((user) => {
GetSimklLoggedInUser().then(user => {
if (Object.keys(user).length === 0) {
simklLoggedIn.set(false);
simklLoggedIn.set(false)
} else {
simklUser.set(user);
SimklGetUserWatchlist().then((result) => {
simklWatchList.set(result);
simklLoggedIn.set(true);
});
simklUser.set(user)
SimklGetUserWatchlist().then(result => {
simklWatchList.set(result)
simklLoggedIn.set(true)
})
}
});
})
}
export function loginToAniList(): void {
GetAniListLoggedInUser().then((result) => {
aniListUser.set(result);
GetAniListLoggedInUser().then(result => {
aniListUser.set(result)
if (isAniListPrimary) {
GetAniListUserWatchingList(page, perPage, sort).then((result) => {
aniListWatchlist.set(result);
aniListLoggedIn.set(true);
});
GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
aniListWatchlist.set(result)
aniListLoggedIn.set(true)
})
} else {
aniListLoggedIn.set(true);
aniListLoggedIn.set(true)
}
});
})
}
export function loginToMAL(): void {
GetMyAnimeListLoggedInUser().then((result) => {
malUser.set(result);
malLoggedIn.set(true);
});
GetMyAnimeListLoggedInUser().then(result => {
malUser.set(result)
malLoggedIn.set(true)
})
}
export function logoutOfAniList(): void {
LogoutAniList().then((result) => {
console.log(result);
LogoutAniList().then(result => {
console.log(result)
if (Object.keys(aniWatchlist).length !== 0) {
aniListWatchlist.set({} as AniListCurrentUserWatchList);
aniListWatchlist.set({} as AniListCurrentUserWatchList)
}
aniListUser.set({} as AniListUser);
aniListLoggedIn.set(false);
});
aniListUser.set({} as AniListUser)
aniListLoggedIn.set(false)
})
}
export function logoutOfMAL(): void {
LogoutMyAnimeList().then((result) => {
console.log(result);
malUser.set({} as MyAnimeListUser);
malLoggedIn.set(false);
});
LogoutMyAnimeList().then(result => {
console.log(result)
malUser.set({} as MyAnimeListUser)
malLoggedIn.set(false)
})
}
export function logoutOfSimkl(): void {
LogoutSimkl().then((result) => {
console.log(result);
simklUser.set({} as SimklUser);
simklLoggedIn.set(false);
});
LogoutSimkl().then(result => {
console.log(result)
simklUser.set({} as SimklUser)
simklLoggedIn.set(false)
})
}
</script>

View File

@@ -5,46 +5,16 @@
aniListLoggedIn,
aniListPrimary,
loading,
isApiDown,
apiError,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import loader from "../helperFunctions/loader";
import loader from '../helperFunctions/loader'
let isAniListPrimary: boolean;
let isAniListLoggedIn: boolean;
let isAniListPrimary: boolean
let isAniListLoggedIn: boolean
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
aniListPrimary.subscribe((value) => isAniListPrimary = value)
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
</script>
{#if $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}
{#if isAniListLoggedIn && isAniListPrimary}
<div class="container py-10">
<Pagination />
<WatchList />

View File

@@ -0,0 +1,10 @@
// vite.config.ts
import { defineConfig } from "file:///home/nymusicman/Code/AniTrack/frontend/node_modules/vite/dist/node/index.js";
import { svelte } from "file:///home/nymusicman/Code/AniTrack/frontend/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
var vite_config_default = defineConfig({
plugins: [svelte()]
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ueW11c2ljbWFuL0NvZGUvQW5pVHJhY2svZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL255bXVzaWNtYW4vQ29kZS9BbmlUcmFjay9mcm9udGVuZC92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ueW11c2ljbWFuL0NvZGUvQW5pVHJhY2svZnJvbnRlbmQvdml0ZS5jb25maWcudHNcIjtpbXBvcnQge2RlZmluZUNvbmZpZ30gZnJvbSAndml0ZSdcbmltcG9ydCB7c3ZlbHRlfSBmcm9tICdAc3ZlbHRlanMvdml0ZS1wbHVnaW4tc3ZlbHRlJ1xuXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW3N2ZWx0ZSgpXVxufSlcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBdVMsU0FBUSxvQkFBbUI7QUFDbFUsU0FBUSxjQUFhO0FBR3JCLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVMsQ0FBQyxPQUFPLENBQUM7QUFDcEIsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K

View File

@@ -12,6 +12,6 @@
},
"info": {
"productName": "AniTrack",
"productVersion": "0.7.0"
"productVersion": "0.5.1"
}
}