Compare commits
No commits in common. "main" and "0.1.5" have entirely different histories.
@ -71,6 +71,7 @@ func (v *CodeVerifier) CodeChallengeS256() string {
|
||||
}
|
||||
|
||||
func (a *App) CheckIfMyAnimeListLoggedIn() bool {
|
||||
fmt.Println("check function reached")
|
||||
if (MyAnimeListJWT{} == myAnimeListJwt) {
|
||||
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
|
||||
expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn")
|
||||
@ -95,6 +96,7 @@ func (a *App) CheckIfMyAnimeListLoggedIn() bool {
|
||||
}
|
||||
|
||||
func (a *App) MyAnimeListLogin() {
|
||||
fmt.Println("login function reached")
|
||||
if !a.CheckIfMyAnimeListLoggedIn() {
|
||||
fmt.Println("check logged in function failed")
|
||||
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
|
||||
|
@ -1,16 +1,13 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>AniTrack</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./src/main.ts" type="module"></script>
|
||||
<script
|
||||
src="./node_modules/flowbite/dist/flowbite.js"
|
||||
type="module"
|
||||
></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>AniTrack</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./src/main.ts" type="module"></script>
|
||||
<script src="./node_modules/flowbite/dist/flowbite.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -11,10 +11,7 @@
|
||||
import { Button } from "flowbite-svelte";
|
||||
import type { AniListGetSingleAnime } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||
import Rating from "./Rating.svelte";
|
||||
import {
|
||||
convertAniListDateToString,
|
||||
convertAniListDateToDate,
|
||||
} from "../helperFunctions/convertAniListDateIn";
|
||||
import convertAniListDateToString from "../helperFunctions/convertAniListDateToString";
|
||||
import AnimeTable from "./AnimeTable.svelte";
|
||||
import type {
|
||||
MALAnime,
|
||||
@ -28,10 +25,7 @@
|
||||
StatusOptions,
|
||||
} from "../helperTypes/StatusTypes";
|
||||
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
|
||||
import {
|
||||
convertDateStringToAniList,
|
||||
convertDateToAniList,
|
||||
} from "../helperFunctions/convertDateToAniList";
|
||||
import convertDateStringToAniList from "../helperFunctions/convertDateStringToAniList";
|
||||
import {
|
||||
AniListDeleteEntry,
|
||||
AniListUpdateEntry,
|
||||
@ -44,8 +38,6 @@
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.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 isMalLoggedIn: boolean;
|
||||
@ -86,10 +78,10 @@
|
||||
(option) =>
|
||||
currentAniListAnime.data.MediaList.status === option.aniList,
|
||||
)[0];
|
||||
let startedAtDate: Date | null = convertAniListDateToDate(
|
||||
const startedAtDate = convertAniListDateToString(
|
||||
currentAniListAnime.data.MediaList.startedAt,
|
||||
);
|
||||
let completedAtDate: Date | null = convertAniListDateToDate(
|
||||
const completedAtDate = convertAniListDateToString(
|
||||
currentAniListAnime.data.MediaList.completedAt,
|
||||
);
|
||||
|
||||
@ -111,34 +103,19 @@
|
||||
notes: currentAniListAnime.data.MediaList.notes,
|
||||
});
|
||||
|
||||
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]}`;
|
||||
}
|
||||
if (isMalLoggedIn)
|
||||
AddAnimeServiceToTable({
|
||||
id: `m-${currentMalAnime.id}`,
|
||||
title: currentMalAnime.title,
|
||||
service: "MyAnimeList",
|
||||
progress: currentMalAnime.my_list_status.num_episodes_watched,
|
||||
status: currentMalAnime.my_list_status.status,
|
||||
startedAt: startDate,
|
||||
completedAt: finishDate,
|
||||
startedAt: currentMalAnime.my_list_status.start_date,
|
||||
completedAt: currentMalAnime.my_list_status.finish_date,
|
||||
score: currentMalAnime.my_list_status.score,
|
||||
repeat: currentMalAnime.my_list_status.num_times_rewatched,
|
||||
notes: currentMalAnime.my_list_status.comments,
|
||||
});
|
||||
}
|
||||
|
||||
if (isSimklLoggedIn && Object.keys(currentSimklAnime).length > 0)
|
||||
AddAnimeServiceToTable({
|
||||
@ -160,8 +137,8 @@
|
||||
rating: number;
|
||||
episodes: number;
|
||||
status: StatusOption;
|
||||
startedAt: Date | null;
|
||||
completedAt: Date | null;
|
||||
startedAt: string;
|
||||
completedAt: string;
|
||||
repeat: number;
|
||||
notes: string;
|
||||
} = {
|
||||
@ -173,8 +150,8 @@
|
||||
mal: "",
|
||||
simkl: "",
|
||||
},
|
||||
startedAt: null,
|
||||
completedAt: null,
|
||||
startedAt: "",
|
||||
completedAt: "",
|
||||
repeat: 0,
|
||||
notes: "",
|
||||
};
|
||||
@ -211,8 +188,8 @@
|
||||
score: submitData.rating,
|
||||
repeat: submitData.repeat,
|
||||
notes: submitData.notes,
|
||||
startedAt: convertDateToAniList(startedAtDate),
|
||||
completedAt: convertDateToAniList(completedAtDate),
|
||||
startedAt: convertDateStringToAniList(submitData.startedAt),
|
||||
completedAt: convertDateStringToAniList(submitData.completedAt),
|
||||
};
|
||||
await AniListUpdateEntry(body).then(
|
||||
(value: AniListGetSingleAnime) => {
|
||||
@ -267,20 +244,6 @@
|
||||
value.my_list_status.comments = malAnimeReturn.comments;
|
||||
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({
|
||||
id: `m-${currentMalAnime.id}`,
|
||||
title: currentMalAnime.title,
|
||||
@ -288,8 +251,8 @@
|
||||
progress:
|
||||
currentMalAnime.my_list_status.num_episodes_watched,
|
||||
status: currentMalAnime.my_list_status.status,
|
||||
startedAt: startDate,
|
||||
completedAt: finishDate,
|
||||
startedAt: currentMalAnime.my_list_status.start_date,
|
||||
completedAt: currentMalAnime.my_list_status.finish_date,
|
||||
score: currentMalAnime.my_list_status.score,
|
||||
repeat: currentMalAnime.my_list_status
|
||||
.num_times_rewatched,
|
||||
@ -520,9 +483,8 @@
|
||||
1)
|
||||
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'bg-gray-700 hover:bg-gray-600 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500'} w-24"
|
||||
bind:value={
|
||||
currentAniListAnime.data.MediaList.progress
|
||||
}
|
||||
bind:value={currentAniListAnime.data.MediaList
|
||||
.progress}
|
||||
required
|
||||
/>
|
||||
<button
|
||||
@ -594,16 +556,33 @@
|
||||
class="text-left block mb-2 text-sm font-medium text-white"
|
||||
>Date Started</label
|
||||
>
|
||||
<Datepicker
|
||||
bind:value={startedAtDate}
|
||||
color="slate"
|
||||
dateFormat={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
}}
|
||||
showActionButtons
|
||||
/>
|
||||
<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-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="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>
|
||||
<label
|
||||
@ -611,16 +590,33 @@
|
||||
class="text-left block mb-2 text-sm font-medium text-white"
|
||||
>Date Completed</label
|
||||
>
|
||||
<Datepicker
|
||||
bind:value={completedAtDate}
|
||||
color="slate"
|
||||
dateFormat={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
}}
|
||||
showActionButtons
|
||||
/>
|
||||
<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-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="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>
|
||||
<label
|
||||
@ -663,53 +659,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-4 rounded-lg shadow max-w-4-4 bg-gray-800">
|
||||
<div id="external-data">
|
||||
<div
|
||||
class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end"
|
||||
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"
|
||||
>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
id="sync-button"
|
||||
class="text-white {$submitSuccess
|
||||
? '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'
|
||||
: '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"
|
||||
type="submit"
|
||||
>
|
||||
<svg
|
||||
id="submit-loader"
|
||||
aria-hidden="true"
|
||||
role="status"
|
||||
class="{isSubmitting
|
||||
? 'inline'
|
||||
: 'hidden'} w-4 h-4 me-3 text-white animate-spin"
|
||||
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>
|
||||
Sync Changes
|
||||
</Button>
|
||||
<Button
|
||||
class="text-white bg-gray-800 border border-gray-600 focus:outline-none hover:bg-gray-700 focus:ring-4
|
||||
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 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"
|
||||
on:click={async () => {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
return push("/");
|
||||
}}
|
||||
>
|
||||
Go Home
|
||||
</Button>
|
||||
<h2 class="text-left mb-1 text-base font-semibold text-white">
|
||||
AniList
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimeTable />
|
||||
|
||||
<div class="flex rounded-lg shadow max-w-4-4 bg-gray-800">
|
||||
|
@ -1,481 +0,0 @@
|
||||
<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 = '';
|
||||
-->
|
@ -1,37 +0,0 @@
|
||||
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 };
|
17
frontend/src/helperFunctions/convertAniListDateToString.ts
Normal file
17
frontend/src/helperFunctions/convertAniListDateToString.ts
Normal file
@ -0,0 +1,17 @@
|
||||
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')
|
||||
}
|
22
frontend/src/helperFunctions/convertDateStringToAniList.ts
Normal file
22
frontend/src/helperFunctions/convertDateStringToAniList.ts
Normal file
@ -0,0 +1,22 @@
|
||||
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])
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
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": {
|
||||
"productName": "AniTrack",
|
||||
"productVersion": "0.1.6"
|
||||
"productVersion": "0.1.5"
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user