Compare commits

...

15 Commits

16 changed files with 766 additions and 325 deletions

View File

@ -3,6 +3,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"log" "log"
"net/http" "net/http"
@ -90,6 +91,15 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
timeUntilAiring timeUntilAiring
episode episode
} }
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
} }
status status
startedAt{ startedAt{
@ -180,6 +190,15 @@ func (a *App) AniListSearch(query string) any {
timeUntilAiring timeUntilAiring
episode episode
} }
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
} }
} }
} }
@ -256,6 +275,15 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
timeUntilAiring timeUntilAiring
episode episode
} }
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
} }
status status
startedAt { startedAt {
@ -330,7 +358,7 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
func (a *App) AniListUpdateEntry( func (a *App) AniListUpdateEntry(
mediaId int, mediaId int,
progress string, progress int,
status string, status string,
score float64, score float64,
repeat int, repeat int,
@ -354,7 +382,7 @@ func (a *App) AniListUpdateEntry(
} }
type Variables struct { type Variables struct {
MediaId int `json:"mediaId"` MediaId int `json:"mediaId"`
Progress string `json:"progress"` Progress int `json:"progress"`
Status string `json:"status"` Status string `json:"status"`
Score float64 `json:"score"` Score float64 `json:"score"`
Repeat int `json:"repeat"` Repeat int `json:"repeat"`
@ -363,10 +391,10 @@ func (a *App) AniListUpdateEntry(
CompletedAt CompletedAt `json:"completedAt"` CompletedAt CompletedAt `json:"completedAt"`
} }
body := struct { body := struct {
Mutation string `json:"mutation"` Query string `json:"query"`
Variables Variables `json:"variables"` Variables Variables `json:"variables"`
}{ }{
Mutation: ` Query: `
mutation( mutation(
$mediaId:Int, $mediaId:Int,
$progress:Int, $progress:Int,
@ -427,6 +455,8 @@ func (a *App) AniListUpdateEntry(
returnedBody, _ := AniListQuery(body, true) returnedBody, _ := AniListQuery(body, true)
fmt.Println(string(returnedBody))
var post interface{} var post interface{}
err := json.Unmarshal(returnedBody, &post) err := json.Unmarshal(returnedBody, &post)
if err != nil { if err != nil {

View File

@ -68,6 +68,15 @@ type MediaList struct {
TimeUntilAiring int `json:"timeUntilAiring"` TimeUntilAiring int `json:"timeUntilAiring"`
Episode int `json:"episode"` Episode int `json:"episode"`
} `json:"nextAiringEpisode"` } `json:"nextAiringEpisode"`
Tags []struct {
Id int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Rank int `json:"rank"`
IsMediaSpoiler bool `json:"isMediaSpoiler"`
IsAdult bool `json:"isAdult"`
} `json:"tags"`
IsAdult bool `json:"isAdult"`
} `json:"media"` } `json:"media"`
Status string `json:"status"` Status string `json:"status"`
StartedAt struct { StartedAt struct {
@ -80,10 +89,10 @@ type MediaList struct {
Month int `json:"month"` Month int `json:"month"`
Day int `json:"day"` Day int `json:"day"`
} `json:"completedAt"` } `json:"completedAt"`
Notes string `json:"notes"` Notes string `json:"notes"`
Progress int `json:"progress"` Progress int `json:"progress"`
Score int `json:"score"` Score float64 `json:"score"`
Repeat int `json:"repeat"` Repeat int `json:"repeat"`
User struct { User struct {
ID int `json:"id"` ID int `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -103,66 +112,66 @@ type MediaList struct {
} `json:"user"` } `json:"user"`
} }
var MediaListSort = struct { //var MediaListSort = struct {
MediaId string // MediaId string
MediaIdDesc string // MediaIdDesc string
Score string // Score string
ScoreDesc string // ScoreDesc string
Status string // Status string
StatusDesc string // StatusDesc string
Progress string // Progress string
ProgressDesc string // ProgressDesc string
ProgressVolumes string // ProgressVolumes string
ProgressVolumesDesc string // ProgressVolumesDesc string
Repeat string // Repeat string
RepeatDesc string // RepeatDesc string
Priority string // Priority string
PriorityDesc string // PriorityDesc string
StartedOn string // StartedOn string
StartedOnDesc string // StartedOnDesc string
FinishedOn string // FinishedOn string
FinishedOnDesc string // FinishedOnDesc string
AddedTime string // AddedTime string
AddedTimeDesc string // AddedTimeDesc string
UpdatedTime string // UpdatedTime string
UpdatedTimeDesc string // UpdatedTimeDesc string
MediaTitleRomaji string // MediaTitleRomaji string
MediaTitleRomajiDesc string // MediaTitleRomajiDesc string
MediaTitleEnglish string // MediaTitleEnglish string
MediaTitleEnglishDesc string // MediaTitleEnglishDesc string
MediaTitleNative string // MediaTitleNative string
MediaTitleNativeDesc string // MediaTitleNativeDesc string
MediaPopularity string // MediaPopularity string
MediaPopularityDesc string // MediaPopularityDesc string
}{ //}{
MediaId: "MEDIA_ID", // MediaId: "MEDIA_ID",
MediaIdDesc: "MEDIA_ID_DESC", // MediaIdDesc: "MEDIA_ID_DESC",
Score: "SCORE", // Score: "SCORE",
ScoreDesc: "SCORE_DESC", // ScoreDesc: "SCORE_DESC",
Status: "STATUS", // Status: "STATUS",
StatusDesc: "STATUS_DESC", // StatusDesc: "STATUS_DESC",
Progress: "PROGRESS", // Progress: "PROGRESS",
ProgressDesc: "PROGRESS_DESC", // ProgressDesc: "PROGRESS_DESC",
ProgressVolumes: "PROGRESS_VOLUMES", // ProgressVolumes: "PROGRESS_VOLUMES",
ProgressVolumesDesc: "PROGRESS_VOLUMES_DESC", // ProgressVolumesDesc: "PROGRESS_VOLUMES_DESC",
Repeat: "REPEAT", // Repeat: "REPEAT",
RepeatDesc: "REPEAT_DESC", // RepeatDesc: "REPEAT_DESC",
Priority: "PRIORITY", // Priority: "PRIORITY",
PriorityDesc: "PRIORITY_DESC", // PriorityDesc: "PRIORITY_DESC",
StartedOn: "STARTED_ON", // StartedOn: "STARTED_ON",
StartedOnDesc: "STARTED_ON_DESC", // StartedOnDesc: "STARTED_ON_DESC",
FinishedOn: "FINISHED_ON", // FinishedOn: "FINISHED_ON",
FinishedOnDesc: "FINISHED_ON_DESC", // FinishedOnDesc: "FINISHED_ON_DESC",
AddedTime: "ADDED_TIME", // AddedTime: "ADDED_TIME",
AddedTimeDesc: "ADDED_TIME_DESC", // AddedTimeDesc: "ADDED_TIME_DESC",
UpdatedTime: "UPDATED_TIME", // UpdatedTime: "UPDATED_TIME",
UpdatedTimeDesc: "UPDATED_TIME_DESC", // UpdatedTimeDesc: "UPDATED_TIME_DESC",
MediaTitleRomaji: "MEDIA_TITLE_ROMAJI", // MediaTitleRomaji: "MEDIA_TITLE_ROMAJI",
MediaTitleRomajiDesc: "MEDIA_TITLE_ROMAJI_DESC", // MediaTitleRomajiDesc: "MEDIA_TITLE_ROMAJI_DESC",
MediaTitleEnglish: "MEDIA_TITLE_ENGLISH", // MediaTitleEnglish: "MEDIA_TITLE_ENGLISH",
MediaTitleEnglishDesc: "MEDIA_TITLE_ENGLISH_DESC", // MediaTitleEnglishDesc: "MEDIA_TITLE_ENGLISH_DESC",
MediaTitleNative: "MEDIA_TITLE_NATIVE", // MediaTitleNative: "MEDIA_TITLE_NATIVE",
MediaTitleNativeDesc: "MEDIA_TITLE_NATIVE_DESC", // MediaTitleNativeDesc: "MEDIA_TITLE_NATIVE_DESC",
MediaPopularity: "MEDIA_POPULARITY", // MediaPopularity: "MEDIA_POPULARITY",
MediaPopularityDesc: "MEDIA_POPULARITY_DESC", // MediaPopularityDesc: "MEDIA_POPULARITY_DESC",
} //}

View File

@ -28,14 +28,14 @@ type SimklUser struct {
type SimklWatchList struct { type SimklWatchList struct {
Anime []struct { Anime []struct {
LastWatchedAt string `json:"last_watched_at" ts_type:"last_watched_at"` LastWatchedAt string `json:"last_watched_at" ts_type:"last_watched_at"`
Status string `json:"status" ts_type:"status"` Status string `json:"status" ts_type:"status"`
UserRating int `json:"user_rating" ts_type:"user_rating"` UserRating float64 `json:"user_rating" ts_type:"user_rating"`
LastWatched string `json:"last_watched" ts_type:"last_watched"` LastWatched string `json:"last_watched" ts_type:"last_watched"`
NextToWatch string `json:"next_to_watch" ts_type:"next_to_watch"` NextToWatch string `json:"next_to_watch" ts_type:"next_to_watch"`
WatchedEpisodesCount int `json:"watched_episodes_count" ts_type:"watched_episodes_count"` WatchedEpisodesCount int `json:"watched_episodes_count" ts_type:"watched_episodes_count"`
TotalEpisodesCount int `json:"total_episodes_count" ts_type:"total_episodes_count"` TotalEpisodesCount int `json:"total_episodes_count" ts_type:"total_episodes_count"`
NotAiredEpisodesCount int `json:"not_aired_episodes_count" ts_type:"not_aired_episodes_count"` NotAiredEpisodesCount int `json:"not_aired_episodes_count" ts_type:"not_aired_episodes_count"`
Show struct { Show struct {
Title string `json:"title" ts_type:"title"` Title string `json:"title" ts_type:"title"`
Poster string `json:"poster" ts_type:"poster"` Poster string `json:"poster" ts_type:"poster"`

View File

@ -157,8 +157,6 @@ func getSimklAuthorizationToken(content string) SimklJWT {
func (a *App) GetSimklLoggedInUser() SimklUser { func (a *App) GetSimklLoggedInUser() SimklUser {
a.SimklLogin() a.SimklLogin()
fmt.Println("Token: ", simklJwt.AccessToken)
client := &http.Client{} client := &http.Client{}
req, _ := http.NewRequest("POST", "https://api.simkl.com/users/settings", nil) req, _ := http.NewRequest("POST", "https://api.simkl.com/users/settings", nil)

View File

@ -24,6 +24,14 @@ body:graphql {
media { media {
id id
idMal idMal
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
title { title {
romaji romaji
english english
@ -42,6 +50,7 @@ body:graphql {
timeUntilAiring timeUntilAiring
episode episode
} }
isAdult
} }
status status
startedAt { startedAt {

View File

@ -6,5 +6,6 @@ vars {
} }
vars:secret [ vars:secret [
code, code,
SIMKL_AUTH_TOKEN SIMKL_AUTH_TOKEN,
ANILIST_ACCESS_TOKEN
] ]

View File

@ -27,6 +27,7 @@
"@ernane/svelte-star-rating": "^1.1.7", "@ernane/svelte-star-rating": "^1.1.7",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"flowbite": "^2.4.1", "flowbite": "^2.4.1",
"flowbite-svelte": "^0.46.15" "flowbite-svelte": "^0.46.15",
"moment": "^2.30.1"
} }
} }

View File

@ -1,53 +1,39 @@
<script lang="ts"> <script lang="ts">
import {anilistModal, GetAniListSingleItemAndOpenModal, title} from "./GetAniListSingleItemAndOpenModal.svelte"; import {
anilistModal,
GetAniListSingleItemAndOpenModal,
title,
simklWatchList,
aniListLoggedIn,
simklLoggedIn
} from "./GlobalVariablesAndHelperFunctions.svelte";
import { import {
CheckIfAniListLoggedIn, CheckIfAniListLoggedIn,
CheckIfSimklLoggedIn, CheckIfSimklLoggedIn,
GetAniListLoggedInUser, GetAniListLoggedInUser,
GetAniListUserWatchingList, GetAniListUserWatchingList,
GetSimklLoggedInUser, SimklGetUserWatchlist, GetSimklLoggedInUser,
SimklGetUserWatchlist,
} from "../wailsjs/go/main/App"; } from "../wailsjs/go/main/App";
import {type AniListUser, MediaListSort} from "./anilist/types/AniListTypes"; import {type AniListUser, MediaListSort} from "./anilist/types/AniListTypes";
import type {AniListCurrentUserWatchList} from "./anilist/types/AniListCurrentUserWatchListType" import type {AniListCurrentUserWatchList} from "./anilist/types/AniListCurrentUserWatchListType"
import Header from "./Header.svelte"; import Header from "./Header.svelte";
import StarRatting from '@ernane/svelte-star-rating' import {Button, Rating} from "flowbite-svelte";
import {Button, Modal} from "flowbite-svelte"; import {default as Modal} from "./modal/Modal.svelte"
import ChangeDataDialogue from "./ChangeDataDialogue.svelte"; import ChangeDataDialogue from "./ChangeDataDialogue.svelte";
import {onMount} from "svelte"; import {onMount} from "svelte";
import type {SimklUser, SimklWatchList} from "./simkl/types/simklTypes"; import type {SimklUser} from "./simkl/types/simklTypes";
import {writable} from "svelte/store";
const config = {
readOnly: false,
countStars: 5,
range: {
min: 0,
max: 5,
step: 0.5
},
score: 0.0,
showScore: true,
scoreFormat: function () {
return `(${this.score.toFixed(1)}/${this.countStars})`
},
name: "",
starConfig: {
size: 20,
fillColor: '#F9ED4F',
strokeColor: "#e2c714",
unfilledColor: '#FFF',
strokeUnfilledColor: '#000'
}
}
let aniListLoggedIn = false
let aniListPrimary = true let aniListPrimary = true
let simklUser: SimklUser let simklUser: SimklUser
let simklLoggedIn = false
let aniListUser: AniListUser let aniListUser: AniListUser
let aniListWatchlist: AniListCurrentUserWatchList let aniListWatchlist: AniListCurrentUserWatchList
export let simklWatchList = writable({} as SimklWatchList) let isAniListLoggedIn: boolean
let isSimklLoggedIn: boolean
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
simklLoggedIn.subscribe((value) => isSimklLoggedIn = value)
let page = 1 let page = 1
let perPage = 20 let perPage = 20
const size = "xl" const size = "xl"
@ -60,10 +46,10 @@
if (aniListPrimary) { if (aniListPrimary) {
GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => { GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
aniListWatchlist = result aniListWatchlist = result
aniListLoggedIn = true aniListLoggedIn.set(true)
}) })
} else { } else {
aniListLoggedIn = result aniListLoggedIn.set(result)
} }
}) })
} }
@ -75,7 +61,7 @@
simklUser = result simklUser = result
SimklGetUserWatchlist().then(result => { SimklGetUserWatchlist().then(result => {
simklWatchList.set(result) simklWatchList.set(result)
simklLoggedIn = result simklLoggedIn.set(result)
}) })
}) })
} }
@ -85,38 +71,38 @@
function loginToSimkl(): void { function loginToSimkl(): void {
GetSimklLoggedInUser().then(result => { GetSimklLoggedInUser().then(result => {
simklUser = result simklUser = result
simklLoggedIn = true simklLoggedIn.set(true)
}) })
} }
function loginToAniList(): void { function loginToAniList(): void {
GetAniListLoggedInUser().then(result => { GetAniListLoggedInUser().then(result => {
aniListUser = result aniListUser = result
aniListLoggedIn = true aniListLoggedIn.set(true)
if (aniListPrimary) { if (aniListPrimary) {
GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then(result => aniListWatchlist = result) GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then(result => aniListWatchlist = result)
} }
}) })
} }
function anilistGetUserWatchlist(): void { // function anilistGetUserWatchlist(): void {
GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => { // GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
aniListWatchlist = result // aniListWatchlist = result
aniListLoggedIn = true // aniListLoggedIn.set(true)
}) // })
} // }
//
let count = 1; // let count = 1;
//
const decrement = () => { // const decrement = () => {
if (count > 0) { // if (count > 0) {
count-- // count--
} // }
} // }
//
const increment = () => { // const increment = () => {
count++ // count++
} // }
</script> </script>
@ -124,88 +110,56 @@
<Header/> <Header/>
<main> <main>
{#if aniListLoggedIn} {#if isAniListLoggedIn}
<div>You are logged into AniList, {aniListWatchlist.data.Page.mediaList[0].user.name}!</div> <div>You are logged into AniList, {aniListUser.data.Viewer.name}!</div>
{:else} {:else}
<button class="btn" on:click={loginToAniList}>Login to AniList</button> <button class="btn" on:click={loginToAniList}>Login to AniList</button>
{/if} {/if}
{#if simklLoggedIn} {#if isSimklLoggedIn}
<div>You are logged into Simkl, {simklUser.user.name}</div> <div>You are logged into Simkl, {simklUser.user.name}</div>
{:else} {:else}
<Button class="btn" on:click={loginToSimkl}>Login to Simkl</Button> <Button class="btn" on:click={loginToSimkl}>Login to Simkl</Button>
{/if} {/if}
{#if aniListLoggedIn} {#if isAniListLoggedIn}
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl lg:px-8"> <div class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8">
<h1>Your Watching List</h1> <h1 class="text-left text-xl font-bold mb-4">Your Watching List</h1>
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8"> <div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
{#each aniListWatchlist.data.Page.mediaList as media} {#each aniListWatchlist.data.Page.mediaList as media}
<div class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7"> <div class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7">
<button on:click={() => GetAniListSingleItemAndOpenModal(media.media.id, true)} class="group"> <div class="flex flex-col items-center group">
<div class="flex flex-col items-center"> <button on:click={() => GetAniListSingleItemAndOpenModal(media.media.id, true)}>
<img class="rounded-lg" src={media.media.coverImage.large} alt={ <img class="rounded-lg" src={media.media.coverImage.large} alt={
media.media.title.english === "" ? media.media.title.english === "" ?
media.media.title.romaji : media.media.title.romaji :
media.media.title.english media.media.title.english
}/> }/>
{config.score = media.score / 2.0} </button>
<StarRatting {config}/> <Rating id="anime-rating" total={5} size={35} rating={media.score/2.0} />
<h3 class="mt-4 text-sm text-white-700">{ <button class="mt-4 text-md font-semibold text-white-700"
on:click={() => GetAniListSingleItemAndOpenModal(media.media.id, true)}>
{
media.media.title.english === "" ? media.media.title.english === "" ?
media.media.title.romaji : media.media.title.romaji :
media.media.title.english media.media.title.english
}</h3> }
<p class="mt-1 text-lg font-medium text-white-900">{media.progress} </button>
/ {media.media.nextAiringEpisode.episode !== 0 ? <p class="mt-1 text-lg font-medium text-white-900">{media.progress}
media.media.nextAiringEpisode.episode - 1 : media.media.episodes}</p> / {media.media.nextAiringEpisode.episode !== 0 ?
{#if media.media.episodes > 0} media.media.nextAiringEpisode.episode - 1 : media.media.episodes}</p>
<p class="mt-1 text-lg font-medium text-white-900">Total {#if media.media.episodes > 0}
Episodes: {media.media.episodes}</p> <p class="mt-1 text-lg font-medium text-white-900">Total
{/if} Episodes: {media.media.episodes}</p>
</div> {/if}
</button> </div>
</div> </div>
{/each} {/each}
</div> </div>
</div> </div>
{/if} {/if}
<div class="flex items-center justify-center">
<button on:click={decrement}
class="flex justify-center items-center w-10 h-10 rounded-full text-white focus:outline-none bg-gray-400 hover:bg-gray-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path>
</svg>
</button>
<input bind:value={count} class="text-2xl font-bold mx-4"/>
<button on:click={increment}
class="flex justify-center items-center w-10 h-10 rounded-full text-white focus:outline-none bg-indigo-500 hover:bg-indigo-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v12M6 12h12"></path>
</svg>
</button>
</div>
<!-- <div class="relative max-w-sm">-->
<!-- <div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">-->
<!-- <svg class="w-4 h-4 text-gray-500 dark: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>-->
<!-- </div>-->
<!-- <input-->
<!-- datepicker-->
<!-- datepicker-buttons-->
<!-- datepicker-autoselect-today-->
<!-- datepicker-autohide-->
<!-- datepicker-title="Started At"-->
<!-- id="startedAt"-->
<!-- type="text"-->
<!-- 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 ps-10 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"-->
<!-- placeholder="Select date">-->
<!-- </div>-->
<Modal title={$title} bind:open={$anilistModal} {size} autoclose={false}> <Modal title={$title} bind:open={$anilistModal} {size} autoclose={false}>
<ChangeDataDialogue/> <ChangeDataDialogue/>
</Modal> </Modal>

View File

@ -1,7 +1,90 @@
<script lang="ts"> <script lang="ts">
import {anime} from "./GetAniListSingleItemAndOpenModal.svelte"; import {
anilistModal,
simklWatchList,
aniListLoggedIn,
simklLoggedIn
} from "./GlobalVariablesAndHelperFunctions.svelte";
import {aniListAnime} from "./GlobalVariablesAndHelperFunctions.svelte";
import {Button} from "flowbite-svelte"; import {Button} from "flowbite-svelte";
// @ts-ignore
import StarRatting from "@ernane/svelte-star-rating" import StarRatting from "@ernane/svelte-star-rating"
import moment from 'moment'
import { Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from 'flowbite-svelte';
import { writable } from 'svelte/store';
import type {SimklAnime} from "./simkl/types/simklTypes";
import { get } from 'svelte/store';
import {AniListUpdateEntry} from "../wailsjs/go/main/App";
const simklWatch = get(simklWatchList);
let isAniListLoggedIn: boolean
let isSimklLoggedIn: boolean
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
simklLoggedIn.subscribe((value) => isSimklLoggedIn = value)
const simklAnime: SimklAnime | undefined = simklWatch.anime.filter((x) => Number(x.show.ids.mal) === aniListAnime.data.MediaList.media.idMal)[0]
let items = [];
if(isAniListLoggedIn) {
items.push({
id: aniListAnime.data.MediaList.id,
service: "AniList",
progress: aniListAnime.data.MediaList.progress,
status: aniListAnime.data.MediaList.status,
startedAt: `${aniListAnime.data.MediaList.startedAt.month}-${aniListAnime.data.MediaList.startedAt.day}-${aniListAnime.data.MediaList.startedAt.year}`,
completedAt: `${aniListAnime.data.MediaList.completedAt.month}-${aniListAnime.data.MediaList.completedAt.day}-${aniListAnime.data.MediaList.completedAt.year}`,
score: aniListAnime.data.MediaList.score,
repeat: aniListAnime.data.MediaList.repeat,
notes: aniListAnime.data.MediaList.notes
})
}
if(isSimklLoggedIn && simklAnime !== undefined) {
items.push({
id: simklAnime.show.ids.simkl,
service: "Simkl",
progress: simklAnime.watched_episodes_count,
status: simklAnime.status,
startedAt: "",
completedAt: "",
score: simklAnime.user_rating,
repeat: 0,
notes: ""
})
}
const sortKey = writable('service'); // default sort key
const sortDirection = writable(1); // default sort direction (ascending)
const sortItems = writable(items.slice()); // make a copy of the items array
// Define a function to sort the items
const sortTable = (key: any) => {
// If the same key is clicked, reverse the sort direction
if ($sortKey === key) {
sortDirection.update((val) => -val);
} else {
sortKey.set(key);
sortDirection.set(1);
}
};
$: {
const key = $sortKey;
const direction = $sortDirection;
const sorted = [...$sortItems].sort((a, b) => {
const aVal = a[key];
const bVal = b[key];
if (aVal < bVal) {
return -direction;
} else if (aVal > bVal) {
return direction;
}
return 0;
});
sortItems.set(sorted);
}
const ratingInWords = { const ratingInWords = {
0: "Not Reviewed", 0: "Not Reviewed",
@ -17,9 +100,14 @@
10: "Masterpiece", 10: "Masterpiece",
} }
const title = anime.data.MediaList.media.title.english !== "" ? const hide = (e) => {
anime.data.MediaList.media.title.english : e.preventDefault();
anime.data.MediaList.media.title.romaji anilistModal.set(false)
};
const title = aniListAnime.data.MediaList.media.title.english !== "" ?
aniListAnime.data.MediaList.media.title.english :
aniListAnime.data.MediaList.media.title.romaji
let config = { let config = {
readOnly: false, readOnly: false,
@ -29,7 +117,7 @@
max: 5, max: 5,
step: 0.5 step: 0.5
}, },
score: anime.data.MediaList.score / 2, score: aniListAnime.data.MediaList.score / 2,
showScore: false, showScore: false,
scoreFormat: function () { scoreFormat: function () {
return `(${this.score.toFixed(1)}/${this.countStars})` return `(${this.score.toFixed(1)}/${this.countStars})`
@ -45,42 +133,37 @@
} }
let values = { let values = {
progress: anime.data.MediaList.progress, progress: aniListAnime.data.MediaList.progress,
status: anime.data.MediaList.status, status: aniListAnime.data.MediaList.status,
startedAt: { startedAt: {
year: anime.data.MediaList.startedAt.year, year: aniListAnime.data.MediaList.startedAt.year,
month: anime.data.MediaList.startedAt.month, month: aniListAnime.data.MediaList.startedAt.month,
day: anime.data.MediaList.startedAt.day day: aniListAnime.data.MediaList.startedAt.day
}, },
completedAt: { completedAt: {
year: anime.data.MediaList.completedAt.year, year: aniListAnime.data.MediaList.completedAt.year,
month: anime.data.MediaList.completedAt.month, month: aniListAnime.data.MediaList.completedAt.month,
day: anime.data.MediaList.completedAt.day day: aniListAnime.data.MediaList.completedAt.day
}, },
repeat: anime.data.MediaList.repeat, repeat: aniListAnime.data.MediaList.repeat,
score: anime.data.MediaList.score, score: aniListAnime.data.MediaList.score,
notes: anime.data.MediaList.notes notes: aniListAnime.data.MediaList.notes
} }
let startedAtDate: string
let completedAtDate: string
const changeRating = (e) => { const changeRating = (e) => {
config.score = e.target.valueAsNumber config.score = e.target.valueAsNumber
values.score = e.target.valueAsNumber * 2 values.score = e.target.valueAsNumber * 2
} }
let count = 1;
const decrement = () => { if (values.startedAt.year > 0) {
if (count > 0) { let startedAtISODate = new Date(values.startedAt.year, values.startedAt.month - 1, values.startedAt.day)
count-- let startedAtMoment = moment(startedAtISODate)
} startedAtDate = startedAtMoment.format('YYYY-MM-DD')
} }
const increment = () => {
count++
}
let startedAtDate = `${values.startedAt.year}-${values.startedAt.month}-${values.startedAt.day}`
const transformStartedAtDate = (e) => { const transformStartedAtDate = (e) => {
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/ const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/
const date = re.exec(e.target.value) const date = re.exec(e.target.value)
@ -89,31 +172,62 @@
values.startedAt.day = Number(date[3]) values.startedAt.day = Number(date[3])
} }
console.log(startedAtDate) if (values.completedAt.year > 0) {
let completedAtISODate = new Date(values.completedAt.year, values.completedAt.month - 1, values.completedAt.day)
let completedAtMoment = moment(completedAtISODate)
completedAtDate = completedAtMoment.format('YYYY-MM-DD')
}
const transformCompletedAtDate = (e) => {
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/
const date = re.exec(e.target.value)
values.completedAt.year = Number(date[1])
values.completedAt.month = Number(date[2])
values.completedAt.day = Number(date[3])
}
const submitData = async () => {
await AniListUpdateEntry(
aniListAnime.data.MediaList.mediaId,
values.progress,
values.status,
values.score,
values.repeat,
values.notes,
values.startedAt.year,
values.startedAt.month,
values.startedAt.day,
values.completedAt.year,
values.completedAt.month,
values.completedAt.day
).then((value) => {
console.log(value)
})
}
</script> </script>
<div> <div id="inapp-data">
<div class="grid grid-rows-2 grid-cols-10 grid-flow-col gap-4 mt-10"> <div class="grid grid-cols-1 md:grid-cols-10 grid-flow-col gap-4">
<div class="row-span-2 col-span-2 space-y-3"> <div class="md:col-span-2 space-y-3">
<img class="rounded-lg" src={anime.data.MediaList.media.coverImage.large} alt="{title} Cover Image"> <img class="rounded-lg" src={aniListAnime.data.MediaList.media.coverImage.large} alt="{title} Cover Image">
<StarRatting bind:config on:change={changeRating}/> <StarRatting bind:config on:change={changeRating}/>
<p>Rating: {config.score * 2}</p> <p>Rating: {config.score * 2}</p>
<p>{ratingInWords[config.score * 2]}</p> <p>{ratingInWords[config.score * 2]}</p>
</div> </div>
<div class="col-span-8 z-30"> <div class="md:col-span-8 ">
<div class="flex flex-row p-10 justify-center 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>
<label for="episodes" class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Episode
<div class="mr-4"> Progress</label>
<label for="episodes" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Episode Progress</label>
<input <input
type="number" type="number"
name="episodes" name="episodes"
min="0" min="0"
id="episodes" id="episodes"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 focus:border-primary-600 block w-24 p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400
dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
bind:value={values.progress} bind:value={values.progress}
required> required>
@ -121,13 +235,13 @@
<div> <div>
<label for="status" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Select <label for="status"
your class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Status</label>
country</label>
<select id="status" <select id="status"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500
focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 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" dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
bind:value={values.status}
> >
<option>CURRENT</option> <option>CURRENT</option>
@ -139,58 +253,141 @@
</select> </select>
</div> </div>
</div> </div>
<div class="flex flex-row p-10 justify-center"> <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="relative z-40 max-w-sm"> <div>
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20"> <label for="startedAt" class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date
<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"/> Started</label>
</svg> <div class="relative max-w-sm">
</div> <div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
<!-- <input--> <svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true"
<!-- datepicker--> xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<!-- datepicker-buttons--> <path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/>
<!-- datepicker-autoselect-today--> </svg>
<!-- datepicker-autohide--> </div>
<!-- datepicker-title="Started At"--> <input
<!-- id="startedAt"--> id="startedAt"
<!-- type="text"--> type="date"
<!-- name="startedAt"--> name="startedAt"
<!-- class="z-50 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500--> 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 ps-10 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"-->
<!-- value={`${values.startedAt.year}-${values.startedAt.month}-${values.startedAt.day}`}-->
<!-- on:change={transformStartedAtDate}-->
<!-- >-->
<input
id="startedAt"
type="date"
name="startedAt"
class="z-50 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 ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 focus:border-blue-500 block w-full ps-10 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" dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
value={startedAtDate} value={startedAtDate}
on:change={transformStartedAtDate} placeholder="Date Started"
> on:change={transformStartedAtDate}
>
</div>
</div> </div>
<div>
<label for="completedAt" class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date
Completed</label>
<div class="relative max-w-sm">
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
<svg class="w-4 h-4 text-gray-500 dark: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>
</div>
<input
id="completedAt"
type="date"
name="completedAt"
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 ps-10 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"
value={completedAtDate}
placeholder="Date Completed"
on:change={transformCompletedAtDate}
>
</div>
</div>
<div>
<label for="repeat"
class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Rewatched</label>
<input
type="number"
name="repeat"
min="0"
id="repeat"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
focus:border-primary-600 block w-24 p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400
dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
bind:value={values.repeat}
required>
</div>
</div>
<div class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center">
<div class="w-full">
<label for="notes" class="text-left block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your
notes</label>
<textarea id="notes" rows="3"
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"
placeholder="Write your thoughts here..."
bind:value={values.notes}></textarea>
</div>
</div> </div>
</div> </div>
</div> </div>
<footer class="bg-white rounded-lg shadow m-4 dark:bg-gray-800"> <div id="external-data">
<div id="anilist-data" class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-16 lg:gap-x-36 group">
<h2 class="text-left mb-1 text-base font-semibold text-gray-900 dark:text-white">AniList</h2>
</div>
</div>
<Table hoverable={true}>
<TableHead>
<TableHeadCell class="cursor-pointer" on:click={() => sortTable('id')}>ID</TableHeadCell>
<TableHeadCell class="cursor-pointer" on:click={() => sortTable('service')}>Service</TableHeadCell>
<TableHeadCell class="cursor-pointer" on:click={() => sortTable('progress')}>Episode Progress</TableHeadCell>
<TableHeadCell class="cursor-pointer" on:click={() => sortTable('status')}>Status</TableHeadCell>
<TableHeadCell class="cursor-pointer" on:click={() => sortTable('startedAt')}>Date Started</TableHeadCell>
<TableHeadCell class="cursor-pointer" on:click={() => sortTable('completedAt')}>Date Completed</TableHeadCell>
<TableHeadCell class="cursor-pointer" on:click={() => sortTable('score')}>Rating</TableHeadCell>
<TableHeadCell class="cursor-pointer" on:click={() => sortTable('repeat')}>Rewatches</TableHeadCell>
<TableHeadCell>Notes</TableHeadCell>
</TableHead>
<TableBody tableBodyClass="divide-y">
{#each $sortItems as item}
<TableBodyRow>
<TableBodyCell class="overflow-x-auto">{item.id}</TableBodyCell>
<TableBodyCell class="overflow-x-auto">{item.service}</TableBodyCell>
<TableBodyCell class="overflow-x-auto">{item.progress}</TableBodyCell>
<TableBodyCell class="overflow-x-auto">{item.status}</TableBodyCell>
<TableBodyCell class="overflow-x-auto">{item.startedAt}</TableBodyCell>
<TableBodyCell class="overflow-x-auto">{item.completedAt}</TableBodyCell>
<TableBodyCell class="overflow-x-auto">{item.score}</TableBodyCell>
<TableBodyCell class="overflow-x-auto">{item.repeat}</TableBodyCell>
<TableBodyCell class="overflow-x-auto">{item.notes}</TableBodyCell>
</TableBodyRow>
{/each}
</TableBody>
</Table>
<div class="bg-white rounded-lg shadow max-w-4-4 dark:bg-gray-800">
<div class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end"> <div class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end">
<Button <Button
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium
rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none
dark:focus:ring-blue-800" dark:focus:ring-blue-800"
on:click={() => alert('Handle "success"')}>Sync Changes on:click={submitData}>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-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 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-100 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={hide}>
Cancel Cancel
</Button> </Button>
</div> </div>
</footer> </div>
<div>
<h3 class="text-2xl">
Summary
</h3>
<p>{@html aniListAnime.data.MediaList.media.description}</p>
</div>
</div> </div>

View File

@ -2,17 +2,21 @@
import {GetAniListItem} from "../wailsjs/go/main/App"; import {GetAniListItem} from "../wailsjs/go/main/App";
import type {AniListGetSingleAnime} from "./anilist/types/AniListCurrentUserWatchListType.js"; import type {AniListGetSingleAnime} from "./anilist/types/AniListCurrentUserWatchListType.js";
import {writable} from 'svelte/store' import {writable} from 'svelte/store'
import type {SimklWatchList} from "./simkl/types/simklTypes";
export let anime: AniListGetSingleAnime export let aniListAnime: AniListGetSingleAnime
export let title = writable("") export let title = writable("")
export let anilistModal = writable(false); export let anilistModal = writable(false);
export let aniListLoggedIn = writable(false)
export let simklLoggedIn = writable(false)
export let simklWatchList = writable({} as SimklWatchList)
export function GetAniListSingleItemAndOpenModal(aniId: number, login: boolean): void { export function GetAniListSingleItemAndOpenModal(aniId: number, login: boolean): void {
GetAniListItem(aniId, login).then(result => { GetAniListItem(aniId, login).then(result => {
anime = result aniListAnime = result
title.set(anime.data.MediaList.media.title.english === "" ? title.set(aniListAnime.data.MediaList.media.title.english === "" ?
anime.data.MediaList.media.title.romaji : aniListAnime.data.MediaList.media.title.romaji :
anime.data.MediaList.media.title.english) aniListAnime.data.MediaList.media.title.english)
anilistModal.set(true) anilistModal.set(true)
}) })
} }

View File

@ -2,7 +2,7 @@
import {AniListSearch} from "../wailsjs/go/main/App"; import {AniListSearch} from "../wailsjs/go/main/App";
import type {AniSearchList} from "./anilist/types/AniListTypes"; import type {AniSearchList} from "./anilist/types/AniListTypes";
import {GetAniListSingleItemAndOpenModal} from "./GetAniListSingleItemAndOpenModal.svelte"; import {GetAniListSingleItemAndOpenModal} from "./GlobalVariablesAndHelperFunctions.svelte";
let aniSearch = "" let aniSearch = ""
let aniListSearch: AniSearchList let aniListSearch: AniSearchList
@ -51,14 +51,18 @@
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">
<button on:click={() => { <div class="flex w-full items-start p-1 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white rounded-lg">
GetAniListSingleItemAndOpenModal(media.id, false) <button on:click={() => {
}} GetAniListSingleItemAndOpenModal(media.id, false)
class="flex w-full items-start p-1 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white rounded-lg"> }}
<img class="rounded-bl-lg rounded-tl-lg max-w-24 max-h-24" src={media.coverImage.large} >
alt="{media.title.english === '' || media.title.english === null ? media.title.romaji : media.title.english} Cover"> <img class="rounded-bl-lg rounded-tl-lg max-w-24 max-h-24" src={media.coverImage.large}
<p class="bg-gray-800 text-left w-full h-24 p-2 rounded-tr-lg rounded-br-lg">{media.title.english === '' || media.title.english === null ? media.title.romaji : media.title.english }</p> alt="{media.title.english === '' || media.title.english === null ? media.title.romaji : media.title.english} Cover">
</button> </button>
<button class="rounded-bl-lg rounded-tl-lg w-full h-24" on:click={() => {
GetAniListSingleItemAndOpenModal(media.id, false)
}} >{media.title.english === '' || media.title.english === null ? media.title.romaji : media.title.english }</button>
</div>
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@ -0,0 +1,155 @@
<script>import { twMerge } from "tailwind-merge";
import { Frame } from "flowbite-svelte";
import { createEventDispatcher } from "svelte";
import {CloseButton} from "flowbite-svelte";
import focusTrap from "../../node_modules/flowbite-svelte/dist/utils/focusTrap.js";
export let open = false;
export let title = "";
export let size = "md";
export let color = "default";
export let placement = "center";
export let autoclose = false;
export let outsideclose = false;
export let dismissable = true;
export let backdropClass = "fixed inset-0 z-20 bg-gray-900 bg-opacity-50 dark:bg-opacity-80";
export let classBackdrop = void 0;
export let dialogClass = "fixed top-0 start-0 end-0 h-modal md:inset-0 md:h-full z-30 w-full p-4 flex";
export let classDialog = void 0;
export let defaultClass = "relative flex flex-col mx-auto";
export let headerClass = "flex justify-between items-center p-4 md:p-5 rounded-t-lg";
export let classHeader = void 0;
export let bodyClass = "p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain";
export let classBody = void 0;
export let footerClass = "flex items-center p-4 md:p-5 space-x-3 rtl:space-x-reverse rounded-b-lg";
export let classFooter = void 0;
const dispatch = createEventDispatcher();
$: dispatch(open ? "open" : "close");
function prepareFocus(node) {
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);
let n;
while (n = walker.nextNode()) {
if (n instanceof HTMLElement) {
const el = n;
const [x, y] = isScrollable(el);
if (x || y) el.tabIndex = 0;
}
}
node.focus();
}
const getPlacementClasses = (placement2) => {
switch (placement2) {
case "top-left":
return ["justify-start", "items-start"];
case "top-center":
return ["justify-center", "items-start"];
case "top-right":
return ["justify-end", "items-start"];
case "center-left":
return ["justify-start", "items-center"];
case "center":
return ["justify-center", "items-center"];
case "center-right":
return ["justify-end", "items-center"];
case "bottom-left":
return ["justify-start", "items-end"];
case "bottom-center":
return ["justify-center", "items-end"];
case "bottom-right":
return ["justify-end", "items-end"];
default:
return ["justify-center", "items-center"];
}
};
const sizes = {
xs: "max-w-md",
sm: "max-w-lg",
md: "max-w-2xl",
lg: "max-w-4xl",
xl: "max-w-6xl"
};
const onAutoClose = (e) => {
const target = e.target;
if (autoclose && target?.tagName === "BUTTON") hide(e);
};
const onOutsideClose = (e) => {
const target = e.target;
if (outsideclose && target === e.currentTarget) hide(e);
};
const hide = (e) => {
e.preventDefault();
open = false;
};
const isScrollable = (e) => [e.scrollWidth > e.clientWidth && ["scroll", "auto"].indexOf(getComputedStyle(e).overflowX) >= 0, e.scrollHeight > e.clientHeight && ["scroll", "auto"].indexOf(getComputedStyle(e).overflowY) >= 0];
function handleKeys(e) {
if (e.key === "Escape" && dismissable) return hide(e);
}
$: backdropCls = twMerge(backdropClass, classBackdrop);
$: dialogCls = twMerge(dialogClass, classDialog, getPlacementClasses(placement));
$: frameCls = twMerge(defaultClass, "w-full divide-y", $$props.class);
$: headerCls = twMerge(headerClass, classHeader);
$: bodyCls = twMerge(bodyClass, classBody);
$: footerCls = twMerge(footerClass, classFooter);
</script>
{#if open}
<!-- backdrop -->
<div class={backdropCls}></div>
<!-- dialog -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<div on:keydown={handleKeys} on:wheel|preventDefault|nonpassive use:prepareFocus use:focusTrap on:click={onAutoClose} on:mousedown={onOutsideClose} class={dialogCls} tabindex="-1" aria-modal="true" role="dialog">
<div class="flex relative {sizes[size]} w-full max-h-full">
<!-- Modal content -->
<Frame rounded shadow {...$$restProps} class={frameCls} {color}>
<!-- Modal header -->
{#if $$slots.header || title}
<Frame class={headerCls} {color}>
<slot name="header">
<h3 class="text-xl font-semibold {color === 'default' ? '' : 'text-gray-900 dark:text-white'} p-0">
{title}
</h3>
</slot>
{#if dismissable}<CloseButton name="Close modal" {color} on:click={hide} />{/if}
</Frame>
{/if}
<!-- Modal body -->
<div class={bodyCls} role="document" on:keydown|stopPropagation={handleKeys} on:wheel|stopPropagation|passive>
{#if dismissable && !$$slots.header && !title}
<CloseButton name="Close modal" class="absolute top-3 end-2.5" {color} on:click={hide} />
{/if}
<slot></slot>
</div>
<!-- Modal footer -->
{#if $$slots.footer}
<Frame class={footerCls} {color}>
<slot name="footer"></slot>
</Frame>
{/if}
</Frame>
</div>
</div>
{/if}
<!--
@component
[Go to docs](https://flowbite-svelte.com/)
## Props
@prop export let open: boolean = false;
@prop export let title: string = '';
@prop export let size: SizeType = 'md';
@prop export let color: ComponentProps<Frame>['color'] = 'default';
@prop export let placement: ModalPlacementType = 'center';
@prop export let autoclose: boolean = false;
@prop export let outsideclose: boolean = false;
@prop export let dismissable: boolean = true;
@prop export let backdropClass: string = 'fixed inset-0 z-40 bg-gray-900 bg-opacity-50 dark:bg-opacity-80';
@prop export let classBackdrop: string | undefined = undefined;
@prop export let dialogClass: string = 'fixed top-0 start-0 end-0 h-modal md:inset-0 md:h-full z-50 w-full p-4 flex';
@prop export let classDialog: string | undefined = undefined;
@prop export let defaultClass: string = 'relative flex flex-col mx-auto';
@prop export let headerClass: string = 'flex justify-between items-center p-4 md:p-5 rounded-t-lg';
@prop export let classHeader: string | undefined = undefined;
@prop export let bodyClass: string = 'p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain';
@prop export let classBody: string | undefined = undefined;
@prop export let footerClass: string = 'flex items-center p-4 md:p-5 space-x-3 rtl:space-x-reverse rounded-b-lg';
@prop export let classFooter: string | undefined = undefined;
-->

77
frontend/src/modal/Modal.svelte.d.ts vendored Normal file
View File

@ -0,0 +1,77 @@
import { SvelteComponentTyped } from "svelte";
import type { Dismissable, SizeType } from '../types';
import type { ModalPlacementType } from '../types';
declare const __propDef: {
props: import("svelte/elements").HTMLAnchorAttributes & {
tag?: string;
color?: import("../utils/Frame.svelte").FrameColor;
rounded?: boolean;
border?: boolean;
shadow?: boolean;
node?: HTMLElement | undefined;
use?: import("svelte/action").Action<HTMLElement, any>;
options?: object;
class?: string;
role?: string;
open?: boolean;
transition?: (node: HTMLElement, params: any) => import("svelte/transition").TransitionConfig;
params?: any;
} & Dismissable & {
open?: boolean;
title?: string;
size?: SizeType;
placement?: ModalPlacementType;
autoclose?: boolean;
outsideclose?: boolean;
backdropClass?: string;
classBackdrop?: string;
dialogClass?: string;
classDialog?: string;
defaultClass?: string;
headerClass?: string;
classHeader?: string;
bodyClass?: string;
classBody?: string;
footerClass?: string;
classFooter?: string;
};
events: {
wheel: WheelEvent;
} & {
[evt: string]: CustomEvent<any>;
};
slots: {
header: {};
default: {};
footer: {};
};
};
export type ModalProps = typeof __propDef.props;
export type ModalEvents = typeof __propDef.events;
export type ModalSlots = typeof __propDef.slots;
/**
* [Go to docs](https://flowbite-svelte.com/)
* ## Props
* @prop export let open: boolean = false;
* @prop export let title: string = '';
* @prop export let size: SizeType = 'md';
* @prop export let color: ComponentProps<Frame>['color'] = 'default';
* @prop export let placement: ModalPlacementType = 'center';
* @prop export let autoclose: boolean = false;
* @prop export let outsideclose: boolean = false;
* @prop export let dismissable: boolean = true;
* @prop export let backdropClass: string = 'fixed inset-0 z-40 bg-gray-900 bg-opacity-50 dark:bg-opacity-80';
* @prop export let classBackdrop: string | undefined = undefined;
* @prop export let dialogClass: string = 'fixed top-0 start-0 end-0 h-modal md:inset-0 md:h-full z-50 w-full p-4 flex';
* @prop export let classDialog: string | undefined = undefined;
* @prop export let defaultClass: string = 'relative flex flex-col mx-auto';
* @prop export let headerClass: string = 'flex justify-between items-center p-4 md:p-5 rounded-t-lg';
* @prop export let classHeader: string | undefined = undefined;
* @prop export let bodyClass: string = 'p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain';
* @prop export let classBody: string | undefined = undefined;
* @prop export let footerClass: string = 'flex items-center p-4 md:p-5 space-x-3 rtl:space-x-reverse rounded-b-lg';
* @prop export let classFooter: string | undefined = undefined;
*/
export default class Modal extends SvelteComponentTyped<ModalProps, ModalEvents, ModalSlots> {
}
export {};

View File

@ -19,41 +19,43 @@ export type SimklUser = {
} }
export type SimklWatchList = { export type SimklWatchList = {
anime: [{ anime: [SimklAnime]
last_watched_at: string, }
status: string
user_rating: number, export type SimklAnime = {
last_watched: string, last_watched_at: string,
next_to_watch: string, status: string
watched_episodes_count: number, user_rating: number,
total_episodes_count: number, last_watched: string,
not_aired_episodes_count: number, next_to_watch: string,
show: { watched_episodes_count: number,
title: string, total_episodes_count: number,
poster: string, not_aired_episodes_count: number,
ids: { show: {
simkl: number, title: string,
slug: string, poster: string,
offjp: string, ids: {
tw: string, simkl: number,
ann: string, slug: string,
mal: string, offjp: string,
wikien: string, tw: string,
wikijp: string, ann: string,
allcin: string, mal: string,
imdb: string, wikien: string,
offen: string, wikijp: string,
crunchyroll: string, allcin: string,
tvdbslug: string, imdb: string,
anilist: string, offen: string,
animeplanet: string, crunchyroll: string,
anisearch: string, tvdbslug: string,
kitsu: string, anilist: string,
livechart: string, animeplanet: string,
traktslug: string, anisearch: string,
anidb: string, kitsu: string,
} livechart: string,
}, traktslug: string,
anime_type: string anidb: string,
}] }
},
anime_type: string
} }

View File

@ -6,7 +6,7 @@ export function AniListLogin():Promise<void>;
export function AniListSearch(arg1:string):Promise<any>; export function AniListSearch(arg1:string):Promise<any>;
export function AniListUpdateEntry(arg1:number,arg2:string,arg3:string,arg4:number,arg5:number,arg6:string,arg7:number,arg8:number,arg9:number,arg10:number,arg11:number,arg12:number):Promise<any>; export function AniListUpdateEntry(arg1:number,arg2:number,arg3:string,arg4:number,arg5:number,arg6:string,arg7:number,arg8:number,arg9:number,arg10:number,arg11:number,arg12:number):Promise<any>;
export function CheckIfAniListLoggedIn():Promise<boolean>; export function CheckIfAniListLoggedIn():Promise<boolean>;

View File

@ -95,7 +95,7 @@ export namespace main {
id: number; id: number;
mediaId: number; mediaId: number;
userId: number; userId: number;
// Go type: struct { ID int "json:\"id\""; IDMal int "json:\"idMal\""; Title struct { Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\""; Description string "json:\"description\""; CoverImage struct { Large string "json:\"large\"" } "json:\"coverImage\""; Season string "json:\"season\""; SeasonYear int "json:\"seasonYear\""; Status string "json:\"status\""; Episodes int "json:\"episodes\""; NextAiringEpisode struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" } "json:\"nextAiringEpisode\"" } // Go type: struct { ID int "json:\"id\""; IDMal int "json:\"idMal\""; Title struct { Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\""; Description string "json:\"description\""; CoverImage struct { Large string "json:\"large\"" } "json:\"coverImage\""; Season string "json:\"season\""; SeasonYear int "json:\"seasonYear\""; Status string "json:\"status\""; Episodes int "json:\"episodes\""; NextAiringEpisode struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" } "json:\"nextAiringEpisode\""; Tags []struct { Id int "json:\"id\""; Name string "json:\"name\""; Description string "json:\"description\""; Rank int "json:\"rank\""; IsMediaSpoiler bool "json:\"isMediaSpoiler\""; IsAdult bool "json:\"isAdult\"" } "json:\"tags\""; IsAdult bool "json:\"isAdult\"" }
media: any; media: any;
status: string; status: string;
// Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" } // Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" }