Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
d08283bd2d | |||
73d349ee1a | |||
c9c6650829 | |||
896c6640e2 | |||
18c744c1cf |
@ -71,7 +71,6 @@ 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, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
|
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
|
||||||
expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn")
|
expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn")
|
||||||
@ -96,7 +95,6 @@ func (a *App) CheckIfMyAnimeListLoggedIn() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) MyAnimeListLogin() {
|
func (a *App) MyAnimeListLogin() {
|
||||||
fmt.Println("login function reached")
|
|
||||||
if !a.CheckIfMyAnimeListLoggedIn() {
|
if !a.CheckIfMyAnimeListLoggedIn() {
|
||||||
fmt.Println("check logged in function failed")
|
fmt.Println("check logged in function failed")
|
||||||
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
|
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8" />
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
<title>AniTrack</title>
|
<title>AniTrack</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="./src/main.ts" type="module"></script>
|
<script src="./src/main.ts" type="module"></script>
|
||||||
<script src="./node_modules/flowbite/dist/flowbite.js"></script>
|
<script
|
||||||
</body>
|
src="./node_modules/flowbite/dist/flowbite.js"
|
||||||
|
type="module"
|
||||||
|
></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -11,7 +11,10 @@
|
|||||||
import { Button } from "flowbite-svelte";
|
import { Button } from "flowbite-svelte";
|
||||||
import type { AniListGetSingleAnime } from "../anilist/types/AniListCurrentUserWatchListType";
|
import type { AniListGetSingleAnime } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||||
import Rating from "./Rating.svelte";
|
import Rating from "./Rating.svelte";
|
||||||
import convertAniListDateToString from "../helperFunctions/convertAniListDateToString";
|
import {
|
||||||
|
convertAniListDateToString,
|
||||||
|
convertAniListDateToDate,
|
||||||
|
} from "../helperFunctions/convertAniListDateIn";
|
||||||
import AnimeTable from "./AnimeTable.svelte";
|
import AnimeTable from "./AnimeTable.svelte";
|
||||||
import type {
|
import type {
|
||||||
MALAnime,
|
MALAnime,
|
||||||
@ -25,7 +28,10 @@
|
|||||||
StatusOptions,
|
StatusOptions,
|
||||||
} from "../helperTypes/StatusTypes";
|
} from "../helperTypes/StatusTypes";
|
||||||
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
|
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
|
||||||
import convertDateStringToAniList from "../helperFunctions/convertDateStringToAniList";
|
import {
|
||||||
|
convertDateStringToAniList,
|
||||||
|
convertDateToAniList,
|
||||||
|
} from "../helperFunctions/convertDateToAniList";
|
||||||
import {
|
import {
|
||||||
AniListDeleteEntry,
|
AniListDeleteEntry,
|
||||||
AniListUpdateEntry,
|
AniListUpdateEntry,
|
||||||
@ -38,6 +44,8 @@
|
|||||||
} 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";
|
||||||
|
import Datepicker from "./Datepicker.svelte";
|
||||||
|
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
||||||
|
|
||||||
let isAniListLoggedIn: boolean;
|
let isAniListLoggedIn: boolean;
|
||||||
let isMalLoggedIn: boolean;
|
let isMalLoggedIn: boolean;
|
||||||
@ -78,10 +86,10 @@
|
|||||||
(option) =>
|
(option) =>
|
||||||
currentAniListAnime.data.MediaList.status === option.aniList,
|
currentAniListAnime.data.MediaList.status === option.aniList,
|
||||||
)[0];
|
)[0];
|
||||||
const startedAtDate = convertAniListDateToString(
|
let startedAtDate: Date | null = convertAniListDateToDate(
|
||||||
currentAniListAnime.data.MediaList.startedAt,
|
currentAniListAnime.data.MediaList.startedAt,
|
||||||
);
|
);
|
||||||
const completedAtDate = convertAniListDateToString(
|
let completedAtDate: Date | null = convertAniListDateToDate(
|
||||||
currentAniListAnime.data.MediaList.completedAt,
|
currentAniListAnime.data.MediaList.completedAt,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -103,19 +111,34 @@
|
|||||||
notes: currentAniListAnime.data.MediaList.notes,
|
notes: currentAniListAnime.data.MediaList.notes,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isMalLoggedIn)
|
if (isMalLoggedIn) {
|
||||||
|
let startDate = "";
|
||||||
|
let finishDate = "";
|
||||||
|
if (currentMalAnime.my_list_status.start_date !== "") {
|
||||||
|
const startArray = re.exec(
|
||||||
|
currentMalAnime.my_list_status.start_date,
|
||||||
|
);
|
||||||
|
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
||||||
|
}
|
||||||
|
if (currentMalAnime.my_list_status.finish_date !== "") {
|
||||||
|
const finishArray = re.exec(
|
||||||
|
currentMalAnime.my_list_status.finish_date,
|
||||||
|
);
|
||||||
|
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
||||||
|
}
|
||||||
AddAnimeServiceToTable({
|
AddAnimeServiceToTable({
|
||||||
id: `m-${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: startDate,
|
||||||
completedAt: currentMalAnime.my_list_status.finish_date,
|
completedAt: finishDate,
|
||||||
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)
|
if (isSimklLoggedIn && Object.keys(currentSimklAnime).length > 0)
|
||||||
AddAnimeServiceToTable({
|
AddAnimeServiceToTable({
|
||||||
@ -137,8 +160,8 @@
|
|||||||
rating: number;
|
rating: number;
|
||||||
episodes: number;
|
episodes: number;
|
||||||
status: StatusOption;
|
status: StatusOption;
|
||||||
startedAt: string;
|
startedAt: Date | null;
|
||||||
completedAt: string;
|
completedAt: Date | null;
|
||||||
repeat: number;
|
repeat: number;
|
||||||
notes: string;
|
notes: string;
|
||||||
} = {
|
} = {
|
||||||
@ -150,8 +173,8 @@
|
|||||||
mal: "",
|
mal: "",
|
||||||
simkl: "",
|
simkl: "",
|
||||||
},
|
},
|
||||||
startedAt: "",
|
startedAt: null,
|
||||||
completedAt: "",
|
completedAt: null,
|
||||||
repeat: 0,
|
repeat: 0,
|
||||||
notes: "",
|
notes: "",
|
||||||
};
|
};
|
||||||
@ -188,8 +211,8 @@
|
|||||||
score: submitData.rating,
|
score: submitData.rating,
|
||||||
repeat: submitData.repeat,
|
repeat: submitData.repeat,
|
||||||
notes: submitData.notes,
|
notes: submitData.notes,
|
||||||
startedAt: convertDateStringToAniList(submitData.startedAt),
|
startedAt: convertDateToAniList(startedAtDate),
|
||||||
completedAt: convertDateStringToAniList(submitData.completedAt),
|
completedAt: convertDateToAniList(completedAtDate),
|
||||||
};
|
};
|
||||||
await AniListUpdateEntry(body).then(
|
await AniListUpdateEntry(body).then(
|
||||||
(value: AniListGetSingleAnime) => {
|
(value: AniListGetSingleAnime) => {
|
||||||
@ -244,6 +267,20 @@
|
|||||||
value.my_list_status.comments = malAnimeReturn.comments;
|
value.my_list_status.comments = malAnimeReturn.comments;
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
|
let startDate = "";
|
||||||
|
let finishDate = "";
|
||||||
|
if (currentMalAnime.my_list_status.start_date !== "") {
|
||||||
|
const startArray = re.exec(
|
||||||
|
currentMalAnime.my_list_status.start_date,
|
||||||
|
);
|
||||||
|
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
||||||
|
}
|
||||||
|
if (currentMalAnime.my_list_status.finish_date !== "") {
|
||||||
|
const finishArray = re.exec(
|
||||||
|
currentMalAnime.my_list_status.finish_date,
|
||||||
|
);
|
||||||
|
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
||||||
|
}
|
||||||
AddAnimeServiceToTable({
|
AddAnimeServiceToTable({
|
||||||
id: `m-${currentMalAnime.id}`,
|
id: `m-${currentMalAnime.id}`,
|
||||||
title: currentMalAnime.title,
|
title: currentMalAnime.title,
|
||||||
@ -251,8 +288,8 @@
|
|||||||
progress:
|
progress:
|
||||||
currentMalAnime.my_list_status.num_episodes_watched,
|
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: startDate,
|
||||||
completedAt: currentMalAnime.my_list_status.finish_date,
|
completedAt: finishDate,
|
||||||
score: currentMalAnime.my_list_status.score,
|
score: currentMalAnime.my_list_status.score,
|
||||||
repeat: currentMalAnime.my_list_status
|
repeat: currentMalAnime.my_list_status
|
||||||
.num_times_rewatched,
|
.num_times_rewatched,
|
||||||
@ -483,8 +520,9 @@
|
|||||||
1)
|
1)
|
||||||
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
? '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"
|
: '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
|
bind:value={
|
||||||
.progress}
|
currentAniListAnime.data.MediaList.progress
|
||||||
|
}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@ -556,33 +594,16 @@
|
|||||||
class="text-left block mb-2 text-sm font-medium text-white"
|
class="text-left block mb-2 text-sm font-medium text-white"
|
||||||
>Date Started</label
|
>Date Started</label
|
||||||
>
|
>
|
||||||
<div class="relative max-w-sm">
|
<Datepicker
|
||||||
<div
|
bind:value={startedAtDate}
|
||||||
class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"
|
color="slate"
|
||||||
>
|
dateFormat={{
|
||||||
<svg
|
year: "numeric",
|
||||||
class="w-4 h-4 text-gray-400"
|
month: "2-digit",
|
||||||
aria-hidden="true"
|
day: "2-digit",
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
}}
|
||||||
fill="currentColor"
|
showActionButtons
|
||||||
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="startedAt"
|
|
||||||
type="date"
|
|
||||||
name="startedAt"
|
|
||||||
class="border text-sm rounded-lg
|
|
||||||
focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 bg-gray-700 border-gray-600
|
|
||||||
placeholder-gray-400 text-white"
|
|
||||||
value={startedAtDate}
|
|
||||||
placeholder="Date Started"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
@ -590,33 +611,16 @@
|
|||||||
class="text-left block mb-2 text-sm font-medium text-white"
|
class="text-left block mb-2 text-sm font-medium text-white"
|
||||||
>Date Completed</label
|
>Date Completed</label
|
||||||
>
|
>
|
||||||
<div class="relative max-w-sm">
|
<Datepicker
|
||||||
<div
|
bind:value={completedAtDate}
|
||||||
class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"
|
color="slate"
|
||||||
>
|
dateFormat={{
|
||||||
<svg
|
year: "numeric",
|
||||||
class="w-4 h-4 text-gray-400"
|
month: "2-digit",
|
||||||
aria-hidden="true"
|
day: "2-digit",
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
}}
|
||||||
fill="currentColor"
|
showActionButtons
|
||||||
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="border text-sm rounded-lg
|
|
||||||
block w-full ps-10 p-2.5 bg-gray-700 border-gray-600
|
|
||||||
placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
|
|
||||||
value={completedAtDate}
|
|
||||||
placeholder="Date Completed"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
|
481
frontend/src/helperComponents/Datepicker.svelte
Normal file
481
frontend/src/helperComponents/Datepicker.svelte
Normal file
@ -0,0 +1,481 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import { Button } from "flowbite-svelte";
|
||||||
|
|
||||||
|
export let value: Date | null = null;
|
||||||
|
export let defaultDate: Date | null = null;
|
||||||
|
export let range: boolean = false;
|
||||||
|
export let rangeFrom: Date | null = null;
|
||||||
|
export let rangeTo: Date | null = null;
|
||||||
|
export let locale: string = "default";
|
||||||
|
export let firstDayOfWeek: number = 0; // 0 = Monday, 6 = Sunday
|
||||||
|
export let dateFormat: Intl.DateTimeFormatOptions = {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
};
|
||||||
|
export let placeholder: string = "Select date";
|
||||||
|
export let disabled: boolean = false;
|
||||||
|
export let required: boolean = false;
|
||||||
|
export let inputClass: string = "";
|
||||||
|
export let color: Button["color"] = "primary";
|
||||||
|
export let inline: boolean = false;
|
||||||
|
export let autohide: boolean = true;
|
||||||
|
export let showActionButtons: boolean = false;
|
||||||
|
export let title: string = "";
|
||||||
|
|
||||||
|
// Internal state
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
let isOpen: boolean = inline;
|
||||||
|
let inputElement: HTMLInputElement;
|
||||||
|
let datepickerContainerElement: HTMLDivElement;
|
||||||
|
let currentMonth: Date = value || defaultDate || new Date();
|
||||||
|
let focusedDate: Date | null = null;
|
||||||
|
let calendarRef: HTMLDivElement;
|
||||||
|
|
||||||
|
$: daysInMonth = getDaysInMonth(currentMonth);
|
||||||
|
$: weekdays = getWeekdays();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!inline) {
|
||||||
|
document.addEventListener("click", handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("click", handleClickOutside);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Color handling functions
|
||||||
|
function getFocusRingClass(color: Button["color"]): string {
|
||||||
|
switch (color) {
|
||||||
|
case "primary":
|
||||||
|
return "focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400";
|
||||||
|
case "blue":
|
||||||
|
return "focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400";
|
||||||
|
case "red":
|
||||||
|
return "focus:ring-2 focus:ring-red-500 dark:focus:ring-red-400";
|
||||||
|
case "green":
|
||||||
|
return "focus:ring-2 focus:ring-green-500 dark:focus:ring-green-400";
|
||||||
|
case "yellow":
|
||||||
|
return "focus:ring-2 focus:ring-yellow-500 dark:focus:ring-yellow-400";
|
||||||
|
case "purple":
|
||||||
|
return "focus:ring-2 focus:ring-purple-500 dark:focus:ring-purple-400";
|
||||||
|
case "slate":
|
||||||
|
return "focus:ring-2 focus:ring-slate-500 dark:focus:ring-slate-400";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRangeBackgroundClass(color: Button["color"]): string {
|
||||||
|
switch (color) {
|
||||||
|
case "primary":
|
||||||
|
return "bg-primary-100 dark:bg-primary-900";
|
||||||
|
case "blue":
|
||||||
|
return "bg-blue-100 dark:bg-blue-900";
|
||||||
|
case "red":
|
||||||
|
return "bg-red-100 dark:bg-red-900";
|
||||||
|
case "green":
|
||||||
|
return "bg-green-100 dark:bg-green-900";
|
||||||
|
case "yellow":
|
||||||
|
return "bg-yellow-100 dark:bg-yellow-900";
|
||||||
|
case "purple":
|
||||||
|
return "bg-purple-100 dark:bg-purple-900";
|
||||||
|
case "slate":
|
||||||
|
return "bg-slate-100 dark:bg-slate-900";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDaysInMonth(date: Date): Date[] {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth();
|
||||||
|
const firstDay = new Date(year, month, 0);
|
||||||
|
const lastDay = new Date(year, month + 1, 0);
|
||||||
|
const daysArray: Date[] = [];
|
||||||
|
|
||||||
|
// Add days from previous month to fill the first week
|
||||||
|
let start = firstDay.getDay() - firstDayOfWeek;
|
||||||
|
if (start < 0) start += 7;
|
||||||
|
for (let i = 0; i < start; i++) {
|
||||||
|
daysArray.unshift(new Date(year, month, -i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add days of the current month
|
||||||
|
for (let i = 1; i <= lastDay.getDate(); i++) {
|
||||||
|
daysArray.push(new Date(year, month, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add days from next month to fill the last week
|
||||||
|
const remainingDays = 7 - (daysArray.length % 7);
|
||||||
|
if (remainingDays < 7) {
|
||||||
|
for (let i = 1; i <= remainingDays; i++) {
|
||||||
|
daysArray.push(new Date(year, month + 1, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return daysArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWeekdays(): string[] {
|
||||||
|
const weekdays = [];
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const day = new Date(2021, 5, i + firstDayOfWeek);
|
||||||
|
weekdays.push(day.toLocaleString(locale, { weekday: "short" }));
|
||||||
|
}
|
||||||
|
return weekdays;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeMonth(increment: number) {
|
||||||
|
currentMonth = new Date(
|
||||||
|
currentMonth.getFullYear(),
|
||||||
|
currentMonth.getMonth() + increment,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDaySelect(day: Date) {
|
||||||
|
if (range) {
|
||||||
|
if (!rangeFrom || (rangeFrom && rangeTo)) {
|
||||||
|
rangeFrom = day;
|
||||||
|
rangeTo = null;
|
||||||
|
} else if (day < rangeFrom) {
|
||||||
|
rangeTo = rangeFrom;
|
||||||
|
rangeFrom = day;
|
||||||
|
} else {
|
||||||
|
rangeTo = day;
|
||||||
|
}
|
||||||
|
dispatch("select", { from: rangeFrom, to: rangeTo });
|
||||||
|
} else {
|
||||||
|
value = day;
|
||||||
|
dispatch("select", value);
|
||||||
|
if (autohide && !inline) isOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInputChange() {
|
||||||
|
const date = new Date(inputElement.value);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
handleDaySelect(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickOutside(event: MouseEvent) {
|
||||||
|
if (
|
||||||
|
isOpen &&
|
||||||
|
datepickerContainerElement &&
|
||||||
|
!datepickerContainerElement.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
isOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(date: Date | null): string {
|
||||||
|
if (!date) return "";
|
||||||
|
return date.toLocaleDateString(locale, dateFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameDate(date1: Date | null, date2: Date | null): boolean {
|
||||||
|
if (!date1 || !date2) return false;
|
||||||
|
return date1.toDateString() === date2.toDateString();
|
||||||
|
}
|
||||||
|
|
||||||
|
$: isSelected = (day: Date): boolean => {
|
||||||
|
if (range) {
|
||||||
|
return isSameDate(day, rangeFrom) || isSameDate(day, rangeTo);
|
||||||
|
}
|
||||||
|
return isSameDate(day, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
function isInRange(day: Date): boolean {
|
||||||
|
if (!range || !rangeFrom || !rangeTo) return false;
|
||||||
|
return day > rangeFrom && day < rangeTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isToday(day: Date): boolean {
|
||||||
|
const today = new Date();
|
||||||
|
return day.toDateString() === today.toDateString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCalendarKeydown(event: KeyboardEvent) {
|
||||||
|
if (!isOpen) return;
|
||||||
|
|
||||||
|
if (!focusedDate) {
|
||||||
|
focusedDate = value || new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case "ArrowLeft":
|
||||||
|
focusedDate = new Date(
|
||||||
|
focusedDate.getFullYear(),
|
||||||
|
focusedDate.getMonth(),
|
||||||
|
focusedDate.getDate() - 1,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "ArrowRight":
|
||||||
|
focusedDate = new Date(
|
||||||
|
focusedDate.getFullYear(),
|
||||||
|
focusedDate.getMonth(),
|
||||||
|
focusedDate.getDate() + 1,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "ArrowUp":
|
||||||
|
focusedDate = new Date(
|
||||||
|
focusedDate.getFullYear(),
|
||||||
|
focusedDate.getMonth(),
|
||||||
|
focusedDate.getDate() - 7,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "ArrowDown":
|
||||||
|
focusedDate = new Date(
|
||||||
|
focusedDate.getFullYear(),
|
||||||
|
focusedDate.getMonth(),
|
||||||
|
focusedDate.getDate() + 7,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "Enter":
|
||||||
|
handleDaySelect(focusedDate);
|
||||||
|
break;
|
||||||
|
case "Escape":
|
||||||
|
isOpen = false;
|
||||||
|
inputElement.focus();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
if (focusedDate.getMonth() !== currentMonth.getMonth()) {
|
||||||
|
currentMonth = new Date(
|
||||||
|
focusedDate.getFullYear(),
|
||||||
|
focusedDate.getMonth(),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus the button for the focused date
|
||||||
|
setTimeout(() => {
|
||||||
|
const focusedButton = calendarRef.querySelector(
|
||||||
|
`button[aria-label="${focusedDate!.toLocaleDateString(locale, { weekday: "long", year: "numeric", month: "long", day: "numeric" })}"]`,
|
||||||
|
) as HTMLButtonElement | null;
|
||||||
|
focusedButton?.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInputKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
isOpen = !isOpen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToday() {
|
||||||
|
handleDaySelect(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClear() {
|
||||||
|
value = null;
|
||||||
|
rangeFrom = null;
|
||||||
|
rangeTo = null;
|
||||||
|
dispatch("clear");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleApply() {
|
||||||
|
dispatch("apply", range ? { from: rangeFrom, to: rangeTo } : value);
|
||||||
|
if (!inline) isOpen = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={datepickerContainerElement}
|
||||||
|
class="relative {inline ? 'inline-block' : ''}"
|
||||||
|
>
|
||||||
|
{#if !inline}
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
bind:this={inputElement}
|
||||||
|
type="text"
|
||||||
|
class="w-full px-4 py-3 text-sm border rounded-md focus:outline-none dark:bg-gray-700 dark:text-white dark:border-gray-600 {getFocusRingClass(
|
||||||
|
color,
|
||||||
|
)} {inputClass}"
|
||||||
|
{placeholder}
|
||||||
|
value={range
|
||||||
|
? `${formatDate(rangeFrom)} - ${formatDate(rangeTo)}`
|
||||||
|
: formatDate(value)}
|
||||||
|
on:focus={() => (isOpen = true)}
|
||||||
|
on:input={handleInputChange}
|
||||||
|
on:keydown={handleInputKeydown}
|
||||||
|
{disabled}
|
||||||
|
{required}
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute inset-y-0 right-0 flex items-center px-3 text-gray-500 dark:text-gray-400 focus:outline-none"
|
||||||
|
on:click={() => (isOpen = !isOpen)}
|
||||||
|
{disabled}
|
||||||
|
aria-label={isOpen ? "Close date picker" : "Open date picker"}
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if isOpen || inline}
|
||||||
|
<div
|
||||||
|
bind:this={calendarRef}
|
||||||
|
id="datepicker-dropdown"
|
||||||
|
class="
|
||||||
|
{inline ? '' : 'absolute z-10 mt-1'}
|
||||||
|
bg-white dark:bg-gray-800 rounded-md shadow-lg"
|
||||||
|
transition:fade={{ duration: 100 }}
|
||||||
|
role="dialog"
|
||||||
|
aria-label="Calendar"
|
||||||
|
>
|
||||||
|
<div class="p-4" role="application">
|
||||||
|
{#if title}
|
||||||
|
<h2 class="text-lg font-semibold mb-4 dark:text-white">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
{/if}
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<Button
|
||||||
|
on:click={() => changeMonth(-1)}
|
||||||
|
{color}
|
||||||
|
size="sm"
|
||||||
|
aria-label="Previous month"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-3 h-3 rtl:rotate-180 text-white dark:text-white"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 10"
|
||||||
|
><path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13 5H1m0 0 4 4M1 5l4-4"
|
||||||
|
></path></svg
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
<h3
|
||||||
|
class="text-lg font-semibold dark:text-white"
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
{currentMonth.toLocaleString(locale, {
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</h3>
|
||||||
|
<Button
|
||||||
|
on:click={() => changeMonth(1)}
|
||||||
|
{color}
|
||||||
|
size="sm"
|
||||||
|
aria-label="Next month"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-3 h-3 rtl:rotate-180 text-white dark:text-white"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 10"
|
||||||
|
><path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M1 5h12m0 0L9 1m4 4L9 9"
|
||||||
|
></path></svg
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-7 gap-1" role="grid">
|
||||||
|
{#each weekdays as day}
|
||||||
|
<div
|
||||||
|
class="text-center text-sm font-medium text-gray-500 dark:text-gray-400"
|
||||||
|
role="columnheader"
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{#each daysInMonth as day}
|
||||||
|
<Button
|
||||||
|
color={isSelected(day) ? color : "alternative"}
|
||||||
|
size="sm"
|
||||||
|
class="w-full h-8 {day.getMonth() !==
|
||||||
|
currentMonth.getMonth()
|
||||||
|
? 'text-gray-300 dark:text-gray-600'
|
||||||
|
: ''} {isToday(day)
|
||||||
|
? 'font-bold'
|
||||||
|
: ''} {isInRange(day)
|
||||||
|
? getRangeBackgroundClass(color)
|
||||||
|
: ''}"
|
||||||
|
on:click={() => handleDaySelect(day)}
|
||||||
|
on:keydown={handleCalendarKeydown}
|
||||||
|
aria-label={day.toLocaleDateString(locale, {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})}
|
||||||
|
aria-selected={isSelected(day)}
|
||||||
|
role="gridcell"
|
||||||
|
>
|
||||||
|
{day.getDate()}
|
||||||
|
</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if showActionButtons}
|
||||||
|
<div class="mt-4 flex justify-between">
|
||||||
|
<Button on:click={handleToday} {color} size="sm"
|
||||||
|
>Today</Button
|
||||||
|
>
|
||||||
|
<Button on:click={handleClear} color="red" size="sm"
|
||||||
|
>Clear</Button
|
||||||
|
>
|
||||||
|
<Button on:click={handleApply} {color} size="sm"
|
||||||
|
>Apply</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
[Go to docs](https://flowbite-svelte.com/)
|
||||||
|
## Props
|
||||||
|
@prop export let value: Date | null = null;
|
||||||
|
@prop export let defaultDate: Date | null = null;
|
||||||
|
@prop export let range: boolean = false;
|
||||||
|
@prop export let rangeFrom: Date | null = null;
|
||||||
|
@prop export let rangeTo: Date | null = null;
|
||||||
|
@prop export let locale: string = 'default';
|
||||||
|
@prop export let firstDayOfWeek: number = 0;
|
||||||
|
@prop export let dateFormat: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' };
|
||||||
|
@prop export let placeholder: string = 'Select date';
|
||||||
|
@prop export let disabled: boolean = false;
|
||||||
|
@prop export let required: boolean = false;
|
||||||
|
@prop export let inputClass: string = '';
|
||||||
|
@prop export let color: Button['color'] = 'primary';
|
||||||
|
@prop export let inline: boolean = false;
|
||||||
|
@prop export let autohide: boolean = true;
|
||||||
|
@prop export let showActionButtons: boolean = false;
|
||||||
|
@prop export let title: string = '';
|
||||||
|
-->
|
37
frontend/src/helperFunctions/convertAniListDateIn.ts
Normal file
37
frontend/src/helperFunctions/convertAniListDateIn.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
const convertAniListDateToString = (date: {
|
||||||
|
year?: number;
|
||||||
|
month?: number;
|
||||||
|
day?: number;
|
||||||
|
}): string => {
|
||||||
|
if (
|
||||||
|
date.year === undefined ||
|
||||||
|
(date.year === 0 && date.month === undefined) ||
|
||||||
|
(date.month === 0 && date.day === undefined) ||
|
||||||
|
date.day === 0
|
||||||
|
) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const newISODate = new Date(date.year, date.month - 1, date.day);
|
||||||
|
const newMoment = moment(newISODate);
|
||||||
|
return newMoment.format("MM-DD-YYYY");
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertAniListDateToDate = (date: {
|
||||||
|
year?: number;
|
||||||
|
month?: number;
|
||||||
|
day?: number;
|
||||||
|
}): Date | null => {
|
||||||
|
if (
|
||||||
|
date.year === undefined ||
|
||||||
|
(date.year === 0 && date.month === undefined) ||
|
||||||
|
(date.month === 0 && date.day === undefined) ||
|
||||||
|
date.day === 0
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Date(date.year, date.month - 1, date.day);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { convertAniListDateToString, convertAniListDateToDate };
|
@ -1,17 +0,0 @@
|
|||||||
import moment from "moment";
|
|
||||||
|
|
||||||
export default (date: {
|
|
||||||
year?: number,
|
|
||||||
month?: number,
|
|
||||||
day?: number,
|
|
||||||
}): string => {
|
|
||||||
if (date.year === undefined || date.year === 0
|
|
||||||
&& date.month === undefined || date.month === 0
|
|
||||||
&& date.day === undefined || date.day === 0
|
|
||||||
) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
const newISODate = new Date(date.year, date.month - 1, date.day)
|
|
||||||
const newMoment = moment(newISODate)
|
|
||||||
return newMoment.format('YYYY-MM-DD')
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
type Date = {
|
|
||||||
year: number,
|
|
||||||
month: number,
|
|
||||||
day: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (date: string): Date => {
|
|
||||||
if (date === "") {
|
|
||||||
return {
|
|
||||||
year: 0,
|
|
||||||
month: 0,
|
|
||||||
day: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/
|
|
||||||
const newDate = re.exec(date)
|
|
||||||
return {
|
|
||||||
year: Number(newDate[1]),
|
|
||||||
month: Number(newDate[2]),
|
|
||||||
day: Number(newDate[3])
|
|
||||||
}
|
|
||||||
}
|
|
39
frontend/src/helperFunctions/convertDateToAniList.ts
Normal file
39
frontend/src/helperFunctions/convertDateToAniList.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
type AnilistDate = {
|
||||||
|
year: number;
|
||||||
|
month: number;
|
||||||
|
day: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertDateStringToAniList = (date: string): AnilistDate => {
|
||||||
|
if (date === "") {
|
||||||
|
return {
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
day: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
||||||
|
const newDate = re.exec(date);
|
||||||
|
return {
|
||||||
|
year: Number(newDate[1]),
|
||||||
|
month: Number(newDate[2]),
|
||||||
|
day: Number(newDate[3]),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertDateToAniList = (date: Date | null): AnilistDate => {
|
||||||
|
if (date === null) {
|
||||||
|
return {
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
day: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
year: Number(date.getFullYear()),
|
||||||
|
month: Number(date.getMonth()) + 1,
|
||||||
|
day: Number(date.getDate()),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { convertDateStringToAniList, convertDateToAniList };
|
@ -12,6 +12,6 @@
|
|||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"productName": "AniTrack",
|
"productName": "AniTrack",
|
||||||
"productVersion": "0.1.5"
|
"productVersion": "0.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user