mirror of
https://github.com/MadeBaruna/paimon-moe.git
synced 2024-11-29 03:32:44 +01:00
Add hoyolab reminder notification
This commit is contained in:
parent
e4715e1e03
commit
5b3c6f76b4
7 changed files with 443 additions and 271 deletions
|
@ -66,6 +66,7 @@
|
||||||
"def": "DEF"
|
"def": "DEF"
|
||||||
},
|
},
|
||||||
"wish": {
|
"wish": {
|
||||||
|
"title": "Wish Counter",
|
||||||
"autoImport": "Auto Import",
|
"autoImport": "Auto Import",
|
||||||
"helpAndSetting": "Help & Settings",
|
"helpAndSetting": "Help & Settings",
|
||||||
"wishesWorth": "Wishes Worth",
|
"wishesWorth": "Wishes Worth",
|
||||||
|
@ -476,6 +477,8 @@
|
||||||
"allowNotification": "Please allow the notification prompt that shows up!",
|
"allowNotification": "Please allow the notification prompt that shows up!",
|
||||||
"transformer": "Parametric Transformer Reminder",
|
"transformer": "Parametric Transformer Reminder",
|
||||||
"last": "Enter when you last used the Parametric Transformer",
|
"last": "Enter when you last used the Parametric Transformer",
|
||||||
|
"lastHoyolab": "Enter what time you want to be notified for the daily check-in",
|
||||||
|
"every": "Everyday at",
|
||||||
"countdown": "Enter countdown time of the Parametric Transformer (If you don't remember the exact time, you need to approximate it)",
|
"countdown": "Enter countdown time of the Parametric Transformer (If you don't remember the exact time, you need to approximate it)",
|
||||||
"useLast": "Use last used time instead",
|
"useLast": "Use last used time instead",
|
||||||
"useCountdown": "Use countdown time instead",
|
"useCountdown": "Use countdown time instead",
|
||||||
|
|
|
@ -46,7 +46,8 @@
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"title": "Bisa di Farm Hari Ini",
|
"title": "Bisa di Farm Hari Ini",
|
||||||
"detail": "Items"
|
"detail": "Items",
|
||||||
|
"sunday": "Minggu bisa farm semua item 😁"
|
||||||
},
|
},
|
||||||
"calculator": {
|
"calculator": {
|
||||||
"title": "🧮 Hitung Ascension dan Talent Book Karakter dan Senjata! Semua hasil perhitungan bisa ditambahkan ke daftar todo, dan juga akan menampilkan berapa resin yang kamu perlukan!",
|
"title": "🧮 Hitung Ascension dan Talent Book Karakter dan Senjata! Semua hasil perhitungan bisa ditambahkan ke daftar todo, dan juga akan menampilkan berapa resin yang kamu perlukan!",
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
"def": "DEF"
|
"def": "DEF"
|
||||||
},
|
},
|
||||||
"wish": {
|
"wish": {
|
||||||
|
"title": "Wish Counter",
|
||||||
"autoImport": "Import Otomatis",
|
"autoImport": "Import Otomatis",
|
||||||
"helpAndSetting": "Bantuan & Pengaturan",
|
"helpAndSetting": "Bantuan & Pengaturan",
|
||||||
"wishesWorth": "Wish Setara Dengan",
|
"wishesWorth": "Wish Setara Dengan",
|
||||||
|
@ -474,6 +476,8 @@
|
||||||
"early": "Notifikasi mungkin akan muncul lebih awal (sekitar 1-10 menit) karena limitasi dalam pengiriman notifikasi.",
|
"early": "Notifikasi mungkin akan muncul lebih awal (sekitar 1-10 menit) karena limitasi dalam pengiriman notifikasi.",
|
||||||
"transformer": "Reminder Parametric Transformer",
|
"transformer": "Reminder Parametric Transformer",
|
||||||
"last": "Masukkan kapan kamu terakhir menggunakan Parametric Transformer",
|
"last": "Masukkan kapan kamu terakhir menggunakan Parametric Transformer",
|
||||||
|
"lastHoyolab": "Masukkan jam berapa kamu ingin diingatkan untuk daily check-in nya",
|
||||||
|
"every": "Setiap hari jam",
|
||||||
"countdown": "Masukkan waktu countdown Parametric Transformer (jika kamu tidak ingat waktu pasti nya, kamu perlu mengira-ngira-kannya)",
|
"countdown": "Masukkan waktu countdown Parametric Transformer (jika kamu tidak ingat waktu pasti nya, kamu perlu mengira-ngira-kannya)",
|
||||||
"useLast": "Gunakan waktu terakhir menggunakan",
|
"useLast": "Gunakan waktu terakhir menggunakan",
|
||||||
"useCountdown": "Gunakan waktu countdown",
|
"useCountdown": "Gunakan waktu countdown",
|
||||||
|
|
|
@ -46,7 +46,8 @@
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"title": "Можно фармить сегодня",
|
"title": "Можно фармить сегодня",
|
||||||
"detail": "Предметы"
|
"detail": "Предметы",
|
||||||
|
"sunday": "В воскресенье можно фармить все предметы 😁"
|
||||||
},
|
},
|
||||||
"calculator": {
|
"calculator": {
|
||||||
"title": "🧮 Рассчитайте количество материалов и книг талантов необходимых для вознесения! Все расчеты можно добавить в список дел, и он покажет, сколько смолы вам нужно!",
|
"title": "🧮 Рассчитайте количество материалов и книг талантов необходимых для вознесения! Все расчеты можно добавить в список дел, и он покажет, сколько смолы вам нужно!",
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
"def": "DEF"
|
"def": "DEF"
|
||||||
},
|
},
|
||||||
"wish": {
|
"wish": {
|
||||||
|
"title": "Счетчик молитв",
|
||||||
"autoImport": "Авто импорт",
|
"autoImport": "Авто импорт",
|
||||||
"helpAndSetting": "Помощь и настройки",
|
"helpAndSetting": "Помощь и настройки",
|
||||||
"wishesWorth": "Стоимость молитв",
|
"wishesWorth": "Стоимость молитв",
|
||||||
|
@ -424,19 +426,35 @@
|
||||||
"collect": [
|
"collect": [
|
||||||
{
|
{
|
||||||
"title": "Посещение сайта paimon.poe, referrer, браузер, операционная система, тип устройства, страна посетителя",
|
"title": "Посещение сайта paimon.poe, referrer, браузер, операционная система, тип устройства, страна посетителя",
|
||||||
"content": ["Paimon.moe использует", "plausible.io", "(аналитика с учетом конфиденциальности) чтобы мы могли знать какие фичи и как часто используют люди, и мы могли знать какие фичи внедрить или улучшить."]
|
"content": [
|
||||||
|
"Paimon.moe использует",
|
||||||
|
"plausible.io",
|
||||||
|
"(аналитика с учетом конфиденциальности) чтобы мы могли знать какие фичи и как часто используют люди, и мы могли знать какие фичи внедрить или улучшить."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Ошибка веб-сайта",
|
"title": "Ошибка веб-сайта",
|
||||||
"content": ["Paimon.moe отправляет информацию об ошибках в ", "sentry.io", "чтобы мы могли отследить проблемы и исправить их."]
|
"content": [
|
||||||
|
"Paimon.moe отправляет информацию об ошибках в ",
|
||||||
|
"sentry.io",
|
||||||
|
"чтобы мы могли отследить проблемы и исправить их."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "История молитв",
|
"title": "История молитв",
|
||||||
"content": ["Paimon.moe сохраняет 4* и 5* счетчик гарантов и инфорацию о 5* роллах (дата, название, счетчик гаранта) если вы выбрали 'Отправить данные о молитвах для глобальной статистики' во время авто импорта или вручную ввели данные в меню Помощь и настройки. Paimon.moe собирает информацию и использует для расчета среднего выпадения гаранта в каждом баннере у пользователей. Вы можете посмотреть информацию здесь ", "глобальная статистика молитв"]
|
"content": [
|
||||||
|
"Paimon.moe сохраняет 4* и 5* счетчик гарантов и инфорацию о 5* роллах (дата, название, счетчик гаранта) если вы выбрали 'Отправить данные о молитвах для глобальной статистики' во время авто импорта или вручную ввели данные в меню Помощь и настройки. Paimon.moe собирает информацию и использует для расчета среднего выпадения гаранта в каждом баннере у пользователей. Вы можете посмотреть информацию здесь ",
|
||||||
|
"глобальная статистика молитв"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"subtitleNotCollect": "Что paimon.moe НЕ собирает",
|
"subtitleNotCollect": "Что paimon.moe НЕ собирает",
|
||||||
"notCollect": ["Paimon.moe никогда не сохраняет пароли, UID, логин, email, временные ключи и ссылки на страницу поддержки. Если вам интересно, этот проект с открытым исходным кодом и вы можете увидеть его в", "paimon-moe-api Github", "и", "paimon-moe Github"]
|
"notCollect": [
|
||||||
|
"Paimon.moe никогда не сохраняет пароли, UID, логин, email, временные ключи и ссылки на страницу поддержки. Если вам интересно, этот проект с открытым исходным кодом и вы можете увидеть его в",
|
||||||
|
"paimon-moe-api Github",
|
||||||
|
"и",
|
||||||
|
"paimon-moe Github"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"message": "Ваши локальные данные в этом браузере конфликтуют с данными, хранящимися на Google Drive!",
|
"message": "Ваши локальные данные в этом браузере конфликтуют с данными, хранящимися на Google Drive!",
|
||||||
|
@ -475,6 +493,8 @@
|
||||||
"allowNotification": "Пожалуйста, разрешите уведомления во всплывшем окне.",
|
"allowNotification": "Пожалуйста, разрешите уведомления во всплывшем окне.",
|
||||||
"transformer": "Напоминание о параметрическом преобразователе",
|
"transformer": "Напоминание о параметрическом преобразователе",
|
||||||
"last": "Напишите когда вы последний раз использовали преобразователь",
|
"last": "Напишите когда вы последний раз использовали преобразователь",
|
||||||
|
"lastHoyolab": "Укажите в какое время вы бы хотели получать уведомление о награде за ежедневный вход",
|
||||||
|
"every": "Каждый день в",
|
||||||
"countdown": "Введите сколько осталось времени до отката преобразователя (если не помните точно - введите приблизительное)",
|
"countdown": "Введите сколько осталось времени до отката преобразователя (если не помните точно - введите приблизительное)",
|
||||||
"useLast": "Использовать время последнего использования",
|
"useLast": "Использовать время последнего использования",
|
||||||
"useCountdown": "Испольовать время обратного отсчета",
|
"useCountdown": "Испольовать время обратного отсчета",
|
||||||
|
|
|
@ -1,263 +0,0 @@
|
||||||
<script>
|
|
||||||
import { stores } from '@sapper/app';
|
|
||||||
import { mdiCalendarCheck, mdiDelete, mdiLoading } from '@mdi/js';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { t, locale } from 'svelte-i18n';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import relative from 'dayjs/plugin/relativeTime';
|
|
||||||
import 'dayjs/locale/id';
|
|
||||||
import 'dayjs/locale/en';
|
|
||||||
import 'dayjs/locale/ru';
|
|
||||||
dayjs.extend(relative);
|
|
||||||
|
|
||||||
import Button from '../components/Button.svelte';
|
|
||||||
import Input from '../components/Input.svelte';
|
|
||||||
import Select from '../components/Select.svelte';
|
|
||||||
import Icon from '../components/Icon.svelte';
|
|
||||||
import { pushToast } from '../stores/toast';
|
|
||||||
|
|
||||||
import {
|
|
||||||
loading,
|
|
||||||
firebaseToken,
|
|
||||||
firstLoadNotification,
|
|
||||||
loadingFirst,
|
|
||||||
notificationAllowed,
|
|
||||||
notificationSupported,
|
|
||||||
requestPermission,
|
|
||||||
} from '../stores/firebase';
|
|
||||||
|
|
||||||
const daysOptions = [...new Array(7)].map((_, i) => ({
|
|
||||||
label: i,
|
|
||||||
value: i,
|
|
||||||
}));
|
|
||||||
const hourOptions = [...new Array(24)].map((_, i) => ({
|
|
||||||
label: i,
|
|
||||||
value: i,
|
|
||||||
}));
|
|
||||||
|
|
||||||
let useType = 'last';
|
|
||||||
let time = dayjs().format('YYYY-MM-DDTHH:mm');
|
|
||||||
let day = null;
|
|
||||||
let hour = null;
|
|
||||||
|
|
||||||
let currentReminder = null;
|
|
||||||
|
|
||||||
let loadingCurrent = false;
|
|
||||||
let loadingSave = false;
|
|
||||||
|
|
||||||
locale.subscribe((val) => {
|
|
||||||
if (currentReminder === null) return;
|
|
||||||
currentReminder = currentReminder.locale(val.substring(0, 2));
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getCurrentReminder() {
|
|
||||||
if ($firebaseToken === '') return;
|
|
||||||
|
|
||||||
console.log('get reminder');
|
|
||||||
const url = new URL(`${__paimon.env.API_HOST}/reminder`);
|
|
||||||
const query = new URLSearchParams({ token: $firebaseToken, type: 'transformer' });
|
|
||||||
url.search = query.toString();
|
|
||||||
|
|
||||||
loadingCurrent = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.time) {
|
|
||||||
currentReminder = dayjs(data.time);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingCurrent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteCurrentReminder() {
|
|
||||||
console.log('delete reminder');
|
|
||||||
const url = new URL(`${__paimon.env.API_HOST}/reminder`);
|
|
||||||
const query = new URLSearchParams({ token: $firebaseToken, type: 'transformer' });
|
|
||||||
url.search = query.toString();
|
|
||||||
|
|
||||||
loadingCurrent = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
currentReminder = null;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingCurrent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setReminder() {
|
|
||||||
loadingSave = true;
|
|
||||||
await requestPermission();
|
|
||||||
|
|
||||||
let reminderTime;
|
|
||||||
|
|
||||||
if (useType === 'last') {
|
|
||||||
reminderTime = dayjs(time).add(6, 'day').add(22, 'h').format('YYYY-MM-DD HH:mm:ssZ');
|
|
||||||
} else {
|
|
||||||
if (day === null || hour === null) {
|
|
||||||
pushToast($t('reminder.errorSelect'), 'error');
|
|
||||||
loadingSave = false;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
reminderTime = dayjs().add(day.value, 'day').add(hour.value, 'h').format('YYYY-MM-DD HH:mm:ssZ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${__paimon.env.API_HOST}/reminder`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
time: reminderTime,
|
|
||||||
type: 'transformer',
|
|
||||||
token: $firebaseToken,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
currentReminder = dayjs(data.reminder.time);
|
|
||||||
loadingSave = false;
|
|
||||||
} catch (err) {
|
|
||||||
loadingSave = false;
|
|
||||||
console.error(err);
|
|
||||||
pushToast($t('reminder.errorSaving'), 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('set reminder', useType, 'transformer', reminderTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleUseType() {
|
|
||||||
if (useType === 'last') {
|
|
||||||
useType = 'countdown';
|
|
||||||
time = dayjs().format('HH:mm');
|
|
||||||
} else {
|
|
||||||
useType = 'last';
|
|
||||||
time = dayjs().format('YYYY-MM-DDTHH:mm');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
await firstLoadNotification();
|
|
||||||
await getCurrentReminder();
|
|
||||||
});
|
|
||||||
|
|
||||||
const { page } = stores();
|
|
||||||
$: if (page) {
|
|
||||||
getCurrentReminder();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Paimon.moe</title>
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Set up a reminder notification for Parametric Transformer and Hoyolab Daily Login here"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content="Set up a reminder notification for Parametric Transformer and Hoyolab Daily Login here"
|
|
||||||
/>
|
|
||||||
</svelte:head>
|
|
||||||
<div class="lg:ml-64 pt-20 lg:pt-8">
|
|
||||||
<div class="px-4 md:px-8 flex flex-col text-white">
|
|
||||||
{#if $loadingFirst}
|
|
||||||
<Icon path={mdiLoading} spin />
|
|
||||||
{:else if !$notificationSupported}
|
|
||||||
<div class="rounded-xl border-2 border-red-600 bg-red-400 bg-opacity-75 p-4 not-supported text-black">
|
|
||||||
{$t('reminder.notSupported')}
|
|
||||||
</div>
|
|
||||||
{:else if !$notificationAllowed}
|
|
||||||
<div class="rounded-xl border-2 border-red-600 bg-red-400 bg-opacity-75 p-4 not-supported text-black">
|
|
||||||
{$t('reminder.blocked')}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 xl:grid-cols-3 max-w-screen-xl">
|
|
||||||
<div>
|
|
||||||
<div class="flex items-center mb-2 h-12">
|
|
||||||
<img src="/images/items/parametric_transformer.png" alt="parametric transformer" class="w-12 h-12 mr-2" />
|
|
||||||
<p class="font-display text-xl font-semibold">{$t('reminder.transformer')}</p>
|
|
||||||
</div>
|
|
||||||
<div class="bg-item p-4 rounded-xl text-white flex flex-col">
|
|
||||||
{#if loadingCurrent}
|
|
||||||
<div class="bg-background mb-4 p-4 rounded-xl flex">
|
|
||||||
<Icon path={mdiLoading} size={0.8} spin className="mr-2" />
|
|
||||||
{$t('reminder.checking')}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if !loadingCurrent && currentReminder !== null}
|
|
||||||
<div class="bg-background mb-4 p-4 rounded-xl relative">
|
|
||||||
<p class="text-gray-400">{$t('reminder.current')}</p>
|
|
||||||
<p class="text-gray-400">{currentReminder.format('YYYY-MM-DD HH:mm')} ({currentReminder.fromNow()})</p>
|
|
||||||
<Button size="sm" on:click={deleteCurrentReminder} className="absolute top-0 right-0 mt-1 mr-1 text-gray-400">
|
|
||||||
<Icon path={mdiDelete} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<p class="mb-1 ml-1">
|
|
||||||
{$t(useType === 'last' ? 'reminder.last' : 'reminder.countdown')}
|
|
||||||
</p>
|
|
||||||
{#if useType === 'last'}
|
|
||||||
<Input type="datetime-local" pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}" bind:value={time} />
|
|
||||||
{:else}
|
|
||||||
<div class="flex">
|
|
||||||
<Select placeholder="Day" className="mr-1 flex-1" options={daysOptions} bind:selected={day} />
|
|
||||||
<Select placeholder="Hour" className="ml-1 flex-1" options={hourOptions} bind:selected={hour} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<Button size="sm" className="my-2" on:click={toggleUseType}>
|
|
||||||
{$t(useType === 'last' ? 'reminder.useCountdown' : 'reminder.useLast')}
|
|
||||||
</Button>
|
|
||||||
<Button on:click={setReminder} disabled={loadingSave}>
|
|
||||||
{#if loadingSave}<Icon path={mdiLoading} size={0.8} spin className="mr-2" />{/if}
|
|
||||||
{$t('reminder.set')}
|
|
||||||
</Button>
|
|
||||||
{#if $loading}
|
|
||||||
<p class="text-green-400 text-center mt-2">{$t('reminder.allowNotification')}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="flex items-center mb-2 h-12">
|
|
||||||
<Icon path={mdiCalendarCheck} size={1.5} className="mr-2" />
|
|
||||||
<p class="font-display text-xl font-semibold">{$t('reminder.hoyolab')}</p>
|
|
||||||
</div>
|
|
||||||
<div class="bg-item p-4 rounded-xl text-white flex flex-col">
|
|
||||||
{$t('reminder.comingsoon')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="rounded-xl border-2 border-green-600 bg-green-400 bg-opacity-75 p-4 not-supported text-black">
|
|
||||||
{$t('reminder.early')}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="rounded-xl border-2 border-orange-600 bg-orange-400 bg-opacity-75 p-4 not-supported text-black mb-4 mt-2 hidden lg:block"
|
|
||||||
>
|
|
||||||
{$t('reminder.desktop')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@screen md {
|
|
||||||
.not-supported {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
147
src/routes/reminder/_hoyolab.svelte
Normal file
147
src/routes/reminder/_hoyolab.svelte
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<script>
|
||||||
|
import { mdiCalendarCheck, mdiDelete, mdiLoading } from '@mdi/js';
|
||||||
|
import { t, locale } from 'svelte-i18n';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import relative from 'dayjs/plugin/relativeTime';
|
||||||
|
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||||
|
import 'dayjs/locale/id';
|
||||||
|
import 'dayjs/locale/en';
|
||||||
|
import 'dayjs/locale/ru';
|
||||||
|
dayjs.extend(relative);
|
||||||
|
dayjs.extend(customParseFormat);
|
||||||
|
|
||||||
|
import Button from '../../components/Button.svelte';
|
||||||
|
import Input from '../../components/Input.svelte';
|
||||||
|
import Icon from '../../components/Icon.svelte';
|
||||||
|
import { pushToast } from '../../stores/toast';
|
||||||
|
|
||||||
|
import { loading, firebaseToken, requestPermission } from '../../stores/firebase';
|
||||||
|
|
||||||
|
let time = '15:00';
|
||||||
|
|
||||||
|
let currentReminder = null;
|
||||||
|
|
||||||
|
let loadingCurrent = false;
|
||||||
|
let loadingSave = false;
|
||||||
|
|
||||||
|
locale.subscribe((val) => {
|
||||||
|
if (currentReminder === null) return;
|
||||||
|
dayjs.locale(val.substring(0, 2));
|
||||||
|
currentReminder = currentReminder.locale(val.substring(0, 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getCurrentReminder() {
|
||||||
|
if ($firebaseToken === '') return;
|
||||||
|
|
||||||
|
console.log('get reminder');
|
||||||
|
const url = new URL(`${__paimon.env.API_HOST}/reminder`);
|
||||||
|
const query = new URLSearchParams({ token: $firebaseToken, type: 'hoyolab' });
|
||||||
|
url.search = query.toString();
|
||||||
|
|
||||||
|
loadingCurrent = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.time) {
|
||||||
|
currentReminder = dayjs(data.time);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingCurrent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteCurrentReminder() {
|
||||||
|
console.log('delete reminder');
|
||||||
|
const url = new URL(`${__paimon.env.API_HOST}/reminder`);
|
||||||
|
const query = new URLSearchParams({ token: $firebaseToken, type: 'hoyolab' });
|
||||||
|
url.search = query.toString();
|
||||||
|
|
||||||
|
loadingCurrent = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(url, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
currentReminder = null;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingCurrent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setReminder() {
|
||||||
|
loadingSave = true;
|
||||||
|
await requestPermission();
|
||||||
|
|
||||||
|
const reminderTime = dayjs(time, 'HH:mm').utc().year(2000).month(0).date(1).format('YYYY-MM-DD HH:mm:ssZ');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${__paimon.env.API_HOST}/reminder`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
time: reminderTime,
|
||||||
|
type: 'hoyolab',
|
||||||
|
token: $firebaseToken,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
currentReminder = dayjs(data.reminder.time);
|
||||||
|
loadingSave = false;
|
||||||
|
} catch (err) {
|
||||||
|
loadingSave = false;
|
||||||
|
console.error(err);
|
||||||
|
pushToast($t('reminder.errorSaving'), 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('set reminder', 'hoyolab', reminderTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: $firebaseToken, getCurrentReminder();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center mb-2 h-12">
|
||||||
|
<Icon path={mdiCalendarCheck} size={1.5} className="mr-2" />
|
||||||
|
<p class="font-display text-xl font-semibold">{$t('reminder.hoyolab')}</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-item p-4 rounded-xl text-white flex flex-col">
|
||||||
|
{#if loadingCurrent}
|
||||||
|
<div class="bg-background mb-4 p-4 rounded-xl flex">
|
||||||
|
<Icon path={mdiLoading} size={0.8} spin className="mr-2" />
|
||||||
|
{$t('reminder.checking')}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if !loadingCurrent && currentReminder !== null}
|
||||||
|
<div class="bg-background mb-4 p-4 rounded-xl relative">
|
||||||
|
<p class="text-gray-400">{$t('reminder.current')}</p>
|
||||||
|
<p class="text-gray-400">{$t('reminder.every')} {currentReminder.format('HH:mm')}</p>
|
||||||
|
<Button size="sm" on:click={deleteCurrentReminder} className="absolute top-0 right-0 mt-1 mr-1 text-gray-400">
|
||||||
|
<Icon path={mdiDelete} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<p class="mb-1 ml-1">
|
||||||
|
{$t('reminder.lastHoyolab')}
|
||||||
|
</p>
|
||||||
|
<Input type="time" bind:value={time} />
|
||||||
|
<Button className="mt-2" on:click={setReminder} disabled={loadingSave}>
|
||||||
|
{#if loadingSave}<Icon path={mdiLoading} size={0.8} spin className="mr-2" />{/if}
|
||||||
|
{$t('reminder.set')}
|
||||||
|
</Button>
|
||||||
|
{#if $loading}
|
||||||
|
<p class="text-green-400 text-center mt-2">{$t('reminder.allowNotification')}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
190
src/routes/reminder/_transformer.svelte
Normal file
190
src/routes/reminder/_transformer.svelte
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
<script>
|
||||||
|
import { mdiDelete, mdiLoading } from '@mdi/js';
|
||||||
|
import { t, locale } from 'svelte-i18n';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import relative from 'dayjs/plugin/relativeTime';
|
||||||
|
import 'dayjs/locale/id';
|
||||||
|
import 'dayjs/locale/en';
|
||||||
|
import 'dayjs/locale/ru';
|
||||||
|
dayjs.extend(relative);
|
||||||
|
|
||||||
|
import Button from '../../components/Button.svelte';
|
||||||
|
import Input from '../../components/Input.svelte';
|
||||||
|
import Select from '../../components/Select.svelte';
|
||||||
|
import Icon from '../../components/Icon.svelte';
|
||||||
|
import { pushToast } from '../../stores/toast';
|
||||||
|
|
||||||
|
import { loading, firebaseToken, requestPermission } from '../../stores/firebase';
|
||||||
|
|
||||||
|
const daysOptions = [...new Array(7)].map((_, i) => ({
|
||||||
|
label: i,
|
||||||
|
value: i,
|
||||||
|
}));
|
||||||
|
const hourOptions = [...new Array(24)].map((_, i) => ({
|
||||||
|
label: i,
|
||||||
|
value: i,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let useType = 'last';
|
||||||
|
let time = dayjs().format('YYYY-MM-DDTHH:mm');
|
||||||
|
let day = null;
|
||||||
|
let hour = null;
|
||||||
|
|
||||||
|
let currentReminder = null;
|
||||||
|
|
||||||
|
let loadingCurrent = false;
|
||||||
|
let loadingSave = false;
|
||||||
|
|
||||||
|
locale.subscribe((val) => {
|
||||||
|
if (currentReminder === null) return;
|
||||||
|
dayjs.locale(val.substring(0, 2));
|
||||||
|
currentReminder = currentReminder.locale(val.substring(0, 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getCurrentReminder() {
|
||||||
|
if ($firebaseToken === '') return;
|
||||||
|
|
||||||
|
console.log('get reminder');
|
||||||
|
const url = new URL(`${__paimon.env.API_HOST}/reminder`);
|
||||||
|
const query = new URLSearchParams({ token: $firebaseToken, type: 'transformer' });
|
||||||
|
url.search = query.toString();
|
||||||
|
|
||||||
|
loadingCurrent = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.time) {
|
||||||
|
currentReminder = dayjs(data.time);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingCurrent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteCurrentReminder() {
|
||||||
|
console.log('delete reminder');
|
||||||
|
const url = new URL(`${__paimon.env.API_HOST}/reminder`);
|
||||||
|
const query = new URLSearchParams({ token: $firebaseToken, type: 'transformer' });
|
||||||
|
url.search = query.toString();
|
||||||
|
|
||||||
|
loadingCurrent = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(url, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
currentReminder = null;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingCurrent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setReminder() {
|
||||||
|
loadingSave = true;
|
||||||
|
await requestPermission();
|
||||||
|
|
||||||
|
let reminderTime;
|
||||||
|
|
||||||
|
if (useType === 'last') {
|
||||||
|
reminderTime = dayjs(time).add(6, 'day').add(22, 'h').format('YYYY-MM-DD HH:mm:ssZ');
|
||||||
|
} else {
|
||||||
|
if (day === null || hour === null) {
|
||||||
|
pushToast($t('reminder.errorSelect'), 'error');
|
||||||
|
loadingSave = false;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
reminderTime = dayjs().add(day.value, 'day').add(hour.value, 'h').format('YYYY-MM-DD HH:mm:ssZ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${__paimon.env.API_HOST}/reminder`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
time: reminderTime,
|
||||||
|
type: 'transformer',
|
||||||
|
token: $firebaseToken,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
currentReminder = dayjs(data.reminder.time);
|
||||||
|
loadingSave = false;
|
||||||
|
} catch (err) {
|
||||||
|
loadingSave = false;
|
||||||
|
console.error(err);
|
||||||
|
pushToast($t('reminder.errorSaving'), 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('set reminder', useType, 'transformer', reminderTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleUseType() {
|
||||||
|
if (useType === 'last') {
|
||||||
|
useType = 'countdown';
|
||||||
|
time = dayjs().format('HH:mm');
|
||||||
|
} else {
|
||||||
|
useType = 'last';
|
||||||
|
time = dayjs().format('YYYY-MM-DDTHH:mm');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: $firebaseToken, getCurrentReminder();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center mb-2 h-12">
|
||||||
|
<img src="/images/items/parametric_transformer.png" alt="parametric transformer" class="w-12 h-12 mr-2" />
|
||||||
|
<p class="font-display text-xl font-semibold">{$t('reminder.transformer')}</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-item p-4 rounded-xl text-white flex flex-col">
|
||||||
|
{#if loadingCurrent}
|
||||||
|
<div class="bg-background mb-4 p-4 rounded-xl flex">
|
||||||
|
<Icon path={mdiLoading} size={0.8} spin className="mr-2" />
|
||||||
|
{$t('reminder.checking')}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if !loadingCurrent && currentReminder !== null}
|
||||||
|
<div class="bg-background mb-4 p-4 rounded-xl relative">
|
||||||
|
<p class="text-gray-400">{$t('reminder.current')}</p>
|
||||||
|
<p class="text-gray-400">{currentReminder.format('YYYY-MM-DD HH:mm')} ({currentReminder.fromNow()})</p>
|
||||||
|
<Button size="sm" on:click={deleteCurrentReminder} className="absolute top-0 right-0 mt-1 mr-1 text-gray-400">
|
||||||
|
<Icon path={mdiDelete} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<p class="mb-1 ml-1">
|
||||||
|
{$t(useType === 'last' ? 'reminder.last' : 'reminder.countdown')}
|
||||||
|
</p>
|
||||||
|
{#if useType === 'last'}
|
||||||
|
<Input type="datetime-local" pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}" bind:value={time} />
|
||||||
|
{:else}
|
||||||
|
<div class="flex">
|
||||||
|
<Select placeholder="Day" className="mr-1 flex-1" options={daysOptions} bind:selected={day} />
|
||||||
|
<Select placeholder="Hour" className="ml-1 flex-1" options={hourOptions} bind:selected={hour} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<Button size="sm" className="my-2" on:click={toggleUseType}>
|
||||||
|
{$t(useType === 'last' ? 'reminder.useCountdown' : 'reminder.useLast')}
|
||||||
|
</Button>
|
||||||
|
<Button on:click={setReminder} disabled={loadingSave}>
|
||||||
|
{#if loadingSave}<Icon path={mdiLoading} size={0.8} spin className="mr-2" />{/if}
|
||||||
|
{$t('reminder.set')}
|
||||||
|
</Button>
|
||||||
|
{#if $loading}
|
||||||
|
<p class="text-green-400 text-center mt-2">{$t('reminder.allowNotification')}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
71
src/routes/reminder/index.svelte
Normal file
71
src/routes/reminder/index.svelte
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<script>
|
||||||
|
import { mdiLoading } from '@mdi/js';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
import Icon from '../../components/Icon.svelte';
|
||||||
|
|
||||||
|
import TransformerReminder from './_transformer.svelte';
|
||||||
|
import HoyolabReminder from './_hoyolab.svelte';
|
||||||
|
|
||||||
|
import {
|
||||||
|
firstLoadNotification,
|
||||||
|
loadingFirst,
|
||||||
|
notificationAllowed,
|
||||||
|
notificationSupported,
|
||||||
|
} from '../../stores/firebase';
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await firstLoadNotification();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Paimon.moe</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Set up a reminder notification for Parametric Transformer and Hoyolab Daily Login here"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Set up a reminder notification for Parametric Transformer and Hoyolab Daily Login here"
|
||||||
|
/>
|
||||||
|
</svelte:head>
|
||||||
|
<div class="lg:ml-64 pt-20 lg:pt-8">
|
||||||
|
<div class="px-4 md:px-8 flex flex-col text-white">
|
||||||
|
{#if $loadingFirst}
|
||||||
|
<Icon path={mdiLoading} spin />
|
||||||
|
{:else if !$notificationSupported}
|
||||||
|
<div class="rounded-xl border-2 border-red-600 bg-red-400 bg-opacity-75 p-4 not-supported text-black">
|
||||||
|
{$t('reminder.notSupported')}
|
||||||
|
</div>
|
||||||
|
{:else if !$notificationAllowed}
|
||||||
|
<div class="rounded-xl border-2 border-red-600 bg-red-400 bg-opacity-75 p-4 not-supported text-black">
|
||||||
|
{$t('reminder.blocked')}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 xl:grid-cols-3 max-w-screen-xl">
|
||||||
|
<TransformerReminder />
|
||||||
|
<HoyolabReminder />
|
||||||
|
<div>
|
||||||
|
<div class="rounded-xl border-2 border-green-600 bg-green-400 bg-opacity-75 p-4 not-supported text-black">
|
||||||
|
{$t('reminder.early')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="rounded-xl border-2 border-orange-600 bg-orange-400 bg-opacity-75 p-4 not-supported text-black mb-4 mt-2 hidden lg:block"
|
||||||
|
>
|
||||||
|
{$t('reminder.desktop')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@screen md {
|
||||||
|
.not-supported {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in a new issue