mirror of
https://github.com/MadeBaruna/paimon-moe.git
synced 2024-12-22 06:25:10 +01:00
Add todo page
This commit is contained in:
parent
abe600373f
commit
a2f942c1eb
19 changed files with 1648 additions and 37 deletions
|
@ -37,6 +37,7 @@
|
|||
"rollup-plugin-terser": "^7.0.0",
|
||||
"sapper": "^0.28.0",
|
||||
"svelte": "^3.17.3",
|
||||
"svelte-masonry": "^0.0.17",
|
||||
"svelte-preprocess": "^4.5.1",
|
||||
"svelte-simple-modal": "^0.6.1",
|
||||
"tailwindcss": "^1.9.5"
|
||||
|
|
|
@ -1,10 +1,40 @@
|
|||
<script>
|
||||
export let className;
|
||||
export let className = '';
|
||||
export let disabled = false;
|
||||
export let size = 'md';
|
||||
export let color = 'blue';
|
||||
export let rounded = true;
|
||||
|
||||
let px = 4;
|
||||
let py = 2;
|
||||
let textColor = 'white';
|
||||
let borderColor = 'white';
|
||||
|
||||
$: switch (color) {
|
||||
case 'blue':
|
||||
textColor = 'white';
|
||||
borderColor = 'primary';
|
||||
break;
|
||||
case 'red':
|
||||
textColor = 'red-400';
|
||||
borderColor = 'red-400';
|
||||
break;
|
||||
}
|
||||
|
||||
$: switch (size) {
|
||||
case 'md':
|
||||
px = 4;
|
||||
py = 2;
|
||||
break;
|
||||
case 'sm':
|
||||
px = 1;
|
||||
py = 1;
|
||||
break;
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
{disabled}
|
||||
class={`text-white border-2 border-white border-opacity-25 rounded-xl px-4 py-2 transition duration-100
|
||||
hover:border-primary focus:outline-none focus:border-primary disabled:opacity-50 disabled:border-gray-600 ${className}`}
|
||||
class={`text-${textColor} border-2 border-white border-opacity-25 ${rounded ? 'rounded-xl' : 'rounded-none'} px-${px} py-${py} transition duration-100
|
||||
hover:border-${borderColor} focus:outline-none focus:border-${borderColor} disabled:opacity-50 disabled:border-gray-600 ${className}`}
|
||||
on:click><slot /></button>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
export let checked = false;
|
||||
export let disabled;
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
import Icon from './Icon.svelte';
|
||||
|
||||
export let icon = null;
|
||||
export let placeholder;
|
||||
export let placeholder = '';
|
||||
export let type = 'text';
|
||||
export let min;
|
||||
export let max;
|
||||
export let min = Math.min();
|
||||
export let max = Math.max();
|
||||
|
||||
export let value = '';
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<SidebarItem
|
||||
on:clicked={close}
|
||||
active={segment === 'todo'}
|
||||
image="/images/settings.png"
|
||||
image="/images/todos.png"
|
||||
label="Todo List"
|
||||
href="/todo" />
|
||||
<SidebarItem
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
|
||||
<a on:click={clicked} class={`w-full rounded-xl ease-in duration-150 ${active ? 'active' : ''}`} {href}>
|
||||
<div class="group w-full py-3 flex items-center px-6 cursor-pointer transition-colors">
|
||||
<img class="w-8 h-8 mr-3 opacity-75 group-hover:opacity-100 ease-in duration-150" src={image} alt={label} />
|
||||
<div class="h-8 w-8 flex justify-center mr-3 opacity-75 group-hover:opacity-100 ease-in duration-150">
|
||||
<img class="h-full" src={image} alt={label} />
|
||||
</div>
|
||||
<span
|
||||
class="font-body font-semibold text-lg leading-none text-gray-500 group-hover:text-white ease-in duration-150">{label}</span>
|
||||
</div>
|
||||
|
|
40
src/components/TodoData.svelte
Normal file
40
src/components/TodoData.svelte
Normal file
|
@ -0,0 +1,40 @@
|
|||
<script>
|
||||
import { todos, loading } from '../stores/todo';
|
||||
import { readSave, updateSave, fromRemote } from '../stores/saveManager';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let unsubscribe = null;
|
||||
let firstLoad = true;
|
||||
|
||||
$: if ($fromRemote) {
|
||||
readLocalData();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
readLocalData();
|
||||
});
|
||||
|
||||
function readLocalData() {
|
||||
loading.set(true);
|
||||
firstLoad = true;
|
||||
|
||||
if (unsubscribe) unsubscribe();
|
||||
|
||||
console.log('todo read local');
|
||||
const data = readSave('todos');
|
||||
if (data !== null) {
|
||||
const todoList = JSON.parse(data);
|
||||
todos.set(todoList);
|
||||
}
|
||||
|
||||
unsubscribe = todos.subscribe((val) => {
|
||||
if (firstLoad) return;
|
||||
|
||||
console.log('todos changed', val);
|
||||
updateSave('todos', JSON.stringify(val));
|
||||
});
|
||||
|
||||
firstLoad = false;
|
||||
loading.set(false);
|
||||
}
|
||||
</script>
|
36
src/components/TodoDeleteModal.svelte
Normal file
36
src/components/TodoDeleteModal.svelte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script>
|
||||
import Button from './Button.svelte';
|
||||
|
||||
export let todo;
|
||||
export let deleteTodo;
|
||||
export let cancel;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p class="text-white font-bold mb-4 text-lg">Delete this todo?</p>
|
||||
<div class="flex items-center mb-4 text-white">
|
||||
{#if todo.type === 'weapon'}
|
||||
<img
|
||||
class="h-8 inline-block mr-2"
|
||||
src={`/images/weapons/${todo.weapon ? todo.weapon.id : 'any_weapon_1'}.png`}
|
||||
alt={todo.weapon ? todo.weapon.name : `Weapon Level ${todo.level.from}-${todo.level.to}`} />
|
||||
<div class="flex-1">
|
||||
<p class="font-bold">{todo.weapon ? todo.weapon.name : 'Weapon'}</p>
|
||||
<p class="text-gray-500">Level {`${todo.level.from}-${todo.level.to}`}</p>
|
||||
</div>
|
||||
{:else if todo.type === 'character'}
|
||||
<img
|
||||
class="h-8 inline-block mr-2"
|
||||
src={`/images/characters/${todo.character ? todo.character.id : 'characters'}.png`}
|
||||
alt={todo.character ? todo.character.name : `Character Level ${todo.level.from}-${todo.level.to}`} />
|
||||
<div class="flex-1">
|
||||
<p class="font-bold">{todo.character ? todo.character.name : 'Character'}</p>
|
||||
<p class="text-gray-500">Level {`${todo.level.from}-${todo.level.to}`}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button on:click={cancel}>Cancel</Button>
|
||||
<Button on:click={deleteTodo} color="red">Delete</Button>
|
||||
</div>
|
||||
</div>
|
|
@ -358,4 +358,8 @@ export const itemList = {
|
|||
any_weapon_1: { id: 'any_weapon_1', name: '1 Star Weapon' },
|
||||
any_weapon_2: { id: 'any_weapon_2', name: '2 Star Weapon' },
|
||||
any_weapon_3: { id: 'any_weapon_3', name: '3 Star Weapon' },
|
||||
mora: { id: 'mora', name: 'Mora' },
|
||||
heros_wit: { id: 'heros_wit', name: "Hero's Wit" },
|
||||
adventurers_experience: { id: 'adventurers_experience', name: "Adventurer's Experience" },
|
||||
wanderes_advice: { id: 'wanderes_advice', name: "Wanderer's Advice" },
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
import { showSidebar } from '../stores/sidebar';
|
||||
import { checkLocalSave } from '../stores/saveManager';
|
||||
import TodoData from '../components/TodoData.svelte';
|
||||
|
||||
export let segment;
|
||||
|
||||
|
@ -31,6 +32,7 @@
|
|||
{/if}
|
||||
<Modal>
|
||||
<DataSync>
|
||||
<TodoData />
|
||||
<main style="flex: 1 0 auto;">
|
||||
<slot />
|
||||
</main>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
import { mdiClose, mdiInformationOutline } from '@mdi/js';
|
||||
import { mdiCheckCircleOutline, mdiClose, mdiInformationOutline } from '@mdi/js';
|
||||
|
||||
import Input from '../../components/Input.svelte';
|
||||
import AscensionSelector from '../../components/AscensionSelector.svelte';
|
||||
|
@ -11,14 +11,23 @@
|
|||
import Icon from '../../components/Icon.svelte';
|
||||
|
||||
import { characterExp } from '../../data/characterExp';
|
||||
import { addTodo } from '../../stores/todo';
|
||||
|
||||
let resources = [
|
||||
{ selected: true, disabled: false, image: '/images/items/heros_wit.png', label: "Hero's Wit", value: '20000' },
|
||||
{
|
||||
selected: true,
|
||||
disabled: false,
|
||||
image: '/images/items/heros_wit.png',
|
||||
label: "Hero's Wit",
|
||||
id: 'heros_wit',
|
||||
value: '20000',
|
||||
},
|
||||
{
|
||||
selected: true,
|
||||
disabled: false,
|
||||
image: '/images/items/adventurers_experience.png',
|
||||
label: "Adventurer's Experience",
|
||||
id: 'adventurers_experience',
|
||||
value: '5000',
|
||||
},
|
||||
{
|
||||
|
@ -26,10 +35,13 @@
|
|||
disabled: false,
|
||||
image: '/images/items/wanderes_advice.png',
|
||||
label: "Wanderer's Advice",
|
||||
id: 'wanderes_advice',
|
||||
value: '1000',
|
||||
},
|
||||
];
|
||||
|
||||
let addedToTodo = false;
|
||||
|
||||
let withAscension = true;
|
||||
|
||||
let selectedCharacter = null;
|
||||
|
@ -243,6 +255,40 @@
|
|||
|
||||
changed = false;
|
||||
}
|
||||
|
||||
function addToTodo() {
|
||||
const levelRes = usedResource.reduce((prev, item, i) => {
|
||||
if (currentMax.usage[i] > 0) {
|
||||
prev[item.id] = currentMax.usage[i];
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
const ascensionRes = Object.keys(ascensionResouce).reduce((prev, item) => {
|
||||
if (ascensionResouce[item].amount > 0) {
|
||||
prev[item] = ascensionResouce[item].amount;
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
addTodo({
|
||||
type: 'character',
|
||||
character: withAscension ? selectedCharacter : null,
|
||||
level: { from: currentLevel, to: intendedLevel },
|
||||
resources: {
|
||||
mora: moraNeeded,
|
||||
...levelRes,
|
||||
...ascensionRes,
|
||||
},
|
||||
});
|
||||
|
||||
addedToTodo = true;
|
||||
setTimeout(() => {
|
||||
addedToTodo = false;
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="bg-item rounded-xl p-4">
|
||||
|
@ -388,6 +434,14 @@
|
|||
</tr>
|
||||
{/if}
|
||||
</table>
|
||||
<Button className="mt-2 w-full" on:click={addedToTodo ? () => {} : addToTodo}>
|
||||
{#if addedToTodo}
|
||||
<span class="text-green-400" in:fade={{ duration: 100 }}>
|
||||
<Icon path={mdiCheckCircleOutline} size={0.8} />
|
||||
Added to Todo List
|
||||
</span>
|
||||
{:else}<span in:fade={{ duration: 100 }}>Add to Todo List </span>{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -307,7 +307,7 @@
|
|||
|
||||
addTodo({
|
||||
type: 'weapon',
|
||||
weapon: selectedWeapon,
|
||||
weapon: withAscension ? selectedWeapon : null,
|
||||
level: { from: currentLevel, to: intendedLevel },
|
||||
resources: {
|
||||
mora: moraNeeded,
|
||||
|
@ -473,7 +473,7 @@
|
|||
</tr>
|
||||
{/if}
|
||||
</table>
|
||||
<Button className="mt-2 w-full" on:click={addToTodo}>
|
||||
<Button className="mt-2 w-full" on:click={addedToTodo ? () => {} : addToTodo}>
|
||||
{#if addedToTodo}
|
||||
<span class="text-green-400" in:fade={{ duration: 100 }}>
|
||||
<Icon path={mdiCheckCircleOutline} size={0.8} />
|
||||
|
|
|
@ -1,6 +1,209 @@
|
|||
<script>
|
||||
import { getContext, tick } from 'svelte';
|
||||
import { mdiChevronLeft, mdiChevronRight, mdiClose, mdiLoading } from '@mdi/js';
|
||||
import { todos, loading } from '../stores/todo';
|
||||
import { itemList } from '../data/itemList';
|
||||
import Masonry from 'svelte-masonry/Masonry.svelte';
|
||||
import Icon from '../components/Icon.svelte';
|
||||
import Button from '../components/Button.svelte';
|
||||
import TodoDeleteModal from '../components/TodoDeleteModal.svelte';
|
||||
|
||||
const { open: openModal, close: closeModal } = getContext('simple-modal');
|
||||
|
||||
let refreshLayout;
|
||||
let numberFormat = Intl.NumberFormat();
|
||||
let adding = false;
|
||||
|
||||
async function reorder(index, pos) {
|
||||
if ((index === 0 && pos === -1) || (index === $todos.length - 1 && pos === 1)) return;
|
||||
|
||||
todos.update((val) => {
|
||||
val.splice(index + pos, 0, val.splice(index, 1)[0]);
|
||||
return val;
|
||||
});
|
||||
|
||||
await tick();
|
||||
refreshLayout();
|
||||
}
|
||||
|
||||
async function deleteTodo(index) {
|
||||
todos.update((val) => {
|
||||
val.splice(index, 1);
|
||||
return val;
|
||||
});
|
||||
|
||||
await tick();
|
||||
refreshLayout();
|
||||
closeModal();
|
||||
}
|
||||
|
||||
async function askDeleteTodo(index) {
|
||||
if (index < 0 && index > $todos.length) return;
|
||||
|
||||
openModal(
|
||||
TodoDeleteModal,
|
||||
{
|
||||
todo: $todos[index],
|
||||
deleteTodo: () => deleteTodo(index),
|
||||
cancel: closeModal,
|
||||
},
|
||||
{
|
||||
closeButton: false,
|
||||
styleWindow: { background: '#25294A', width: '400px' },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function decrease(key, val) {
|
||||
todos.update((n) => {
|
||||
let i = 0;
|
||||
let leftover = val;
|
||||
for (const current of n) {
|
||||
const remaining = current.resources[key];
|
||||
if (remaining !== undefined && remaining > 0) {
|
||||
const reducedBy = Math.min(val, leftover, remaining);
|
||||
|
||||
n[i].resources[key] -= reducedBy;
|
||||
leftover -= reducedBy;
|
||||
|
||||
if (leftover === 0) break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return n;
|
||||
});
|
||||
}
|
||||
|
||||
$: summary = $todos.reduce((prev, current) => {
|
||||
for (const [id, amount] of Object.entries(current.resources)) {
|
||||
if (prev[id] === undefined) {
|
||||
prev[id] = 0;
|
||||
}
|
||||
|
||||
prev[id] += amount;
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Todo List - Paimon.moe</title>
|
||||
</svelte:head>
|
||||
<div class="lg:ml-64 pt-20 px-8 lg:pt-8">
|
||||
<h1 class="font-display font-black text-5xl text-white">Todo List</h1>
|
||||
<Masonry stretchFirst={true} bind:refreshLayout>
|
||||
<h1 class="font-display font-black text-3xl lg:text-left lg:text-5xl text-white">Todo List</h1>
|
||||
<div class="bg-item rounded-xl p-4 text-white">
|
||||
{#if $loading}
|
||||
<Icon path={mdiLoading} color="white" spin />
|
||||
{:else if $todos.length > 0}
|
||||
<p class="font-bold text-xl mb-4">Summary</p>
|
||||
{:else}
|
||||
<p class="font-bold text-xl">Nothing to do yet 😀<br />Add some here or from the Calculator!</p>
|
||||
{/if}
|
||||
<table class="w-full">
|
||||
{#each Object.entries(summary) as [id, amount], i}
|
||||
<tr>
|
||||
<td class="text-right border-b border-gray-700 py-1">
|
||||
<span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} mr-2 whitespace-no-wrap`}>
|
||||
{numberFormat.format(amount)}
|
||||
<Icon size={0.5} path={mdiClose} /></span>
|
||||
</td>
|
||||
<td class="border-b border-gray-700 py-1">
|
||||
<span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} block mb-1`}>
|
||||
<span class="w-6 inline-block">
|
||||
<img class="h-6 inline-block mr-1" src={`/images/items/${id}.png`} alt={itemList[id].name} />
|
||||
</span>
|
||||
{itemList[id].name}
|
||||
</span>
|
||||
{#if id === 'mora'}
|
||||
<Button size="sm" disabled={amount === 0 && !adding} on:click={() => decrease(id, 1000)}>
|
||||
{adding ? '+' : '-'}1000
|
||||
</Button>
|
||||
<Button size="sm" disabled={amount === 0 && !adding} on:click={() => decrease(id, 10000)}>
|
||||
{adding ? '+' : '-'}10000
|
||||
</Button>
|
||||
<Button size="sm" disabled={amount === 0 && !adding} on:click={() => decrease(id, 50000)}>
|
||||
{adding ? '+' : '-'}50000
|
||||
</Button>
|
||||
{:else}
|
||||
<Button size="sm" disabled={amount === 0 && !adding} className="w-10" on:click={() => decrease(id, 1)}>
|
||||
{adding ? '+' : '-'}1
|
||||
</Button>
|
||||
<Button size="sm" disabled={amount === 0 && !adding} className="w-10" on:click={() => decrease(id, 5)}>
|
||||
{adding ? '+' : '-'}5
|
||||
</Button>
|
||||
<Button size="sm" disabled={amount === 0 && !adding} className="w-10" on:click={() => decrease(id, 10)}>
|
||||
{adding ? '+' : '-'}10
|
||||
</Button>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</div>
|
||||
{#each $todos as todo, i}
|
||||
<div class="bg-item rounded-xl p-4 text-white">
|
||||
<div class="flex items-center mb-2">
|
||||
{#if todo.type === 'weapon'}
|
||||
<img
|
||||
class="h-8 inline-block mr-2"
|
||||
src={`/images/weapons/${todo.weapon ? todo.weapon.id : 'any_weapon_1'}.png`}
|
||||
alt={todo.weapon ? todo.weapon.name : `Weapon Level ${todo.level.from}-${todo.level.to}`} />
|
||||
<div class="flex-1">
|
||||
<p class="font-bold">{todo.weapon ? todo.weapon.name : 'Weapon'}</p>
|
||||
<p class="text-gray-500">Level {`${todo.level.from}-${todo.level.to}`}</p>
|
||||
</div>
|
||||
{:else if todo.type === 'character'}
|
||||
<img
|
||||
class="h-8 inline-block mr-2"
|
||||
src={`/images/characters/${todo.character ? todo.character.id : 'characters'}.png`}
|
||||
alt={todo.character ? todo.character.name : `Character Level ${todo.level.from}-${todo.level.to}`} />
|
||||
<div class="flex-1">
|
||||
<p class="font-bold">{todo.character ? todo.character.name : 'Character'}</p>
|
||||
<p class="text-gray-500">Level {`${todo.level.from}-${todo.level.to}`}</p>
|
||||
</div>
|
||||
{/if}
|
||||
<Button disabled={i === 0} on:click={() => reorder(i, -1)} rounded={false} size="sm" className="rounded-l-xl">
|
||||
<Icon path={mdiChevronLeft} color="white" />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={i === $todos.length - 1}
|
||||
on:click={() => reorder(i, 1)}
|
||||
rounded={false}
|
||||
size="sm"
|
||||
className="rounded-r-xl">
|
||||
<Icon path={mdiChevronRight} color="white" />
|
||||
</Button>
|
||||
</div>
|
||||
<table class="w-full">
|
||||
{#each Object.entries(todo.resources) as [id, amount]}
|
||||
<tr>
|
||||
<td class="text-right border-b border-gray-700 py-1">
|
||||
<span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} mr-2 whitespace-no-wrap`}>
|
||||
{numberFormat.format(amount)}
|
||||
<Icon size={0.5} path={mdiClose} /></span>
|
||||
</td>
|
||||
<td class="border-b border-gray-700 py-1">
|
||||
<span class={amount === 0 ? 'line-through text-gray-600' : 'text-white'}>
|
||||
<span class="w-6 inline-block">
|
||||
<img class="h-6 inline-block mr-1" src={`/images/items/${id}.png`} alt={itemList[id].name} />
|
||||
</span>
|
||||
{itemList[id].name}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
<div class="flex mt-2 items-end">
|
||||
<p class="flex-1 text-gray-400"># {i + 1}</p>
|
||||
<Button on:click={() => askDeleteTodo(i)} size="sm" className="px-2">
|
||||
<Icon path={mdiClose} color="white" size={0.8} />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</Masonry>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
export const loading = writable(true);
|
||||
export const todos = writable([]);
|
||||
|
||||
export function addTodo(data) {
|
||||
|
|
BIN
static/images/characters/characters.png
Normal file
BIN
static/images/characters/characters.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
static/images/items/mora.png
Normal file
BIN
static/images/items/mora.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
BIN
static/images/todos.png
Normal file
BIN
static/images/todos.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 942 B |
BIN
static/images/weapons/any_weapon_1.png
Normal file
BIN
static/images/weapons/any_weapon_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
Loading…
Reference in a new issue