36 Commits

Author SHA1 Message Date
dde5d20537 turned off webgl for this app due to breakage 2025-01-17 22:05:11 -05:00
314646e7f5 upgraded go packages 2025-01-17 22:04:38 -05:00
72dfbf4a03 spelling correct 2025-01-17 20:39:18 -05:00
d4ad4bc430 updated go packages 2025-01-17 20:39:06 -05:00
49681f3ffb point appicon to anitrack logo 2024-12-15 00:31:34 -05:00
d98d0e77c1 wails autofix based on changes 2024-12-15 00:31:10 -05:00
8e57b4b259 added a simple desktop file to repo 2024-12-15 00:30:53 -05:00
1e1c891173 renamed appicon to AniTrack 2024-12-15 00:30:38 -05:00
5ee9c42352 cleaned up errors in go code 2024-12-15 00:19:48 -05:00
23ec111c60 upped minor version for episode button 2024-12-14 13:50:09 -05:00
f24ee9edfd improved episode input in Anime Page 2024-12-14 13:48:55 -05:00
1fd453f399 upped version number for bug fix 2024-12-06 16:32:06 -05:00
3ab77ea8d3 fixed bug in episode input when 0 2024-12-06 16:30:53 -05:00
3edfed6272 updated version number to distinguish ep count 2024-12-01 19:12:48 -05:00
aa81102194 added currentl episode release to anime single page 2024-11-14 20:11:41 -05:00
0c90c3e29d added check for 403 in get anilist watchlist 2024-11-14 20:10:41 -05:00
2292ae32c2 updated bruno files 2024-11-14 18:50:51 -05:00
31cc19ba7a removed logged in buttons from navigation 2024-10-26 21:33:42 -04:00
5c712454d5 fixed bug in tailwind build and updated minor version 2024-10-26 19:55:13 -04:00
10430caddf added login, icons and theming to user dropdown 2024-10-26 18:02:05 -04:00
9a6c844691 added code plist to see window on mac 2024-10-26 18:01:36 -04:00
476507a695 added MapleMono font to project 2024-10-26 18:01:04 -04:00
1fdb859f05 upgraded vite from vulnerability 2024-10-18 23:21:59 -04:00
064a2c7f7d added info for mac keychain 2024-10-18 22:06:33 -04:00
bd39268c0a bumped minor version number 2024-10-02 19:32:24 -04:00
2cffd54c4d made anime Id in table a link to their respective sites 2024-10-02 19:26:52 -04:00
7e3369d0f0 fixed buttons colors 2024-10-01 18:53:34 -04:00
e229311190 made entire app only work in dark mode 2024-10-01 16:57:42 -04:00
753ecd968e made header permanently dark mode 2024-10-01 16:01:13 -04:00
30c48dcf9b Added versions numbers and display on titlebar 2024-10-01 15:53:57 -04:00
9b28f2fb0a test simkl urls 2024-10-01 15:52:32 -04:00
0bf784562a fixed bug that was stopping anilist from logging in 2024-10-01 15:07:02 -04:00
ea2c4475de changed url for simkl to pull all lists, not just watching fixed several simkl bugs 2024-09-24 18:44:42 -04:00
572366eb91 updated table when entries are deleted and fixed simkl watchlist 2024-09-18 15:06:35 -04:00
77dc48fcf2 removed unnecessary println and console.log 2024-09-18 14:10:49 -04:00
c7694900e3 added ability to delete entries. Added MAL RefreshToken Function 2024-09-18 14:06:11 -04:00
36 changed files with 1250 additions and 682 deletions

View File

@ -17,26 +17,29 @@ func AniListQuery(body interface{}, login bool) (json.RawMessage, string) {
if login && (AniListJWT{}) != aniListJwt { if login && (AniListJWT{}) != aniListJwt {
response.Header.Add("Authorization", "Bearer "+aniListJwt.AccessToken) response.Header.Add("Authorization", "Bearer "+aniListJwt.AccessToken)
} else if login { } else if login {
return nil, "Please login to anilist to make this request" return nil, "Please login to AniList to make this request"
} }
response.Header.Add("Content-Type", "application/json") response.Header.Add("Content-Type", "application/json")
response.Header.Add("Accept", "application/json") response.Header.Add("Accept", "application/json")
client := &http.Client{} client := &http.Client{}
res, reserr := client.Do(response) res, resErr := client.Do(response)
if reserr != nil { if resErr != nil {
log.Printf("Failed at res, %s\n", err) log.Printf("Failed at res, %s\n", err)
} }
defer res.Body.Close() defer res.Body.Close()
returnedBody, err := io.ReadAll(res.Body) returnedBody, err := io.ReadAll(res.Body)
if err != nil {
return nil, "Could not read the returned body."
}
return returnedBody, res.Status return returnedBody, res.Status
} }
func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime { func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
var user = a.GetAniListLoggedInUser() user := a.GetAniListLoggedInUser()
var neededVariables interface{} var neededVariables interface{}
@ -141,11 +144,11 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
returnedBody, status := AniListQuery(body, login) returnedBody, status := AniListQuery(body, login)
var post AniListGetSingleAnime var post AniListGetSingleAnime
if status == "404 Not Found" && login == false { if status == "404 Not Found" && !login {
return post return post
} }
if status == "404 Not Found" && login { if status == "404 Not Found" {
post = a.GetAniListItem(aniId, false) post = a.GetAniListItem(aniId, false)
} }
@ -154,7 +157,7 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
log.Printf("Failed at unmarshal, %s\n", err) log.Printf("Failed at unmarshal, %s\n", err)
} }
if login == false { if !login {
post.Data.MediaList.UserID = user.Data.Viewer.ID post.Data.MediaList.UserID = user.Data.Viewer.ID
post.Data.MediaList.Status = "" post.Data.MediaList.Status = ""
post.Data.MediaList.StartedAt.Year = 0 post.Data.MediaList.StartedAt.Year = 0
@ -249,7 +252,7 @@ func (a *App) AniListSearch(query string) any {
} }
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList { func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList {
var user = a.GetAniListLoggedInUser() user := a.GetAniListLoggedInUser()
type Variables struct { type Variables struct {
Page int `json:"page"` Page int `json:"page"`
PerPage int `json:"perPage"` PerPage int `json:"perPage"`
@ -360,14 +363,25 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
}, },
} }
returnedBody, _ := AniListQuery(body, true) returnedBody, status := AniListQuery(body, true)
var badPost struct {
Errors []struct {
Message string `json:"message"`
Status int `json:"status"`
Locations []struct {
Line int `json:"line"`
Column int `json:"column"`
} `json:"locations"`
} `json:"errors"`
Data any `json:"data"`
}
var post AniListCurrentUserWatchList var post AniListCurrentUserWatchList
if status == "200 OK" {
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)
} }
// Getting the real total, finding the real last page and storing that in the Page info // Getting the real total, finding the real last page and storing that in the Page info
statuses := post.Data.Page.MediaList[0].User.Statistics.Anime.Statuses statuses := post.Data.Page.MediaList[0].User.Statistics.Anime.Statuses
var total int var total int
@ -381,6 +395,15 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
post.Data.Page.PageInfo.Total = total post.Data.Page.PageInfo.Total = total
post.Data.Page.PageInfo.LastPage = lastPage post.Data.Page.PageInfo.LastPage = lastPage
}
if status == "403 Forbidden" {
err := json.Unmarshal(returnedBody, &badPost)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
}
log.Fatal(badPost.Errors[0].Message)
}
return post return post
} }

View File

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -20,17 +21,21 @@ var aniListJwt AniListJWT
var aniRing, _ = keyring.Open(keyring.Config{ var aniRing, _ = keyring.Open(keyring.Config{
ServiceName: "AniTrack", ServiceName: "AniTrack",
KeychainName: "AniTrack",
KeychainSynchronizable: false,
KeychainTrustApplication: true,
KeychainAccessibleWhenUnlocked: true,
}) })
var aniCtxShutdown, aniCancel = context.WithCancel(context.Background()) var aniCtxShutdown, aniCancel = context.WithCancel(context.Background())
func (a *App) CheckIfAniListLoggedIn() bool { func (a *App) CheckIfAniListLoggedIn() bool {
if (AniListJWT{} == aniListJwt) { if (AniListJWT{} == aniListJwt) {
tokenType, err := aniRing.Get("anilistTokenType") tokenType, tokenErr := aniRing.Get("anilistTokenType")
expiresIn, err := aniRing.Get("anilistTokenExpiresIn") expiresIn, expiresInErr := aniRing.Get("anilistTokenExpiresIn")
accessToken, err := aniRing.Get("anilistAccessToken") refreshToken, refreshTokenErr := aniRing.Get("anilistRefreshToken")
refreshToken, err := aniRing.Get("anilistRefreshToken") accessToken, accessTokenErr := aniRing.Get("anilistAccessToken")
if err != nil || len(accessToken.Data) == 0 { if (tokenErr != nil || expiresInErr != nil || refreshTokenErr != nil || accessTokenErr != nil) || len(accessToken.Data) == 0 {
return false return false
} else { } else {
aniListJwt.TokenType = string(tokenType.Data) aniListJwt.TokenType = string(tokenType.Data)
@ -46,11 +51,11 @@ func (a *App) CheckIfAniListLoggedIn() bool {
func (a *App) AniListLogin() { func (a *App) AniListLogin() {
if (AniListJWT{} == aniListJwt) { if (AniListJWT{} == aniListJwt) {
tokenType, err := aniRing.Get("anilistTokenType") tokenType, tokenErr := aniRing.Get("anilistTokenType")
expiresIn, err := aniRing.Get("anilistTokenExpiresIn") expiresIn, expiresInErr := aniRing.Get("anilistTokenExpiresIn")
accessToken, err := aniRing.Get("anilistAccessToken") refreshToken, refreshTokenErr := aniRing.Get("anilistRefreshToken")
refreshToken, err := aniRing.Get("anilistRefreshToken") accessToken, accessTokenErr := aniRing.Get("anilistAccessToken")
if err != nil || len(accessToken.Data) == 0 { if (tokenErr != nil || expiresInErr != nil || refreshTokenErr != nil || accessTokenErr != nil) || len(accessToken.Data) == 0 {
getAniListCodeUrl := "https://anilist.co/api/v2/oauth/authorize?client_id=" + Environment.ANILIST_APP_ID + "&redirect_uri=" + Environment.ANILIST_CALLBACK_URI + "&response_type=code" getAniListCodeUrl := "https://anilist.co/api/v2/oauth/authorize?client_id=" + Environment.ANILIST_APP_ID + "&redirect_uri=" + Environment.ANILIST_CALLBACK_URI + "&response_type=code"
runtime.BrowserOpenURL(*wailsContext, getAniListCodeUrl) runtime.BrowserOpenURL(*wailsContext, getAniListCodeUrl)
@ -78,7 +83,6 @@ func (a *App) handleAniListCallback(wg *sync.WaitGroup) {
default: default:
} }
content := r.FormValue("code") content := r.FormValue("code")
if content != "" { if content != "" {
aniListJwt = getAniListAuthorizationToken(content) aniListJwt = getAniListAuthorizationToken(content)
_ = aniRing.Set(keyring.Item{ _ = aniRing.Set(keyring.Item{
@ -120,7 +124,7 @@ func (a *App) handleAniListCallback(wg *sync.WaitGroup) {
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err) log.Fatalf("listen: %s\n", err)
} }
fmt.Println("Shutting down...") fmt.Println("Shutting down...")
@ -145,19 +149,21 @@ func getAniListAuthorizationToken(content string) AniListJWT {
if err != nil { if err != nil {
log.Printf("Failed at response, %s\n", err) log.Printf("Failed at response, %s\n", err)
} }
response.Header.Add("content-type", "application/x-www-form-urlencoded") response.Header.Add("Content-type", "application/x-www-form-urlencoded")
response.Header.Add("Content-Type", "application/json")
response.Header.Add("Accept", "application/json") response.Header.Add("Accept", "application/json")
client := &http.Client{} client := &http.Client{}
res, reserr := client.Do(response) res, resErr := client.Do(response)
if reserr != nil { if resErr != nil {
log.Printf("Failed at res, %s\n", err) log.Printf("Failed at res, %s\n", err)
} }
defer res.Body.Close() defer res.Body.Close()
returnedBody, err := io.ReadAll(res.Body) returnedBody, err := io.ReadAll(res.Body)
if err != nil {
log.Printf("Could not read returned body, %s\n.", err)
}
var post AniListJWT var post AniListJWT
err = json.Unmarshal(returnedBody, &post) err = json.Unmarshal(returnedBody, &post)
@ -202,13 +208,12 @@ func (a *App) GetAniListLoggedInUser() AniListUser {
func (a *App) LogoutAniList() string { func (a *App) LogoutAniList() string {
if (AniListJWT{} != aniListJwt) { if (AniListJWT{} != aniListJwt) {
err := aniRing.Remove("anilistTokenType") typeErr := aniRing.Remove("anilistTokenType")
err = aniRing.Remove("anilistTokenExpiresIn") expiresInErr := aniRing.Remove("anilistTokenExpiresIn")
err = aniRing.Remove("anilistAccessToken") accessTokenErr := aniRing.Remove("anilistAccessToken")
err = aniRing.Remove("anilistRefreshToken") refreshTokenErr := aniRing.Remove("anilistRefreshToken")
if typeErr != nil || expiresInErr != nil || accessTokenErr != nil || refreshTokenErr != nil {
if err != nil { fmt.Println("AniList Logout Failed")
fmt.Println("AniList Logout Failed", err)
} }
aniListJwt = AniListJWT{} aniListJwt = AniListJWT{}
} }

View File

@ -18,7 +18,6 @@ func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage,
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)
fmt.Println(myAnimeListJwt.AccessToken)
resp, err := client.Do(req) resp, err := client.Do(req)
@ -92,7 +91,6 @@ func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListS
} }
func (a *App) GetMyAnimeListAnime(id int) MALAnime { func (a *App) GetMyAnimeListAnime(id int) MALAnime {
fmt.Println(id)
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) respBody, respStatus := MALHelper("GET", malUrl, nil)
var malAnime MALAnime var malAnime MALAnime

View File

@ -46,10 +46,10 @@ type MALWatchlist struct {
Id int `json:"id" ts_type:"id"` Id int `json:"id" ts_type:"id"`
Title string `json:"title" ts_type:"title"` Title string `json:"title" ts_type:"title"`
MainPicture struct { MainPicture struct {
Medium string `json:"medium" json:"medium"` Medium string `json:"medium" ts_type:"medium"`
Large string `json:"large" json:"large"` Large string `json:"large" ts_type:"large"`
} `json:"main_picture" json:"mainPicture"` } `json:"main_picture" ts_type:"mainPicture"`
} `json:"node" json:"node"` } `json:"node" ts_type:"node"`
ListStatus struct { ListStatus struct {
Status string `json:"status" ts_type:"status"` Status string `json:"status" ts_type:"status"`
Score int `json:"score" ts_type:"score"` Score int `json:"score" ts_type:"score"`
@ -59,7 +59,7 @@ type MALWatchlist struct {
StartDate string `json:"start_date" ts_type:"startDate"` StartDate string `json:"start_date" ts_type:"startDate"`
FinishDate string `json:"finish_date" ts_type:"finishDate"` FinishDate string `json:"finish_date" ts_type:"finishDate"`
} `json:"list_status" ts_type:"listStatus"` } `json:"list_status" ts_type:"listStatus"`
} `json:"data" json:"data"` } `json:"data" ts_type:"data"`
Paging struct { Paging struct {
Previous string `json:"previous" ts_type:"previous"` Previous string `json:"previous" ts_type:"previous"`
Next string `json:"next" ts_type:"next"` Next string `json:"next" ts_type:"next"`
@ -70,9 +70,9 @@ type MALAnime struct {
Id int `json:"id" ts_type:"id"` Id int `json:"id" ts_type:"id"`
Title string `json:"title" ts_type:"title"` Title string `json:"title" ts_type:"title"`
MainPicture struct { MainPicture struct {
Large string `json:"large" json:"large"` Large string `json:"large" ts_type:"large"`
Medium string `json:"medium" json:"medium"` Medium string `json:"medium" ts_type:"medium"`
} `json:"main_picture" json:"mainPicture"` } `json:"main_picture" ts_type:"mainPicture"`
AlternativeTitles struct { AlternativeTitles struct {
Synonyms []string `json:"synonyms" ts_type:"synonyms"` Synonyms []string `json:"synonyms" ts_type:"synonyms"`
En string `json:"en" ts_type:"en"` En string `json:"en" ts_type:"en"`

View File

@ -5,6 +5,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -24,6 +25,10 @@ var myAnimeListJwt MyAnimeListJWT
var myAnimeListRing, _ = keyring.Open(keyring.Config{ var myAnimeListRing, _ = keyring.Open(keyring.Config{
ServiceName: "AniTrack", ServiceName: "AniTrack",
KeychainName: "AniTrack",
KeychainSynchronizable: false,
KeychainTrustApplication: true,
KeychainAccessibleWhenUnlocked: true,
}) })
var myAnimeListCtxShutdown, myAnimeListCancel = context.WithCancel(context.Background()) var myAnimeListCtxShutdown, myAnimeListCancel = context.WithCancel(context.Background())
@ -46,7 +51,7 @@ func base64URLEncode(str []byte) string {
func verifier() (*CodeVerifier, error) { func verifier() (*CodeVerifier, error) {
r := rand.New(rand.NewSource(time.Now().UnixNano())) r := rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, length, length) b := make([]byte, length)
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
b[i] = byte(r.Intn(255)) b[i] = byte(r.Intn(255))
} }
@ -66,17 +71,19 @@ func (v *CodeVerifier) CodeChallengeS256() string {
} }
func (a *App) CheckIfMyAnimeListLoggedIn() bool { func (a *App) CheckIfMyAnimeListLoggedIn() bool {
fmt.Println("check function reached")
if (MyAnimeListJWT{} == myAnimeListJwt) { if (MyAnimeListJWT{} == myAnimeListJwt) {
tokenType, err := myAnimeListRing.Get("MyAnimeListTokenType") tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
expiresIn, err := myAnimeListRing.Get("MyAnimeListExpiresIn") expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn")
accessToken, err := myAnimeListRing.Get("MyAnimeListAccessToken") refreshToken, refreshTokenErr := myAnimeListRing.Get("MyAnimeListAccessToken")
refreshToken, err := myAnimeListRing.Get("MyAnimeListRefreshToken") accessToken, accessTokenErr := myAnimeListRing.Get("MyAnimeListRefreshToken")
if err != nil || len(accessToken.Data) == 0 { if (tokenErr != nil || expiresInErr != nil || refreshTokenErr != nil || accessTokenErr != nil) || len(accessToken.Data) == 0 {
return false return false
} else { } else {
var expiresInConvertErr error
myAnimeListJwt.TokenType = string(tokenType.Data) myAnimeListJwt.TokenType = string(tokenType.Data)
myAnimeListJwt.ExpiresIn, err = strconv.Atoi(string(expiresIn.Data)) myAnimeListJwt.ExpiresIn, expiresInConvertErr = strconv.Atoi(string(expiresIn.Data))
if err != nil { if expiresInConvertErr != nil {
fmt.Println("unable to convert string to int") fmt.Println("unable to convert string to int")
} }
myAnimeListJwt.AccessToken = string(accessToken.Data) myAnimeListJwt.AccessToken = string(accessToken.Data)
@ -89,12 +96,14 @@ func (a *App) CheckIfMyAnimeListLoggedIn() bool {
} }
func (a *App) MyAnimeListLogin() { func (a *App) MyAnimeListLogin() {
if a.CheckIfMyAnimeListLoggedIn() == false { fmt.Println("login function reached")
tokenType, err := myAnimeListRing.Get("MyAnimeListTokenType") if !a.CheckIfMyAnimeListLoggedIn() {
expiresIn, err := myAnimeListRing.Get("MyAnimeListExpiresIn") fmt.Println("check logged in function failed")
accessToken, err := myAnimeListRing.Get("MyAnimeListAccessToken") tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
refreshToken, err := myAnimeListRing.Get("MyAnimeListRefreshToken") expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn")
if err != nil || len(accessToken.Data) == 0 { refreshToken, refreshTokenErr := myAnimeListRing.Get("MyAnimeListAccessToken")
accessToken, accessTokenErr := myAnimeListRing.Get("MyAnimeListRefreshToken")
if (tokenErr != nil || expiresInErr != nil || refreshTokenErr != nil || accessTokenErr != nil) || len(accessToken.Data) == 0 {
verifier, _ := verifier() verifier, _ := verifier()
getMyAnimeListCodeUrl := "https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=" + Environment.MAL_CLIENT_ID + "&redirect_uri=" + Environment.MAL_CALLBACK_URI + "&code_challenge=" + verifier.Value + "&code_challenge_method=plain" getMyAnimeListCodeUrl := "https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=" + Environment.MAL_CLIENT_ID + "&redirect_uri=" + Environment.MAL_CALLBACK_URI + "&code_challenge=" + verifier.Value + "&code_challenge_method=plain"
runtime.BrowserOpenURL(*wailsContext, getMyAnimeListCodeUrl) runtime.BrowserOpenURL(*wailsContext, getMyAnimeListCodeUrl)
@ -103,9 +112,10 @@ func (a *App) MyAnimeListLogin() {
a.handleMyAnimeListCallback(serverDone, verifier) a.handleMyAnimeListCallback(serverDone, verifier)
serverDone.Wait() serverDone.Wait()
} else { } else {
var expiresInConvertErr error
myAnimeListJwt.TokenType = string(tokenType.Data) myAnimeListJwt.TokenType = string(tokenType.Data)
myAnimeListJwt.ExpiresIn, err = strconv.Atoi(string(expiresIn.Data)) myAnimeListJwt.ExpiresIn, expiresInConvertErr = strconv.Atoi(string(expiresIn.Data))
if err != nil { if expiresInConvertErr != nil {
fmt.Println("unable to convert string to int in Login function") fmt.Println("unable to convert string to int in Login function")
} }
myAnimeListJwt.AccessToken = string(accessToken.Data) myAnimeListJwt.AccessToken = string(accessToken.Data)
@ -167,7 +177,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 && err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err) log.Fatalf("listen: %s\n", err)
} }
fmt.Println("Shutting down...") fmt.Println("Shutting down...")
@ -206,14 +216,17 @@ func getMyAnimeListAuthorizationToken(content string, verifier *CodeVerifier) My
response.Header.Add("Content-Type", "application/x-www-form-urlencoded") response.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{} client := &http.Client{}
res, reserr := client.Do(response) res, resErr := client.Do(response)
if reserr != nil { if resErr != nil {
log.Printf("Failed at res, %s\n", err) log.Printf("Failed at res, %s\n", err)
} }
defer res.Body.Close() defer res.Body.Close()
returnedBody, err := io.ReadAll(res.Body) returnedBody, err := io.ReadAll(res.Body)
if err != nil {
log.Printf("Could not read returned body, %s\n", err)
}
var post MyAnimeListJWT var post MyAnimeListJWT
err = json.Unmarshal(returnedBody, &post) err = json.Unmarshal(returnedBody, &post)
@ -253,14 +266,17 @@ func refreshMyAnimeListAuthorizationToken() {
response.Header.Add("Content-Type", "application/x-www-form-urlencoded") response.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{} client := &http.Client{}
res, reserr := client.Do(response) res, resErr := client.Do(response)
if reserr != nil { if resErr != nil {
log.Printf("Failed at res, %s\n", err) log.Printf("Failed at res, %s\n", err)
} }
defer res.Body.Close() defer res.Body.Close()
returnedBody, err := io.ReadAll(res.Body) returnedBody, err := io.ReadAll(res.Body)
if err != nil {
log.Printf("Could not read returned body, %s\n", err)
}
err = json.Unmarshal(returnedBody, &myAnimeListJwt) err = json.Unmarshal(returnedBody, &myAnimeListJwt)
if err != nil { if err != nil {
@ -287,12 +303,9 @@ func refreshMyAnimeListAuthorizationToken() {
Title: "MyAnimeList Authorization", Title: "MyAnimeList Authorization",
Message: "It is now safe to close your browser tab", Message: "It is now safe to close your browser tab",
}) })
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
return
} }
func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser { func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser {
@ -307,7 +320,6 @@ func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser {
req.Header.Add("myAnimeList-api-key", Environment.MAL_CLIENT_ID) req.Header.Add("myAnimeList-api-key", Environment.MAL_CLIENT_ID)
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
log.Printf("Failed at request, %s\n", err) log.Printf("Failed at request, %s\n", err)
return MyAnimeListUser{} return MyAnimeListUser{}
@ -329,13 +341,12 @@ func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser {
func (a *App) LogoutMyAnimeList() string { func (a *App) LogoutMyAnimeList() string {
if (MyAnimeListJWT{} != myAnimeListJwt) { if (MyAnimeListJWT{} != myAnimeListJwt) {
err := myAnimeListRing.Remove("MyAnimeListTokenType") typeErr := myAnimeListRing.Remove("MyAnimeListTokenType")
err = myAnimeListRing.Remove("MyAnimeListExpiresIn") expiresInErr := myAnimeListRing.Remove("MyAnimeListExpiresIn")
err = myAnimeListRing.Remove("MyAnimeListAccessToken") accessTokenErr := myAnimeListRing.Remove("MyAnimeListAccessToken")
err = myAnimeListRing.Remove("MyAnimeListRefreshToken") refreshTokenErr := myAnimeListRing.Remove("MyAnimeListRefreshToken")
if typeErr != nil || expiresInErr != nil || accessTokenErr != nil || refreshTokenErr != nil {
if err != nil { fmt.Println("MAL Logout Failed")
fmt.Println("MAL Logout Failed", err)
} }
myAnimeListJwt = MyAnimeListJWT{} myAnimeListJwt = MyAnimeListJWT{}
} }

View File

@ -8,6 +8,7 @@ import (
"log" "log"
"net/http" "net/http"
"reflect" "reflect"
"slices"
"strconv" "strconv"
) )
@ -30,7 +31,6 @@ func SimklHelper(method string, url string, body interface{}) json.RawMessage {
req.Header.Add("simkl-api-key", Environment.SIMKL_CLIENT_ID) req.Header.Add("simkl-api-key", Environment.SIMKL_CLIENT_ID)
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") fmt.Println("Errored when sending request to the server")
message, _ := json.Marshal(struct { message, _ := json.Marshal(struct {
@ -46,12 +46,11 @@ func SimklHelper(method string, url string, body interface{}) json.RawMessage {
respBody, _ := io.ReadAll(resp.Body) respBody, _ := io.ReadAll(resp.Body)
return respBody return respBody
} }
func (a *App) SimklGetUserWatchlist() SimklWatchListType { func (a *App) SimklGetUserWatchlist() SimklWatchListType {
method := "GET" method := "GET"
url := "https://api.simkl.com/sync/all-items/anime/watching" url := "https://api.simkl.com/sync/all-items/anime"
respBody := SimklHelper(method, url, nil) respBody := SimklHelper(method, url, nil)
@ -61,7 +60,6 @@ func (a *App) SimklGetUserWatchlist() SimklWatchListType {
} }
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)
} }
@ -84,7 +82,6 @@ func (a *App) SimklGetUserWatchlist() SimklWatchListType {
} }
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime { func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
var episodes []Episode var episodes []Episode
var url string var url string
var shows []SimklPostShow var shows []SimklPostShow
@ -132,12 +129,14 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
anime.WatchedEpisodesCount = progress anime.WatchedEpisodesCount = progress
WatchListUpdate(anime)
return anime return anime
} }
func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime { func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
var url string var url string
var showWithRating = ShowWithRating{ showWithRating := ShowWithRating{
Title: anime.Show.Title, Title: anime.Show.Title,
Ids: Ids{ Ids: Ids{
Simkl: anime.Show.Ids.Simkl, Simkl: anime.Show.Ids.Simkl,
@ -147,7 +146,7 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
Rating: rating, Rating: rating,
} }
var showWithoutRating = ShowWithoutRating{ showWithoutRating := ShowWithoutRating{
Title: anime.Show.Title, Title: anime.Show.Title,
Ids: Ids{ Ids: Ids{
Simkl: anime.Show.Ids.Simkl, Simkl: anime.Show.Ids.Simkl,
@ -187,12 +186,14 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
anime.UserRating = rating anime.UserRating = rating
WatchListUpdate(anime)
return anime return anime
} }
func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime { func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
url := "https://api.simkl.com/sync/add-to-list" url := "https://api.simkl.com/sync/add-to-list"
var show = SimklShowStatus{ show := SimklShowStatus{
Title: anime.Show.Title, Title: anime.Show.Title,
Ids: Ids{ Ids: Ids{
Simkl: anime.Show.Ids.Simkl, Simkl: anime.Show.Ids.Simkl,
@ -227,6 +228,8 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
anime.Status = status anime.Status = status
WatchListUpdate(anime)
return anime return anime
} }
@ -257,7 +260,6 @@ func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
err := json.Unmarshal(respBody, &anime) err := json.Unmarshal(respBody, &anime)
if len(anime) == 0 { if len(anime) == 0 {
fmt.Println("reached search by mal")
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 = SimklHelper("GET", url, nil)
fmt.Println(string(respBody)) fmt.Println(string(respBody))
@ -292,7 +294,9 @@ func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
func (a *App) SimklSyncRemove(anime SimklAnime) bool { func (a *App) SimklSyncRemove(anime SimklAnime) bool {
url := "https://api.simkl.com/sync/history/remove" url := "https://api.simkl.com/sync/history/remove"
var show = SimklShowStatus{ var showArray []SimklShowStatus
singleShow := SimklShowStatus{
Title: anime.Show.Title, Title: anime.Show.Title,
Ids: Ids{ Ids: Ids{
Simkl: anime.Show.Ids.Simkl, Simkl: anime.Show.Ids.Simkl,
@ -301,6 +305,14 @@ func (a *App) SimklSyncRemove(anime SimklAnime) bool {
}, },
} }
showArray = append(showArray, singleShow)
show := struct {
Shows []SimklShowStatus `json:"shows"`
}{
Shows: showArray,
}
respBody := SimklHelper("POST", url, show) respBody := SimklHelper("POST", url, show)
var success SimklDeleteType var success SimklDeleteType
@ -311,8 +323,26 @@ func (a *App) SimklSyncRemove(anime SimklAnime) bool {
} }
if success.Deleted.Shows >= 1 { 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 return true
} else { } else {
return false return false
} }
} }
func WatchListUpdate(anime SimklAnime) {
updated := false
for i, simklAnime := range SimklWatchList.Anime {
if simklAnime.Show.Ids.Simkl == anime.Show.Ids.Simkl {
SimklWatchList.Anime[i] = anime
updated = true
}
}
if !updated {
SimklWatchList.Anime = append(SimklWatchList.Anime, anime)
}
}

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -18,16 +19,20 @@ var simklJwt SimklJWT
var simklRing, _ = keyring.Open(keyring.Config{ var simklRing, _ = keyring.Open(keyring.Config{
ServiceName: "AniTrack", ServiceName: "AniTrack",
KeychainName: "AniTrack",
KeychainSynchronizable: false,
KeychainTrustApplication: true,
KeychainAccessibleWhenUnlocked: true,
}) })
var simklCtxShutdown, simklCancel = context.WithCancel(context.Background()) var simklCtxShutdown, simklCancel = context.WithCancel(context.Background())
func (a *App) CheckIfSimklLoggedIn() bool { func (a *App) CheckIfSimklLoggedIn() bool {
if (SimklJWT{} == simklJwt) { if (SimklJWT{} == simklJwt) {
tokenType, err := simklRing.Get("SimklTokenType") tokenType, tokenTypeErr := simklRing.Get("SimklTokenType")
accessToken, err := simklRing.Get("SimklAccessToken") accessToken, accessTokenErr := simklRing.Get("SimklAccessToken")
scope, err := simklRing.Get("SimklScope") scope, scopeErr := simklRing.Get("SimklScope")
if err != nil || len(accessToken.Data) == 0 { if (tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil) || len(accessToken.Data) == 0 {
return false return false
} else { } else {
simklJwt.TokenType = string(tokenType.Data) simklJwt.TokenType = string(tokenType.Data)
@ -41,11 +46,11 @@ func (a *App) CheckIfSimklLoggedIn() bool {
} }
func (a *App) SimklLogin() { func (a *App) SimklLogin() {
if a.CheckIfSimklLoggedIn() == false { if !a.CheckIfSimklLoggedIn() {
tokenType, err := simklRing.Get("SimklTokenType") tokenType, tokenTypeErr := simklRing.Get("SimklTokenType")
accessToken, err := simklRing.Get("SimklAccessToken") accessToken, accessTokenErr := simklRing.Get("SimklAccessToken")
scope, err := simklRing.Get("SimklScope") scope, scopeErr := simklRing.Get("SimklScope")
if err != nil || len(accessToken.Data) == 0 { if (tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil) || len(accessToken.Data) == 0 {
getSimklCodeUrl := "https://simkl.com/oauth/authorize?response_type=code&client_id=" + Environment.SIMKL_CLIENT_ID + "&redirect_uri=" + Environment.SIMKL_CALLBACK_URI getSimklCodeUrl := "https://simkl.com/oauth/authorize?response_type=code&client_id=" + Environment.SIMKL_CLIENT_ID + "&redirect_uri=" + Environment.SIMKL_CALLBACK_URI
runtime.BrowserOpenURL(*wailsContext, getSimklCodeUrl) runtime.BrowserOpenURL(*wailsContext, getSimklCodeUrl)
@ -110,7 +115,7 @@ func (a *App) handleSimklCallback(wg *sync.WaitGroup) {
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err) log.Fatalf("listen: %s\n", err)
} }
fmt.Println("Shutting down...") fmt.Println("Shutting down...")
@ -143,14 +148,17 @@ func getSimklAuthorizationToken(content string) SimklJWT {
response.Header.Add("Content-Type", "application/json") response.Header.Add("Content-Type", "application/json")
client := &http.Client{} client := &http.Client{}
res, reserr := client.Do(response) res, resErr := client.Do(response)
if reserr != nil { if resErr != nil {
log.Printf("Failed at res, %s\n", err) log.Printf("Failed at res, %s\n", err)
} }
defer res.Body.Close() defer res.Body.Close()
returnedBody, err := io.ReadAll(res.Body) returnedBody, err := io.ReadAll(res.Body)
if err != nil {
log.Printf("Could not read returned body, %s\n.", err)
}
var post SimklJWT var post SimklJWT
err = json.Unmarshal(returnedBody, &post) err = json.Unmarshal(returnedBody, &post)
@ -173,7 +181,6 @@ func (a *App) GetSimklLoggedInUser() SimklUser {
req.Header.Add("simkl-api-key", Environment.SIMKL_CLIENT_ID) req.Header.Add("simkl-api-key", Environment.SIMKL_CLIENT_ID)
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
log.Printf("Failed at request, %s\n", err) log.Printf("Failed at request, %s\n", err)
return SimklUser{} return SimklUser{}
@ -189,7 +196,6 @@ func (a *App) GetSimklLoggedInUser() SimklUser {
} }
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)
} }
@ -211,12 +217,12 @@ func (a *App) GetSimklLoggedInUser() SimklUser {
func (a *App) LogoutSimkl() string { func (a *App) LogoutSimkl() string {
if (SimklJWT{} != simklJwt) { if (SimklJWT{} != simklJwt) {
err := simklRing.Remove("SimklTokenType") tokenTypeErr := simklRing.Remove("SimklTokenType")
err = simklRing.Remove("SimklAccessToken") accessTokenErr := simklRing.Remove("SimklAccessToken")
err = simklRing.Remove("SimklScope") scopeErr := simklRing.Remove("SimklScope")
if err != nil { if tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil {
fmt.Println("Simkl Logout Failed", err) fmt.Println("Simkl Logout Failed")
} }
simklJwt = SimklJWT{} simklJwt = SimklJWT{}
} }

8
app.go
View File

@ -2,11 +2,17 @@ package main
import ( import (
"context" "context"
_ "embed"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/runtime" "github.com/wailsapp/wails/v2/pkg/runtime"
"strings" "strings"
"github.com/tidwall/gjson"
) )
//go:embed wails.json
var wailsJSON string
var wailsContext *context.Context var wailsContext *context.Context
// App struct // App struct
@ -22,7 +28,9 @@ func NewApp() *App {
// startup is called when the app starts. The context is saved // startup is called when the app starts. The context is saved
// so we can call the runtime methods // so we can call the runtime methods
func (a *App) startup(ctx context.Context) { func (a *App) startup(ctx context.Context) {
version := gjson.Get(wailsJSON, "info.productVersion")
wailsContext = &ctx wailsContext = &ctx
runtime.WindowSetTitle(ctx, "AniTrack "+version.String())
//runtime.WindowMaximise(ctx) //runtime.WindowMaximise(ctx)
} }

View File

@ -0,0 +1,90 @@
meta {
name: AniChart
type: graphql
seq: 5
}
post {
url: https://graphql.anilist.co
body: graphql
auth: none
}
body:graphql {
# Write your query or mutation here
query ($page: Int, $perPage: Int, $airingAt_greater:Int) {
Page(page: $page, perPage: $perPage) {
pageInfo {
total
perPage
currentPage
lastPage
hasNextPage
}
airingSchedules(airingAt_greater:$airingAt_greater){
id
airingAt
timeUntilAiring
episode
mediaId
media{
id
title{
english
romaji
native
}
type
format
status
startDate{
year
month
day
}
endDate{
year
month
day
}
season
seasonYear
episodes
duration
coverImage{
medium
large
color
extraLarge
}
bannerImage
genres
averageScore
meanScore
popularity
trending
favourites
tags{
id
name
description
category
rank
isGeneralSpoiler
isMediaSpoiler
isAdult
}
isAdult
}
}
}
}
}
body:graphql:vars {
{
"page": 50,
"perPage": 20,
"airingAt_greater": 1730260800
}
}

View File

@ -0,0 +1,31 @@
meta {
name: Delete Media
type: graphql
seq: 4
}
post {
url: https://graphql.anilist.co
body: graphql
auth: none
}
headers {
Authorization: Bearer {{ANILIST_ACCESS_TOKEN}}
Content-Type: application/json
Accept: application/json
}
body:graphql {
mutation($id:Int){
DeleteMediaListEntry(id:$id){
deleted
}
}
}
body:graphql:vars {
{
"id":430978266
}
}

View File

@ -5,7 +5,7 @@ meta {
} }
get { get {
url: https://api.simkl.com/anime/1579943?extended=full url: https://api.simkl.com/anime/40084?extended=full
body: none body: none
auth: none auth: none
} }

View File

@ -5,7 +5,7 @@ meta {
} }
get { get {
url: https://api.simkl.com/sync/all-items/anime/watching url: https://api.simkl.com/sync/all-items/anime/
body: none body: none
auth: none auth: none
} }

View File

@ -0,0 +1,29 @@
meta {
name: Delete Entry
type: http
seq: 2
}
post {
url: https://api.simkl.com/sync/history/remove
body: json
auth: none
}
headers {
Authorization: Bearer {{SIMKL_AUTH_TOKEN}}
Content-Type: application/json
simkl-api-key: {{SIMKL_CLIENT_ID}}
}
body:json {
{
"shows": [
{
"ids": {
"simkl": 909121
}
}
]
}
}

11
build/AniTrack.desktop Executable file
View File

@ -0,0 +1,11 @@
[Desktop Entry]
Name=AniTrack
Comment=A manual synchronizer for various Anime trackers.
Exec=/home/nymusicman/Applications/AniTrack
Icon=AniTrack
Terminal=false
Type=Application
StartupNotify=true
Categories=Internet
Keywords=anitrack;anilist;simkl;mal;myanimelist;anime;sync
Path=

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -1,6 +1,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true />
</dict>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleName</key> <key>CFBundleName</key>

View File

@ -23,7 +23,7 @@
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.10",
"tslib": "^2.7.0", "tslib": "^2.7.0",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"vite": "^4.5.3" "vite": "^4.5.5"
}, },
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",

View File

@ -30,7 +30,6 @@
conditions: [ conditions: [
async () => await CheckIfAniListLoggedIn(), async () => await CheckIfAniListLoggedIn(),
async (detail) => { async (detail) => {
console.log("reached condition")
aniListAnime.update(value => { aniListAnime.update(value => {
value = AniListGetSingleAnimeDefaultData value = AniListGetSingleAnimeDefaultData
return value return value

Binary file not shown.

View File

@ -13,72 +13,99 @@
import Rating from "./Rating.svelte"; import Rating from "./Rating.svelte";
import convertAniListDateToString from "../helperFunctions/convertAniListDateToString"; import convertAniListDateToString from "../helperFunctions/convertAniListDateToString";
import AnimeTable from "./AnimeTable.svelte"; import AnimeTable from "./AnimeTable.svelte";
import type {MALAnime, MalListStatus, MALUploadStatus} from "../mal/types/MALTypes"; import type {
MALAnime,
MalListStatus,
MALUploadStatus,
} from "../mal/types/MALTypes";
import type { SimklAnime } from "../simkl/types/simklTypes"; import type { SimklAnime } from "../simkl/types/simklTypes";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import type {StatusOption, StatusOptions} from "../helperTypes/StatusTypes"; import type {
StatusOption,
StatusOptions,
} from "../helperTypes/StatusTypes";
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes"; import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
import convertDateStringToAniList from "../helperFunctions/convertDateStringToAniList"; import convertDateStringToAniList from "../helperFunctions/convertDateStringToAniList";
import { import {
AniListDeleteEntry, AniListDeleteEntry,
AniListUpdateEntry, DeleteMyAnimeListEntry, AniListUpdateEntry,
DeleteMyAnimeListEntry,
MyAnimeListUpdate, MyAnimeListUpdate,
SimklSyncEpisodes, SimklSyncEpisodes,
SimklSyncRating, SimklSyncRemove, SimklSyncRating,
SimklSyncStatus SimklSyncRemove,
SimklSyncStatus,
} from "../../wailsjs/go/main/App"; } from "../../wailsjs/go/main/App";
import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte"; import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte";
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte"; import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
let isAniListLoggedIn: boolean let isAniListLoggedIn: boolean;
let isMalLoggedIn: boolean let isMalLoggedIn: boolean;
let isSimklLoggedIn: boolean let isSimklLoggedIn: boolean;
let currentAniListAnime: AniListGetSingleAnime let currentAniListAnime: AniListGetSingleAnime;
let currentMalAnime: MALAnime let currentMalAnime: MALAnime;
let currentSimklAnime: SimklAnime let currentSimklAnime: SimklAnime;
let submitting = writable(false) let submitting = writable(false);
let isSubmitting: boolean let isSubmitting: boolean;
let submitSuccess = writable(false) let submitSuccess = writable(false);
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));
aniListAnime.subscribe((value) => currentAniListAnime = value) aniListAnime.subscribe((value) => (currentAniListAnime = value));
malAnime.subscribe((value) => currentMalAnime = value) malAnime.subscribe((value) => (currentMalAnime = value));
simklAnime.subscribe((value) => currentSimklAnime = value) simklAnime.subscribe((value) => (currentSimklAnime = value));
submitting.subscribe((value) => isSubmitting = value) submitting.subscribe((value) => (isSubmitting = value));
const title = currentAniListAnime.data.MediaList.media.title.english !== "" ? const title =
currentAniListAnime.data.MediaList.media.title.english : currentAniListAnime.data.MediaList.media.title.english !== ""
currentAniListAnime.data.MediaList.media.title.romaji ? currentAniListAnime.data.MediaList.media.title.english
: currentAniListAnime.data.MediaList.media.title.romaji;
const statusOptions: StatusOptions = [ const statusOptions: StatusOptions = [
{ id: 0, aniList: "CURRENT", mal: "watching", simkl: "watching" }, { id: 0, aniList: "CURRENT", mal: "watching", simkl: "watching" },
{id: 1, aniList: "PLANNING", mal: "plan_to_watch", simkl: "plantowatch"}, {
id: 1,
aniList: "PLANNING",
mal: "plan_to_watch",
simkl: "plantowatch",
},
{ id: 2, aniList: "COMPLETED", mal: "completed", simkl: "completed" }, { id: 2, aniList: "COMPLETED", mal: "completed", simkl: "completed" },
{ id: 3, aniList: "DROPPED", mal: "dropped", simkl: "dropped" }, { id: 3, aniList: "DROPPED", mal: "dropped", simkl: "dropped" },
{ id: 4, aniList: "PAUSED", mal: "on_hold", simkl: "hold" }, { id: 4, aniList: "PAUSED", mal: "on_hold", simkl: "hold" },
{id: 5, aniList: "REPEATING", mal: "rewatching", simkl: "watching"} { id: 5, aniList: "REPEATING", mal: "rewatching", simkl: "watching" },
] ];
let startingAnilistStatusOption: StatusOption = statusOptions.filter(option => currentAniListAnime.data.MediaList.status === option.aniList)[0] let startingAnilistStatusOption: StatusOption = statusOptions.filter(
const startedAtDate = convertAniListDateToString(currentAniListAnime.data.MediaList.startedAt) (option) =>
const completedAtDate = convertAniListDateToString(currentAniListAnime.data.MediaList.completedAt) currentAniListAnime.data.MediaList.status === option.aniList,
)[0];
const startedAtDate = convertAniListDateToString(
currentAniListAnime.data.MediaList.startedAt,
);
const completedAtDate = convertAniListDateToString(
currentAniListAnime.data.MediaList.completedAt,
);
if (isAniListLoggedIn) AddAnimeServiceToTable({ if (isAniListLoggedIn)
id: currentAniListAnime.data.MediaList.mediaId, AddAnimeServiceToTable({
id: `a-${currentAniListAnime.data.MediaList.mediaId}`,
title, title,
service: "AniList", service: "AniList",
progress: currentAniListAnime.data.MediaList.progress, progress: currentAniListAnime.data.MediaList.progress,
status: currentAniListAnime.data.MediaList.status, status: currentAniListAnime.data.MediaList.status,
startedAt: convertAniListDateToString(currentAniListAnime.data.MediaList.startedAt), startedAt: convertAniListDateToString(
completedAt: convertAniListDateToString(currentAniListAnime.data.MediaList.completedAt), currentAniListAnime.data.MediaList.startedAt,
),
completedAt: convertAniListDateToString(
currentAniListAnime.data.MediaList.completedAt,
),
score: currentAniListAnime.data.MediaList.score, score: currentAniListAnime.data.MediaList.score,
repeat: currentAniListAnime.data.MediaList.repeat, repeat: currentAniListAnime.data.MediaList.repeat,
notes: currentAniListAnime.data.MediaList.notes notes: currentAniListAnime.data.MediaList.notes,
}) });
if (isMalLoggedIn)
if (isMalLoggedIn) AddAnimeServiceToTable({ AddAnimeServiceToTable({
id: currentMalAnime.id, id: `m-${currentMalAnime.id}`,
title: currentMalAnime.title, title: currentMalAnime.title,
service: "MyAnimeList", service: "MyAnimeList",
progress: currentMalAnime.my_list_status.num_episodes_watched, progress: currentMalAnime.my_list_status.num_episodes_watched,
@ -87,11 +114,12 @@
completedAt: currentMalAnime.my_list_status.finish_date, completedAt: currentMalAnime.my_list_status.finish_date,
score: currentMalAnime.my_list_status.score, 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 notes: currentMalAnime.my_list_status.comments,
}) });
if (isSimklLoggedIn && Object.keys(currentSimklAnime).length > 0) AddAnimeServiceToTable({ if (isSimklLoggedIn && Object.keys(currentSimklAnime).length > 0)
id: currentSimklAnime.show.ids.simkl, AddAnimeServiceToTable({
id: `s-${currentSimklAnime.show.ids.simkl}`,
title: currentSimklAnime.show.title, title: currentSimklAnime.show.title,
service: "Simkl", service: "Simkl",
progress: currentSimklAnime.watched_episodes_count, progress: currentSimklAnime.watched_episodes_count,
@ -100,19 +128,19 @@
completedAt: "", completedAt: "",
score: currentSimklAnime.user_rating, score: currentSimklAnime.user_rating,
repeat: 0, repeat: 0,
notes: "" notes: "",
}) });
const handleSubmit = async (e: any) => { const handleSubmit = async (e: any) => {
submitting.set(true) submitting.set(true);
let submitData: { let submitData: {
rating: number, rating: number;
episodes: number, episodes: number;
status: StatusOption, status: StatusOption;
startedAt: string, startedAt: string;
completedAt: string, completedAt: string;
repeat: number, repeat: number;
notes: string, notes: string;
} = { } = {
rating: 0, rating: 0,
episodes: 0, episodes: 0,
@ -126,30 +154,33 @@
completedAt: "", completedAt: "",
repeat: 0, repeat: 0,
notes: "", notes: "",
} };
const formData = new FormData(e.target) const formData = new FormData(e.target);
for (let field of formData) { for (let field of formData) {
const [key, value] = field const [key, value] = field;
if (key === "rating") { if (key === "rating") {
submitData.rating = (Number(value) * 2) submitData.rating = Number(value) * 2;
continue continue;
} }
if (key === "episodes") { if (key === "episodes") {
submitData.episodes = Number(value) submitData.episodes = Number(value);
continue continue;
} }
if (key === "repeat") { if (key === "repeat") {
submitData.repeat = Number(value) submitData.repeat = Number(value);
continue continue;
} }
if (key === "status") { if (key === "status") {
submitData.status = startingAnilistStatusOption submitData.status = startingAnilistStatusOption;
continue continue;
} }
submitData[key] = value submitData[key] = value;
} }
if (isAniListLoggedIn && currentAniListAnime.data.MediaList.mediaId !== 0) { if (
isAniListLoggedIn &&
currentAniListAnime.data.MediaList.mediaId !== 0
) {
let body: AniListUpdateVariables = { let body: AniListUpdateVariables = {
mediaId: currentAniListAnime.data.MediaList.mediaId, mediaId: currentAniListAnime.data.MediaList.mediaId,
progress: submitData.episodes, progress: submitData.episodes,
@ -158,29 +189,35 @@
repeat: submitData.repeat, repeat: submitData.repeat,
notes: submitData.notes, notes: submitData.notes,
startedAt: convertDateStringToAniList(submitData.startedAt), startedAt: convertDateStringToAniList(submitData.startedAt),
completedAt: convertDateStringToAniList(submitData.completedAt) completedAt: convertDateStringToAniList(submitData.completedAt),
} };
await AniListUpdateEntry(body).then((value: AniListGetSingleAnime) => { await AniListUpdateEntry(body).then(
// in future when you inevitably add tags to typescript, until Anilist fixes the api bug (value: AniListGetSingleAnime) => {
// where tags break the SaveMediaListEntry return, you'll want to use this delete line /* TODO in future when you inevitably add tags to typescript, until Anilist fixes the api bug
// delete value.data.MediaList.media.tags where tags break the SaveMediaListEntry return, you'll want to use this delete line
aniListAnime.update(newValue => { delete value.data.MediaList.media.tags */
newValue = value aniListAnime.update((newValue) => {
return newValue newValue = value;
}) return newValue;
});
AddAnimeServiceToTable({ AddAnimeServiceToTable({
id: currentAniListAnime.data.MediaList.mediaId, id: `a-${currentAniListAnime.data.MediaList.mediaId}`,
title, title,
service: "AniList", service: "AniList",
progress: currentAniListAnime.data.MediaList.progress, progress: currentAniListAnime.data.MediaList.progress,
status: currentAniListAnime.data.MediaList.status, status: currentAniListAnime.data.MediaList.status,
startedAt: convertAniListDateToString(currentAniListAnime.data.MediaList.startedAt), startedAt: convertAniListDateToString(
completedAt: convertAniListDateToString(currentAniListAnime.data.MediaList.completedAt), currentAniListAnime.data.MediaList.startedAt,
),
completedAt: convertAniListDateToString(
currentAniListAnime.data.MediaList.completedAt,
),
score: currentAniListAnime.data.MediaList.score, score: currentAniListAnime.data.MediaList.score,
repeat: currentAniListAnime.data.MediaList.repeat, repeat: currentAniListAnime.data.MediaList.repeat,
notes: currentAniListAnime.data.MediaList.notes, notes: currentAniListAnime.data.MediaList.notes,
}) });
}) },
);
} }
if (malLoggedIn && currentMalAnime.id !== 0) { if (malLoggedIn && currentMalAnime.id !== 0) {
@ -190,39 +227,51 @@
score: submitData.rating, score: submitData.rating,
num_watched_episodes: submitData.episodes, num_watched_episodes: submitData.episodes,
num_times_rewatched: submitData.repeat, num_times_rewatched: submitData.repeat,
comments: submitData.notes comments: submitData.notes,
} };
await MyAnimeListUpdate(currentMalAnime, body).then((malAnimeReturn: MalListStatus) => { await MyAnimeListUpdate(currentMalAnime, body).then(
malAnime.update(value => { (malAnimeReturn: MalListStatus) => {
value.my_list_status.status = malAnimeReturn.status malAnime.update((value) => {
value.my_list_status.is_rewatching = malAnimeReturn.is_rewatching value.my_list_status.status = malAnimeReturn.status;
value.my_list_status.score = malAnimeReturn.score value.my_list_status.is_rewatching =
value.my_list_status.num_episodes_watched = malAnimeReturn.num_episodes_watched malAnimeReturn.is_rewatching;
value.my_list_status.num_times_rewatched = malAnimeReturn.num_times_rewatched value.my_list_status.score = malAnimeReturn.score;
value.my_list_status.comments = malAnimeReturn.comments value.my_list_status.num_episodes_watched =
return value malAnimeReturn.num_episodes_watched;
}) value.my_list_status.num_times_rewatched =
malAnimeReturn.num_times_rewatched;
value.my_list_status.comments = malAnimeReturn.comments;
return value;
});
AddAnimeServiceToTable({ AddAnimeServiceToTable({
id: currentMalAnime.id, id: `m-${currentMalAnime.id}`,
title: currentMalAnime.title, title: currentMalAnime.title,
service: "MyAnimeList", service: "MyAnimeList",
progress: currentMalAnime.my_list_status.num_episodes_watched, progress:
currentMalAnime.my_list_status.num_episodes_watched,
status: currentMalAnime.my_list_status.status, status: currentMalAnime.my_list_status.status,
startedAt: currentMalAnime.my_list_status.start_date, startedAt: currentMalAnime.my_list_status.start_date,
completedAt: currentMalAnime.my_list_status.finish_date, completedAt: currentMalAnime.my_list_status.finish_date,
score: currentMalAnime.my_list_status.score, 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, notes: currentMalAnime.my_list_status.comments,
}) });
}) },
);
} }
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) { if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
if (currentSimklAnime.watched_episodes_count !== submitData.episodes) { if (
await SimklSyncEpisodes(currentSimklAnime, submitData.episodes).then((value: SimklAnime) => { currentSimklAnime.watched_episodes_count !== submitData.episodes
) {
await SimklSyncEpisodes(
currentSimklAnime,
submitData.episodes,
).then((value: SimklAnime) => {
AddAnimeServiceToTable({ AddAnimeServiceToTable({
id: value.show.ids.simkl, id: `s-${value.show.ids.simkl}`,
title: value.show.title, title: value.show.title,
service: "Simkl", service: "Simkl",
progress: value.watched_episodes_count, progress: value.watched_episodes_count,
@ -231,19 +280,22 @@
completedAt: "", completedAt: "",
score: value.user_rating, score: value.user_rating,
repeat: 0, repeat: 0,
notes: "" notes: "",
}) });
simklAnime.update(newValue => { simklAnime.update((newValue) => {
newValue = value newValue = value;
return newValue return newValue;
}) });
}) });
} }
if (currentSimklAnime.user_rating !== submitData.rating) { if (currentSimklAnime.user_rating !== submitData.rating) {
await SimklSyncRating(currentSimklAnime, submitData.rating).then(value => { await SimklSyncRating(
currentSimklAnime,
submitData.rating,
).then((value) => {
AddAnimeServiceToTable({ AddAnimeServiceToTable({
id: value.show.ids.simkl, id: `s-${value.show.ids.simkl}`,
title: value.show.title, title: value.show.title,
service: "Simkl", service: "Simkl",
progress: value.watched_episodes_count, progress: value.watched_episodes_count,
@ -252,19 +304,22 @@
completedAt: "", completedAt: "",
score: value.user_rating, score: value.user_rating,
repeat: 0, repeat: 0,
notes: "" notes: "",
}) });
simklAnime.update(newValue => { simklAnime.update((newValue) => {
newValue = value newValue = value;
return newValue return newValue;
}) });
}) });
} }
if (currentSimklAnime.status !== submitData.status.simkl) { if (currentSimklAnime.status !== submitData.status.simkl) {
await SimklSyncStatus(currentSimklAnime, submitData.status.simkl).then(value => { await SimklSyncStatus(
currentSimklAnime,
submitData.status.simkl,
).then((value) => {
AddAnimeServiceToTable({ AddAnimeServiceToTable({
id: value.show.ids.simkl, id: `s-${value.show.ids.simkl}`,
title: value.show.title, title: value.show.title,
service: "Simkl", service: "Simkl",
progress: value.watched_episodes_count, progress: value.watched_episodes_count,
@ -273,69 +328,217 @@
completedAt: "", completedAt: "",
score: value.user_rating, score: value.user_rating,
repeat: 0, repeat: 0,
notes: "" notes: "",
}) });
simklAnime.update(newValue => { simklAnime.update((newValue) => {
newValue = value newValue = value;
return newValue return newValue;
}) });
}) });
} }
} }
submitting.set(false) submitting.set(false);
submitSuccess.set(true) submitSuccess.set(true);
setTimeout(() => submitSuccess.set(false), 2000) setTimeout(() => submitSuccess.set(false), 2000);
} };
const deleteEntries = async () => { const deleteEntries = async () => {
submitting.set(true) submitting.set(true);
if (isAniListLoggedIn && currentAniListAnime.data.MediaList.mediaId !== 0) await AniListDeleteEntry(currentAniListAnime.data.MediaList.id) if (
if (malLoggedIn && currentMalAnime.id !== 0) await DeleteMyAnimeListEntry(currentMalAnime.id) isAniListLoggedIn &&
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) await SimklSyncRemove(currentSimklAnime) currentAniListAnime.data.MediaList.mediaId !== 0
submitting.set(false) ) {
submitSuccess.set(true) await AniListDeleteEntry(currentAniListAnime.data.MediaList.id);
setTimeout(() => submitSuccess.set(false), 2000) AddAnimeServiceToTable({
id: `a-${currentAniListAnime.data.MediaList.mediaId}`,
title,
service: "AniList",
progress: 0,
status: "",
startedAt: "",
completedAt: "",
score: 0,
repeat: 0,
notes: "",
});
}
if (malLoggedIn && currentMalAnime.id !== 0) {
await DeleteMyAnimeListEntry(currentMalAnime.id);
AddAnimeServiceToTable({
id: `m-${currentMalAnime.id}`,
title: currentMalAnime.title,
service: "MyAnimeList",
progress: 0,
status: "",
startedAt: "",
completedAt: "",
score: 0,
repeat: 0,
notes: "",
});
}
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
await SimklSyncRemove(currentSimklAnime);
AddAnimeServiceToTable({
id: `s-${currentSimklAnime.show.ids.simkl}`,
title: currentSimklAnime.show.title,
service: "Simkl",
progress: 0,
status: "",
startedAt: "",
completedAt: "",
score: 0,
repeat: 0,
notes: "",
});
}
submitting.set(false);
submitSuccess.set(true);
setTimeout(() => submitSuccess.set(false), 2000);
};
let max = 999;
if (currentAniListAnime.data.MediaList.media.episodes !== 0) {
max = currentAniListAnime.data.MediaList.media.episodes;
}
if (
currentAniListAnime.data.MediaList.media.episodes === 0 &&
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0
) {
max =
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode -
1;
} }
</script> </script>
<form on:submit|preventDefault={handleSubmit} class="container py-10">
<form on:submit|preventDefault={handleSubmit} class="container pt-3 pb-10">
<h1 class="text-white font-bold text-left text-xl pb-3">
{title}
</h1>
<div class="grid grid-cols-1 md:grid-cols-10 grid-flow-col gap-4"> <div class="grid grid-cols-1 md:grid-cols-10 grid-flow-col gap-4">
<div class="md:col-span-2 space-y-3"> <div class="md:col-span-2 space-y-3">
<img class="rounded-lg" src={currentAniListAnime.data.MediaList.media.coverImage.large} <img
alt="{title} Cover Image"> class="rounded-lg"
src={currentAniListAnime.data.MediaList.media.coverImage.large}
alt="{title} Cover Image"
/>
<Rating bind:score={currentAniListAnime.data.MediaList.score} /> <Rating bind:score={currentAniListAnime.data.MediaList.score} />
</div> </div>
<div class="md:col-span-8"> <div class="md:col-span-8">
<div class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-24 lg:gap-x-56"> <div
class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-24 lg:gap-x-56"
>
<div> <div>
<label for="episodes" <label
class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Episode for="episodes"
Progress</label> class="text-left block mb-2 text-sm font-medium text-white"
>Episode Progress</label
>
<div class="relative flex items-center max-w-[8rem]">
<button
type="button"
id="decrement-button"
data-input-counter-decrement="quantity-input"
on:click={() =>
(currentAniListAnime.data.MediaList.progress -= 1)}
class="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-s-lg p-3 h-11 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none"
>
<svg
class="w-3 h-3 text-gray-900 dark: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 <input
type="number" type="number"
name="episodes" name="episodes"
min="0" min="0"
max="{currentAniListAnime.data.MediaList.media.episodes}" {max}
id="episodes" id="episodes"
class="bg-gray-50 border {currentAniListAnime.data.MediaList.progress < 0 || currentAniListAnime.data.MediaList.progress > currentAniListAnime.data.MediaList.media.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
'border-red-300 dark:border-red-500 border-[2px] text-rose-500 dark:text-rose-300 focus:ring-red-500 focus:border-red-500 dark:focus:ring-red-500 dark:focus:border-red-500' : {currentAniListAnime.data.MediaList.progress < 0 ||
'border-gray-300 dark:border-gray-500 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500' (currentAniListAnime.data.MediaList.media.episodes >
} text-sm rounded-lg block w-24 p-2.5 dark:bg-gray-600 dark:placeholder-gray-400" 0 &&
bind:value={currentAniListAnime.data.MediaList.progress} currentAniListAnime.data.MediaList.progress >
required> currentAniListAnime.data.MediaList.media
<div>of {currentAniListAnime.data.MediaList.media.episodes}</div> .episodes) ||
(currentAniListAnime.data.MediaList.media
.nextAiringEpisode.episode > 0 &&
currentAniListAnime.data.MediaList.progress >
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}
required
/>
<button
type="button"
id="increment-button"
data-input-counter-increment="quantity-input"
on:click={() =>
(currentAniListAnime.data.MediaList.progress += 1)}
class="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-e-lg p-3 h-11 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none"
>
<svg
class="w-3 h-3 text-gray-900 dark: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>
/ {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}
</div>
{/if}
</div> </div>
<div> <div>
<label for="status" <label
class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Status</label> for="status"
<select id="status" name="status" class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 >Status</label
focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 >
dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" <select
id="status"
name="status"
class="border text-sm rounded-lg
block p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400
text-white focus:ring-blue-500 focus:border-blue-500"
bind:value={startingAnilistStatusOption} bind:value={startingAnilistStatusOption}
> >
{#each statusOptions as option} {#each statusOptions as option}
@ -344,150 +547,215 @@
</select> </select>
</div> </div>
</div> </div>
<div class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-16 lg:gap-x-36"> <div
class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-16 lg:gap-x-36"
>
<div> <div>
<label
<label for="startedAt" for="startedAt"
class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date class="text-left block mb-2 text-sm font-medium text-white"
Started</label> >Date Started</label
>
<div class="relative max-w-sm"> <div class="relative max-w-sm">
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"> <div
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"
xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20"> >
<path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/> <svg
class="w-4 h-4 text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"
/>
</svg> </svg>
</div> </div>
<input <input
id="startedAt" id="startedAt"
type="date" type="date"
name="startedAt" name="startedAt"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 class="border text-sm rounded-lg
focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 bg-gray-700 border-gray-600
dark:placeholder-gray-400 dark:text-white" placeholder-gray-400 text-white"
value={startedAtDate} value={startedAtDate}
placeholder="Date Started" placeholder="Date Started"
> />
</div> </div>
</div> </div>
<div> <div>
<label
<label for="completedAt" for="completedAt"
class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date class="text-left block mb-2 text-sm font-medium text-white"
Completed</label> >Date Completed</label
>
<div class="relative max-w-sm"> <div class="relative max-w-sm">
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"> <div
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"
xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20"> >
<path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/> <svg
class="w-4 h-4 text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"
/>
</svg> </svg>
</div> </div>
<input <input
id="completedAt" id="completedAt"
type="date" type="date"
name="completedAt" name="completedAt"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 class="border text-sm rounded-lg
focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 block w-full ps-10 p-2.5 bg-gray-700 border-gray-600
dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
value={completedAtDate} value={completedAtDate}
placeholder="Date Completed" placeholder="Date Completed"
> />
</div> </div>
</div> </div>
<div> <div>
<label for="repeat" <label
class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Rewatched</label> for="repeat"
class="text-left block mb-2 text-sm font-medium text-white"
>Rewatched</label
>
<input <input
type="number" type="number"
name="repeat" name="repeat"
min="0" min="0"
id="repeat" id="repeat"
class="bg-gray-50 border {currentAniListAnime.data.MediaList.repeat < 0 ? class="border {currentAniListAnime.data.MediaList
'border-red-300 dark:border-red-500 border-[2px] text-rose-500 dark:text-rose-300 focus:ring-red-500 focus:border-red-500 dark:focus:ring-red-500 dark:focus:border-red-500' : .repeat < 0
'border-gray-300 dark:border-gray-500 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500' ? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
} text-sm rounded-lg block w-24 p-2.5 dark:bg-gray-600 dark:placeholder-gray-400 dark:text-white" : '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} bind:value={currentAniListAnime.data.MediaList.repeat}
required> required
/>
</div> </div>
</div> </div>
<div class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center"> <div
class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center"
>
<div class="w-full"> <div class="w-full">
<label for="notes" class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your <label
notes</label> for="notes"
<textarea id="notes" rows="3" name="notes" class="text-left block mb-2 text-sm font-medium text-white"
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" >Your notes</label
>
<textarea
id="notes"
rows="3"
name="notes"
class="block p-2.5 w-full text-sm rounded-lg border bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
placeholder="Write your thoughts here..." placeholder="Write your thoughts here..."
bind:value={currentAniListAnime.data.MediaList.notes}></textarea> bind:value={currentAniListAnime.data.MediaList.notes}
></textarea>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div id="external-data"> <div id="external-data">
<div id="anilist-data" <div
class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-16 lg:gap-x-36 group"> id="anilist-data"
<h2 class="text-left mb-1 text-base font-semibold text-gray-900 dark:text-white">AniList</h2> class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-16 lg:gap-x-36 group"
>
<h2 class="text-left mb-1 text-base font-semibold text-white">
AniList
</h2>
</div> </div>
</div> </div>
<AnimeTable /> <AnimeTable />
<div class="bg-white flex rounded-lg shadow max-w-4-4 dark:bg-gray-800"> <div class="flex rounded-lg shadow max-w-4-4 bg-gray-800">
<div class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-start"> <div
<Button disabled={isSubmitting} class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-start"
>
<Button
disabled={isSubmitting}
id="delete-button" id="delete-button"
class="text-white bg-red-700 {$submitSuccess ? class="text-white bg-red-700 {$submitSuccess
'dark:bg-green-600 hover:bg-green-800 dark:hover:bg-green-700 focus:ring-4 focus:ring-green-300 dark:focus:ring-green-800' : ? 'bg-green-600 dark:bg-green-600 hover:bg-green-700 dark:hover:bg-green-700 focus:ring-4 focus:ring-green-800 dark:focus:ring-green-800'
'dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-700 focus:ring-4 focus:ring-red-300 dark:focus:ring-red-800' : 'bg-red-600 dark:bg-red-600 hover:bg-red-700 dark:hover:bg-red-700 focus:ring-4 focus:ring-red-800 dark:focus:ring-red-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
} font-medium on:click={deleteEntries}
rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none" >
on:click={deleteEntries}> <svg
<svg id="submit-loader" aria-hidden="true" role="status" id="submit-loader"
class="{isSubmitting ? 'inline': 'hidden'} w-4 h-4 me-3 text-white animate-spin" aria-hidden="true"
viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"> role="status"
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" class="{isSubmitting
fill="#E5E7EB"/> ? 'inline'
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" : 'hidden'} w-4 h-4 me-3 text-white animate-spin"
fill="currentColor"/> viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="#E5E7EB"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentColor"
/>
</svg> </svg>
Delete Entries Delete Entries
</Button> </Button>
</div> </div>
<div class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end"> <div
<Button disabled={isSubmitting} class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end"
>
<Button
disabled={isSubmitting}
id="sync-button" id="sync-button"
class="text-white bg-blue-700 {$submitSuccess ? class="text-white {$submitSuccess
'dark:bg-green-600 hover:bg-green-800 dark:hover:bg-green-700 focus:ring-4 focus:ring-green-300 dark:focus:ring-green-800' : ? 'bg-green-600 dark:bg-green-600 hover:bg-green-700 dark:hover:bg-green-700 focus:ring-4 focus:ring-green-800 dark:focus:ring-green-800'
'dark:bg-blue-600 hover:bg-blue-800 dark:hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800' : 'bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:ring-4 focus:ring-blue-800 dark:focus:ring-blue-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
} font-medium type="submit"
rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none" >
type="submit"> <svg
<svg id="submit-loader" aria-hidden="true" role="status" id="submit-loader"
class="{isSubmitting ? 'inline': 'hidden'} w-4 h-4 me-3 text-white animate-spin" aria-hidden="true"
viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"> role="status"
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" class="{isSubmitting
fill="#E5E7EB"/> ? 'inline'
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" : 'hidden'} w-4 h-4 me-3 text-white animate-spin"
fill="currentColor"/> viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="#E5E7EB"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentColor"
/>
</svg> </svg>
Sync Changes Sync Changes
</Button> </Button>
<Button <Button
class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 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-100 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:text-white focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:text-white
dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700" dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
on:click={async () => { on:click={async () => {
await CheckIfAniListLoggedInAndLoadWatchList() await CheckIfAniListLoggedInAndLoadWatchList();
return push('/') return push("/");
}}> }}
>
Go Home Go Home
</Button> </Button>
</div> </div>
</div> </div>
<div> <div>
<h3 class="text-2xl"> <h3 class="text-2xl">Summary</h3>
Summary
</h3>
<p>{@html currentAniListAnime.data.MediaList.media.description}</p> <p>{@html currentAniListAnime.data.MediaList.media.description}</p>
</div> </div>
</form> </form>

View File

@ -1,8 +1,14 @@
<script lang="ts"> <script lang="ts">
import {createTable, Render, Subscribe} from "svelte-headless-table"; import {
createRender,
createTable,
Render,
Subscribe,
} from "svelte-headless-table"
// @ts-ignore // @ts-ignore
import { addSortBy } from "svelte-headless-table/plugins"; import { addSortBy } from "svelte-headless-table/plugins"
import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte" import { tableItems } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"
import WebsiteLink from "./WebsiteLink.svelte"
//when adding sort here is code { sort: addSortBy() } //when adding sort here is code { sort: addSortBy() }
const table = createTable(tableItems, { sort: addSortBy() }) const table = createTable(tableItems, { sort: addSortBy() })
@ -10,79 +16,80 @@
const columns = table.createColumns([ const columns = table.createColumns([
table.column({ table.column({
header: "Service Id", header: "Service Id",
accessor: 'id', cell: ({ value }) => createRender(WebsiteLink, {id: value}),
accessor: "id",
}), }),
table.column({ table.column({
header: "Anime Title", header: "Anime Title",
accessor: "title", accessor: "title",
}), }),
table.column({ table.column({
header: 'Service', header: "Service",
accessor: 'service', accessor: "service",
}), }),
table.column({ table.column({
header: 'Episode Progress', header: "Episode Progress",
accessor: 'progress', accessor: "progress",
}), }),
table.column({ table.column({
header: 'Status', header: "Status",
accessor: 'status', accessor: "status",
}), }),
table.column({ table.column({
header: 'Started At', header: "Started At",
accessor: 'startedAt', accessor: "startedAt",
}), }),
table.column({ table.column({
header: 'Completed At', header: "Completed At",
accessor: 'completedAt', accessor: "completedAt",
}), }),
table.column({ table.column({
header: 'Rating', header: "Rating",
accessor: 'score', accessor: "score",
}), }),
table.column({ table.column({
header: 'Repeat', header: "Repeat",
accessor: 'repeat', accessor: "repeat",
}), }),
table.column({ table.column({
header: 'Notes', header: "Notes",
accessor: 'notes', accessor: "notes",
}), }),
]) ])
//add pluginStates when add sort back //add pluginStates when add sort back
const { const { headerRows, rows, tableAttrs, tableBodyAttrs } =
headerRows, table.createViewModel(columns)
rows,
tableAttrs,
tableBodyAttrs,
} = table.createViewModel(columns)
</script> </script>
<div class="relative overflow-x-auto rounded-lg mb-5"> <div class="relative overflow-x-auto rounded-lg mb-5">
<table <table
class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400" class="w-full text-sm text-left rtl:text-right text-gray-400"
{...$tableAttrs} {...$tableAttrs}
> >
<thead <thead class="text-xs uppercase bg-gray-700 text-gray-400">
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"
>
{#each $headerRows as headerRow (headerRow.id)} {#each $headerRows as headerRow (headerRow.id)}
<Subscribe attrs={headerRow.attrs()} let:attrs> <Subscribe attrs={headerRow.attrs()} let:attrs>
<tr {...attrs}> <tr {...attrs}>
{#each headerRow.cells as cell (cell.id)} {#each headerRow.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props> <Subscribe
attrs={cell.attrs()}
let:attrs
props={cell.props()}
let:props
>
<th <th
{...attrs} {...attrs}
on:click={props.sort.toggle} on:click={props.sort.toggle}
class:sorted={props.sort.order !== undefined} class:sorted={props.sort.order !==
undefined}
class="px-6 py-3" class="px-6 py-3"
> >
<div> <div>
<Render of={cell.render()} /> <Render of={cell.render()} />
{#if props.sort.order === 'asc'} {#if props.sort.order === "asc"}
⬇️ ⬇️
{:else if props.sort.order === 'desc'} {:else if props.sort.order === "desc"}
⬆️ ⬆️
{/if} {/if}
</div> </div>
@ -96,10 +103,7 @@
<tbody {...$tableBodyAttrs}> <tbody {...$tableBodyAttrs}>
{#each $rows as row (row.id)} {#each $rows as row (row.id)}
<Subscribe attrs={row.attrs()} let:attrs> <Subscribe attrs={row.attrs()} let:attrs>
<tr <tr {...attrs} class="bg-gray-800 border-gray-700">
{...attrs}
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
>
{#each row.cells as cell (cell.id)} {#each row.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs> <Subscribe attrs={cell.attrs()} let:attrs>
<td {...attrs} class="px-6 py-4"> <td {...attrs} class="px-6 py-4">

View File

@ -1,74 +1,128 @@
<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 {aniListLoggedIn, aniListUser, malLoggedIn, simklLoggedIn, logoutOfAniList, logoutOfMAL, logoutOfSimkl} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte" import {
aniListLoggedIn,
aniListUser,
malUser,
simklUser,
malLoggedIn,
simklLoggedIn,
loginToAniList,
loginToMAL,
loginToSimkl,
logoutOfAniList,
logoutOfMAL,
logoutOfSimkl,
} 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 {SimklUser} from "../simkl/types/simklTypes";
let currentAniListUser: AniListUser let currentAniListUser: AniListUser;
let isAniListLoggedIn: boolean let currentMALUser: MyAnimeListUser;
let isSimklLoggedIn: boolean let currentSimklUser: SimklUser;
let isMALLoggedIn: boolean let isAniListLoggedIn: boolean;
let isSimklLoggedIn: boolean;
let isMALLoggedIn: boolean;
aniListUser.subscribe((value) => currentAniListUser = value) aniListUser.subscribe((value) => (currentAniListUser = value));
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value) malUser.subscribe((value) => (currentMALUser = value))
simklLoggedIn.subscribe((value) => isSimklLoggedIn = value) simklUser.subscribe(value => currentSimklUser = value)
malLoggedIn.subscribe((value) => isMALLoggedIn = value) aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = 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");
} }
</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 src="{currentAniListUser.data.Viewer.avatar.medium}" class="cursor-pointer" <Avatar
dot={{ color: 'green' }}/> src={currentAniListUser.data.Viewer.avatar.medium}
class="cursor-pointer"
dot={{ color: "green" }}
/>
{:else} {:else}
<Avatar class="cursor-pointer" dot={{ color: 'red' }}/> <Avatar class="cursor-pointer" dot={{ color: "red" }} />
{/if} {/if}
</button> </button>
<div id="userDropdown" <div
class="absolute hidden right-0 2xl:left-1/2 2xl:-translate-x-1/2 z-10 bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700 dark:divide-gray-600"> id="userDropdown"
<div class="px-4 py-3 text-sm text-gray-900 dark: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"
>
<div class="px-4 py-3 text-sm text-white">
{#if isAniListLoggedIn} {#if isAniListLoggedIn}
<div>{currentAniListUser.data.Viewer.name}</div> <div>{currentAniListUser.data.Viewer.name}</div>
{:else} {:else}
<div>You are not logged into AniList</div> <div>You are not logged into AniList</div>
{/if} {/if}
</div> </div>
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200" <ul
aria-labelledby="dropdownUserAvatarButton"> class="py-2 text-sm text-gray-200"
aria-labelledby="dropdownUserAvatarButton"
>
{#if isAniListLoggedIn} {#if isAniListLoggedIn}
<li> <li>
<button on:click={logoutOfAniList} <button
class="block px-4 py-2 w-full hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> on:click={logoutOfAniList}
Logout Anilist 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={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> </button>
</li> </li>
{/if} {/if}
{#if isMALLoggedIn} {#if isMALLoggedIn}
<li> <li>
<button on:click={logoutOfMAL} <button
class="block px-4 py-2 w-full hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> on:click={logoutOfMAL}
Logout MAL 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={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> </button>
</li> </li>
{/if} {/if}
{#if isSimklLoggedIn} {#if isSimklLoggedIn}
<li> <li>
<button on:click={logoutOfSimkl} <button
class="block px-4 py-2 w-full hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> on:click={logoutOfSimkl}
Logout Simkl 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={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> </button>
</li> </li>
{/if} {/if}
</ul> </ul>
<div class="py-2"> <div class="py-2">
<button on:click={() => runtime.Quit()} <button
class="block px-4 py-2 w-full text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"> 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 Exit Application
</button> </button>
</div> </div>

View File

@ -2,41 +2,25 @@
import Search from "./Search.svelte" import Search from "./Search.svelte"
import { import {
aniListLoggedIn, aniListLoggedIn,
aniListUser,
loginToAniList, loginToAniList,
loginToMAL, loginToMAL,
loginToSimkl, loginToSimkl,
malLoggedIn, malLoggedIn,
malUser,
simklLoggedIn, simklLoggedIn,
simklUser,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte" } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"
import type {AniListUser} from "../anilist/types/AniListTypes";
import type {SimklUser} from "../simkl/types/simklTypes";
import type {MyAnimeListUser} from "../mal/types/MALTypes";
import AvatarMenu from "./AvatarMenu.svelte"; import AvatarMenu from "./AvatarMenu.svelte";
import logo from "../assets/images/AniTrackLogo.svg" import logo from "../assets/images/AniTrackLogo.svg"
import {location} from "svelte-spa-router";
let isAniListLoggedIn: boolean let isAniListLoggedIn: boolean
let isSimklLoggedIn: boolean let isSimklLoggedIn: boolean
let isMALLoggedIn: boolean let isMALLoggedIn: boolean
let currentAniListUser: AniListUser
let currentSimklUser: SimklUser
let currentMALUser: MyAnimeListUser
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)
aniListUser.subscribe((value) => currentAniListUser = value)
simklUser.subscribe((value) => currentSimklUser = value)
malUser.subscribe((value) => currentMALUser = value)
let currentLocation: any
location.subscribe(value => currentLocation = value)
</script> </script>
<nav class="bg-white border-gray-200 dark:bg-gray-900"> <nav class="border-gray-200 bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"> <div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<div class="flex items-center space-x-3 rtl:space-x-reverse"> <div class="flex items-center space-x-3 rtl:space-x-reverse">
<a href="/"><img src={logo} class="h-8" alt="AniTrack Logo"/></a> <a href="/"><img src={logo} class="h-8" alt="AniTrack Logo"/></a>
@ -50,7 +34,7 @@
let menu = document.querySelector("#navbar-user") let menu = document.querySelector("#navbar-user")
menu.classList.toggle("hidden") menu.classList.toggle("hidden")
}} type="button" }} type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg min-[950px]:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm rounded-lg min-[950px]:hidden focus:outline-none focus:ring-2 text-gray-400 hover:bg-gray-700 focus:ring-gray-600"
aria-controls="navbar-user" aria-expanded="false"> aria-controls="navbar-user" aria-expanded="false">
<span class="sr-only">Open main menu</span> <span class="sr-only">Open main menu</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" <svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
@ -60,40 +44,26 @@
</svg> </svg>
</button> </button>
</div> </div>
<div class="hidden items-center justify-between w-full pb-4 min-[950px]:pb-0 min-[950px]:flex min-[950px]:w-auto min-[950px]:order-1 border border-gray-100 dark:border-gray-700 min-[950px]:border-0 bg-gray-50 dark:bg-gray-800 min-[950px]:bg-transparent min-[950px]:dark:bg-transparent rounded-lg" id="navbar-user"> <div class="hidden items-center justify-between w-full pb-4 min-[950px]:pb-0 min-[950px]:flex min-[950px]:w-auto min-[950px]:order-1 border border-gray-700 min-[950px]:border-0 bg-gray-800 min-[950px]:bg-transparent rounded-lg" id="navbar-user">
<ul class="flex flex-col font-medium pb-6 min-[950px]:p-0 mt-4 min-[950px]:space-x-8 rtl:space-x-reverse min-[950px]:flex-row min-[950px]:mt-0"> <ul class="flex flex-col font-medium pb-6 min-[950px]:p-0 mt-4 min-[950px]:space-x-8 rtl:space-x-reverse min-[950px]:flex-row min-[950px]:mt-0">
<li> <li>
{#if isAniListLoggedIn} {#if !isAniListLoggedIn}
<div class="flex justify-center py-2 px-3 rounded bg-transparent min-[950px]:p-0"> <button on:click={loginToAniList}>
<span class="w-48 min-[950px]:w-auto bg-green-100 text-green-800 text-sm font-medium me-2 px-3 py-2 rounded dark:bg-green-800 dark:text-green-200 cursor-default">AniList: {currentAniListUser.data.Viewer.name}</span> <!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
</div>
{:else}
<button on:click={loginToAniList}
class="block py-2 px-3 text-gray-900 w-full min-[950px]:w-auto rounded hover:bg-gray-100 min-[950px]:hover:bg-transparent min-[950px]:hover:text-blue-700 min-[950px]:p-0 dark:text-white min-[950px]:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white min-[950px]:dark:hover:bg-transparent dark:border-gray-700">
AniList Login AniList Login
</button> </button>
{/if} {/if}
</li> {#if !isMALLoggedIn}
<li> <button on:click={loginToMAL}>
{#if isMALLoggedIn} <!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded min-[950px]:p-0 text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
<div class="flex justify-center py-2 px-3 rounded bg-transparent min-[950px]:p-0">
<span class="w-48 min-[950px]:w-auto bg-blue-100 text-blue-800 text-sm font-medium me-2 px-3 py-2 rounded dark:bg-blue-800 dark:text-blue-200 cursor-default">MyAnimeList: {currentMALUser.name}</span>
</div>
{:else}
<button on:click={loginToMAL}
class="block py-2 px-3 text-gray-900 w-full min-[950px]:w-auto rounded hover:bg-gray-100 min-[950px]:hover:bg-transparent min-[950px]:hover:text-blue-700 min-[950px]:p-0 dark:text-white min-[950px]:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white min-[950px]:dark:hover:bg-transparent dark:border-gray-700">
MyAnimeList Login MyAnimeList Login
</button> </button>
{/if} {/if}
</li> </li>
<li> <li>
{#if isSimklLoggedIn} {#if !isSimklLoggedIn}
<div class="flex justify-center py-2 px-3 rounded bg-transparent min-[950px]:p-0"> <button on:click={loginToSimkl}>
<span class="w-48 min-[950px]:w-auto bg-indigo-100 text-indigo-800 text-sm font-medium me-2 px-3 py-2 rounded dark:bg-indigo-800 dark:text-indigo-200 cursor-default">Simkl: {currentSimklUser.user.name}</span> <!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded min-[950px]:p-0 text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
</div>
{:else}
<button on:click={loginToSimkl}
class="block py-2 px-3 text-gray-900 w-full min-[950px]:w-auto rounded hover:bg-gray-100 min-[950px]:hover:bg-transparent min-[950px]:hover:text-blue-700 min-[950px]:p-0 dark:text-white min-[950px]:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white min-[950px]:dark:hover:bg-transparent dark:border-gray-700">
Simkl Login Simkl Login
</button> </button>
{/if} {/if}

View File

@ -50,14 +50,14 @@
{#if page === 1} {#if page === 1}
<li> <li>
<button disabled <button disabled
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight text-gray-500 bg-white border border-e-0 border-gray-300 rounded-s-lg dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 cursor-default"> class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 cursor-default">
Previous Previous
</button> </button>
</li> </li>
{:else} {:else}
<li> <li>
<button on:click={() => ChangeWatchListPage(page-1)} <button on:click={() => ChangeWatchListPage(page-1)}
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight text-gray-500 bg-white border border-e-0 border-gray-300 rounded-s-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"> 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 Previous
</button> </button>
</li> </li>
@ -66,26 +66,26 @@
{#if i + 1 === page} {#if i + 1 === page}
<li> <li>
<button on:click={() => ChangeWatchListPage(i+1)} <button on:click={() => ChangeWatchListPage(i+1)}
class="flex items-center justify-center px-4 h-10 leading-tight border border-gray-300 bg-gray-100 dark:border-gray-700 dark:bg-gray-700 dark:text-white">{i + 1}</button> 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> </li>
{:else} {:else}
<li> <li>
<button on:click={() => ChangeWatchListPage(i+1)} <button on:click={() => ChangeWatchListPage(i+1)}
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">{i + 1}</button> 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> </li>
{/if} {/if}
{/each} {/each}
{#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage} {#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage}
<li> <li>
<button disabled <button disabled
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 cursor-default"> 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 Next
</button> </button>
</li> </li>
{:else} {:else}
<li> <li>
<button on:click={() => ChangeWatchListPage(page+1)} <button on:click={() => ChangeWatchListPage(page+1)}
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"> 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 Next
</button> </button>
</li> </li>
@ -96,7 +96,7 @@
<div class="flex mt-5"> <div class="flex mt-5">
<div class="w-20 mx-auto"> <div class="w-20 mx-auto">
<select bind:value={perPage} on:change={(e) => changeCountPerPage(e)} id="countPerPage" <select bind:value={perPage} on:change={(e) => changeCountPerPage(e)} id="countPerPage"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> 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} {#each perPageOptions as option}
<option value={option}> <option value={option}>
{option} {option}
@ -117,8 +117,8 @@
<div class="max-w-xs mx-auto"> <div class="max-w-xs mx-auto">
<div class="relative flex items-center max-w-[11rem]"> <div class="relative flex items-center max-w-[11rem]">
<button type="button" id="decrement-button" on:click={() => ChangeWatchListPage(page-1)} <button type="button" id="decrement-button" on:click={() => ChangeWatchListPage(page-1)}
class="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-s-lg p-3 h-11 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none"> 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-gray-900 dark:text-white" aria-hidden="true" <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"> 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" <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M1 1h16"/> d="M1 1h16"/>
@ -126,14 +126,14 @@
</button> </button>
<input type="number" min="1" max="{aniListWatchListLoaded.data.Page.pageInfo.lastPage}" <input type="number" min="1" max="{aniListWatchListLoaded.data.Page.pageInfo.lastPage}"
on:keydown={changePage} id="page-counter" on:keydown={changePage} id="page-counter"
class="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none bg-gray-50 border-x-0 border-gray-300 h-11 font-medium text-center text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 block w-full pb-6 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" 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/> 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"> <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> <span>Page #</span>
</div> </div>
<button type="button" id="increment-button" on:click={() => ChangeWatchListPage(page+1)} <button type="button" id="increment-button" on:click={() => ChangeWatchListPage(page+1)}
class="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-e-lg p-3 h-11 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none"> 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-gray-900 dark:text-white" aria-hidden="true" <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"> 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" <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 1v16M1 9h16"/> d="M9 1v16M1 9h16"/>

View File

@ -25,11 +25,11 @@
<div id="searchDropdown" class="relative w-64 md:w-48"> <div id="searchDropdown" class="relative w-64 md:w-48">
<div class="flex"> <div class="flex">
<label for="anime-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Find <label for="anime-search" class="mb-2 text-sm font-medium sr-only text-white">Find
Anime</label> Anime</label>
<div class="relative w-full"> <div class="relative w-full">
<input type="search" id="anime-search" bind:value={aniSearch} <input type="search" id="anime-search" bind:value={aniSearch}
class="rounded-s-lg block p-2.5 w-full z-20 text-sm text-gray-900 bg-gray-50 rounded-e-lg border-s-gray-50 border-s-2 border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-s-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:border-blue-500" class="rounded-s-lg block p-2.5 w-full z-20 text-sm rounded-e-lg bg-gray-700 border-s-gray-700 border-gray-600 placeholder-gray-400 text-white focus:border-blue-500"
placeholder="Search for Anime" placeholder="Search for Anime"
on:keypress={(e) => { on:keypress={(e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
@ -39,7 +39,7 @@
}} }}
required/> required/>
<button id="aniListSearchButton" <button id="aniListSearchButton"
class="absolute top-0 end-0 h-full p-2.5 text-sm font-medium text-white bg-blue-700 rounded-e-lg border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" class="absolute top-0 end-0 h-full p-2.5 text-sm font-medium rounded-e-lg border focus:ring-4 focus:outline-none bg-blue-600 hover:bg-blue-700 focus:ring-blue-800"
on:click={() => { on:click={() => {
searchDropdown() searchDropdown()
if(aniSearch.length > 0) runAniListSearch() if(aniSearch.length > 0) runAniListSearch()
@ -60,7 +60,7 @@
aria-labelledby="aniListSearchButton"> aria-labelledby="aniListSearchButton">
{#each aniListSearch.data.Page.media as media} {#each aniListSearch.data.Page.media as media}
<li class="w-full"> <li class="w-full">
<div class="flex w-full items-start p-1 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white rounded-lg"> <div class="flex w-full items-start p-1 hover:bg-gray-600 hover:text-white rounded-lg">
<button on:click={() => { <button on:click={() => {
searchDropdown() searchDropdown()
push(`#/anime/${media.id}`) push(`#/anime/${media.id}`)

View File

@ -0,0 +1,28 @@
<script lang="ts">
import {BrowserOpenURL} from "../../wailsjs/runtime"
export let id: string
let url = ""
let isAniList = false
let isMAL = false
let isSimkl = false
let newId = id
let re = /[ams]?-?(.*)/
if (id !== undefined && id.length > 0) {
isAniList = id.includes("a-")
isMAL = id.includes("m-")
isSimkl = id.includes("s-")
newId = id.match(re)[1]
}
if (isAniList) url = `https://anilist.co/anime/${newId}`
if (isMAL) url = `https://myanimelist.net/anime/${newId}`
if (isSimkl) url = `https://simkl.com/anime/${newId}`
</script>
{#if url.length > 0}
<button class="underline underline-offset-2 px-4 py-1" on:click={() => BrowserOpenURL(url)}>{newId}</button>
{:else}
{id}
{/if}

View File

@ -1,7 +1,7 @@
export type TableItems = TableItem[] export type TableItems = TableItem[]
export type TableItem = { export type TableItem = {
id: number id: string
title: string title: string
service: string service: string
progress: number progress: number

View File

@ -16,6 +16,12 @@ body {
sans-serif; sans-serif;
} }
.maple-font {
font-family: "Maple Mono NF", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
}
@font-face { @font-face {
font-family: "Nunito"; font-family: "Nunito";
font-style: normal; font-style: normal;
@ -24,6 +30,14 @@ body {
url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2"); url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");
} }
@font-face {
font-family: "Maple Mono NF";
font-style: normal;
font-weight: 800;
src: local(""),
url("assets/fonts/MapleMono-Bold.woff2") format("woff2");
}
#app { #app {
height: 100vh; height: 100vh;
text-align: center; text-align: center;

View File

@ -1,3 +1,4 @@
import flowbitePlugin from 'flowbite/plugin'
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: [
@ -6,9 +7,7 @@ export default {
"./node_modules/flowbite/**/*.{html,js,svelte,ts}", "./node_modules/flowbite/**/*.{html,js,svelte,ts}",
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}", "./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
], ],
plugins: [ plugins: [ flowbitePlugin ],
require('flowbite/plugin')
],
darkMode: 'media', darkMode: 'media',

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

@ -202,8 +202,7 @@ export namespace main {
export class MALAnime { export class MALAnime {
id: id; id: id;
title: title; title: title;
// Go type: struct { Large string "json:\"large\" json:\"large\""; Medium string "json:\"medium\" json:\"medium\"" } main_picture: mainPicture;
main_picture: any;
alternative_titles: alternativeTitles; alternative_titles: alternativeTitles;
start_date: startDate; start_date: startDate;
end_date: endDate; end_date: endDate;
@ -231,6 +230,8 @@ export namespace main {
background: background; background: background;
related_anime: relatedAnime; related_anime: relatedAnime;
recommendations: recommendations; recommendations: recommendations;
// Go type: struct { NumListUsers int "json:\"num_list_users\" ts_type:\"numListUsers\""; Status struct { Watching string "json:\"watching\" ts_type:\"watching\""; Completed string "json:\"completed\" ts_type:\"completed\""; OnHold string "json:\"on_hold\" ts_type:\"onHold\""; Dropped string "json:\"dropped\" ts_type:\"dropped\""; PlanToWatch string "json:\"plan_to_watch\" ts_type:\"planToWatch\"" } }
Statistics: any;
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
return new MALAnime(source); return new MALAnime(source);
@ -240,7 +241,7 @@ export namespace main {
if ('string' === typeof source) source = JSON.parse(source); if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"]; this.id = source["id"];
this.title = source["title"]; this.title = source["title"];
this.main_picture = this.convertValues(source["main_picture"], Object); this.main_picture = source["main_picture"];
this.alternative_titles = source["alternative_titles"]; this.alternative_titles = source["alternative_titles"];
this.start_date = source["start_date"]; this.start_date = source["start_date"];
this.end_date = source["end_date"]; this.end_date = source["end_date"];
@ -268,6 +269,7 @@ export namespace main {
this.background = source["background"]; this.background = source["background"];
this.related_anime = source["related_anime"]; this.related_anime = source["related_anime"];
this.recommendations = source["recommendations"]; this.recommendations = source["recommendations"];
this.Statistics = this.convertValues(source["Statistics"], Object);
} }
convertValues(a: any, classs: any, asMap: boolean = false): any { convertValues(a: any, classs: any, asMap: boolean = false): any {
@ -311,7 +313,7 @@ export namespace main {
} }
} }
export class MALWatchlist { export class MALWatchlist {
data: struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" json:\"medium\""; Large string "json:\"large\" json:\"large\"" } "json:\"main_picture\" json:\"mainPicture\"" } "json:\"node\" json:\"node\""; ListStatus struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time.Time "json:\"updated_at\" ts_type:\"updatedAt\""; StartDate string "json:\"start_date\" ts_type:\"startDate\""; FinishDate string "json:\"finish_date\" ts_type:\"finishDate\"" } "json:\"list_status\" ts_type:\"listStatus\"" }[]; data: data;
paging: paging; paging: paging;
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
@ -320,27 +322,9 @@ export namespace main {
constructor(source: any = {}) { constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source); if ('string' === typeof source) source = JSON.parse(source);
this.data = this.convertValues(source["data"], struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" json:\"medium\""; Large string "json:\"large\" json:\"large\"" } "json:\"main_picture\" json:\"mainPicture\"" } "json:\"node\" json:\"node\""; ListStatus struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time.Time "json:\"updated_at\" ts_type:\"updatedAt\""; StartDate string "json:\"start_date\" ts_type:\"startDate\""; FinishDate string "json:\"finish_date\" ts_type:\"finishDate\"" } "json:\"list_status\" ts_type:\"listStatus\"" }); this.data = source["data"];
this.paging = source["paging"]; this.paging = source["paging"];
} }
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
} }
export class MalListStatus { export class MalListStatus {
status: status; status: status;
@ -621,11 +605,10 @@ export namespace struct { Node main {
} }
export namespace struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" json:\"medium\""; Large string "json:\"large\" json:\"large\"" } "json:\"main_picture\" json:\"mainPicture\"" } "json:\"node\" json:\"node\""; ListStatus struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time { export namespace struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" ts_type:\"medium\""; Large string "json:\"large\" ts_type:\"large\"" } "json:\"main_picture\" ts_type:\"mainPicture\"" } "json:\"node\" ts_type:\"node\""; ListStatus struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time {
export class { export class {
// Go type: struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" json:\"medium\""; Large string "json:\"large\" json:\"large\"" } "json:\"main_picture\" json:\"mainPicture\"" } node: node;
node: any;
list_status: listStatus; list_status: listStatus;
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
@ -634,27 +617,9 @@ export namespace struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Tit
constructor(source: any = {}) { constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source); if ('string' === typeof source) source = JSON.parse(source);
this.node = this.convertValues(source["node"], Object); this.node = source["node"];
this.list_status = source["list_status"]; this.list_status = source["list_status"];
} }
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
} }
} }

29
go.mod
View File

@ -1,48 +1,49 @@
module AniTrack module AniTrack
go 1.21 go 1.23
toolchain go1.21.11
require ( require (
github.com/99designs/keyring v1.2.2 github.com/99designs/keyring v1.2.2
github.com/wailsapp/wails/v2 v2.9.1 github.com/tidwall/gjson v1.18.0
github.com/wailsapp/wails/v2 v2.9.2
) )
require ( require (
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/bep/debounce v1.2.1 // indirect github.com/bep/debounce v1.2.1 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect github.com/danieljoos/wincred v1.2.2 // indirect
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/labstack/echo/v4 v4.12.0 // indirect github.com/labstack/echo/v4 v4.13.3 // indirect
github.com/labstack/gommon v0.4.2 // indirect github.com/labstack/gommon v0.4.2 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.4 // indirect github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect github.com/leaanthony/u v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mtibben/percent v0.2.1 // indirect github.com/mtibben/percent v0.2.1 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.47.0 // indirect github.com/samber/lo v1.47.0 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.13 // indirect github.com/wailsapp/go-webview2 v1.0.19 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.26.0 // indirect golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.28.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.25.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.24.0 // indirect golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.18.0 // indirect golang.org/x/text v0.21.0 // indirect
) )
// replace github.com/wailsapp/wails/v2 v2.9.1 => /home/nymusicman/go/pkg/mod // replace github.com/wailsapp/wails/v2 v2.9.1 => /home/nymusicman/go/pkg/mod

57
go.sum
View File

@ -8,8 +8,8 @@ github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo= github.com/dvsekhvalnov/jose2go v1.8.0 h1:LqkkVKAlHFfH9LOEl5fe4p/zL02OhWE7pCufMBG2jLA=
github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
@ -25,8 +25,8 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEE
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
@ -42,9 +42,8 @@ github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQc
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
@ -64,39 +63,45 @@ github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.13 h1:I17/44xQ5/SujBaAUS4KMkWJYIoWCp35YxCEFWsMLKA= github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
github.com/wailsapp/go-webview2 v1.0.13/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.9.1 h1:irsXnoQrCpeKzKTYZ2SUVlRRyeMR6I0vCO9Q1cvlEdc= github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k=
github.com/wailsapp/wails/v2 v2.9.1/go.mod h1:7maJV2h+Egl11Ak8QZN/jlGLj2wg05bsQS+ywJPT0gI= github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -34,9 +34,9 @@ func main() {
app, app,
}, },
Linux: &linux.Options{ Linux: &linux.Options{
Icon: []byte("./build/appicon.png"), Icon: []byte("./build/AniTrack.png"),
WindowIsTranslucent: false, WindowIsTranslucent: false,
WebviewGpuPolicy: linux.WebviewGpuPolicyAlways, WebviewGpuPolicy: linux.WebviewGpuPolicyNever,
ProgramName: "AniTrack", ProgramName: "AniTrack",
}, },
}) })

View File

@ -9,5 +9,9 @@
"author": { "author": {
"name": "John O'Keefe", "name": "John O'Keefe",
"email": "jokeefe@fastmail.com" "email": "jokeefe@fastmail.com"
},
"info": {
"productName": "AniTrack",
"productVersion": "0.1.5"
} }
} }