Gisle Aune
4 years ago
17 changed files with 319 additions and 26 deletions
-
2database/postgres/tasks.go
-
2svelte-ui/src/App.svelte
-
30svelte-ui/src/components/Menu.svelte
-
23svelte-ui/src/components/ParentEntry.svelte
-
16svelte-ui/src/components/ProjectEntry.svelte
-
21svelte-ui/src/components/ProjectProgress.svelte
-
28svelte-ui/src/components/QLList.svelte
-
77svelte-ui/src/components/QLListItem.svelte
-
57svelte-ui/src/components/QuestLog.svelte
-
8svelte-ui/src/components/RefreshSelection.svelte
-
6svelte-ui/src/pages/FrontPage.svelte
-
2svelte-ui/src/pages/GoalPage.svelte
-
2svelte-ui/src/pages/GroupPage.svelte
-
2svelte-ui/src/pages/LogsPage.svelte
-
2svelte-ui/src/pages/ProjectPage.svelte
-
33svelte-ui/src/pages/QLPage.svelte
-
32svelte-ui/src/stores/selection.ts
@ -0,0 +1,21 @@ |
|||
<script lang="ts"> |
|||
import type { TaskResult } from "../models/task"; |
|||
import Progress from "./Progress.svelte"; |
|||
|
|||
interface ProjectLike { |
|||
tasks?: TaskResult[] |
|||
} |
|||
|
|||
export let project: ProjectLike; |
|||
|
|||
let progressAmount: number; |
|||
let progressTarget: number; |
|||
|
|||
$: progressAmount = (project.tasks||[]).map(t => t.active |
|||
? Math.min(t.completedAmount, t.itemAmount) * t.item.groupWeight |
|||
: t.itemAmount * t.item.groupWeight |
|||
).reduce((n,m) => n+m, 0); |
|||
$: progressTarget = Math.max((project.tasks||[]).map(t => t.itemAmount * t.item.groupWeight).reduce((n,m) => n+m, 0), 1); |
|||
</script> |
|||
|
|||
<Progress thin green count={progressAmount} target={progressTarget} /> |
@ -0,0 +1,28 @@ |
|||
<script lang="ts"> |
|||
import type { ProjectResult } from "../models/project"; |
|||
import QlListItem from "./QLListItem.svelte"; |
|||
|
|||
export let projects: ProjectResult[]; |
|||
export let label: string = ""; |
|||
</script> |
|||
|
|||
{#if projects.length > 0} |
|||
<div class="ql-list"> |
|||
<h2>{label}</h2> |
|||
{#each projects as project (project.id)} |
|||
<QlListItem project={project} /> |
|||
{/each} |
|||
</div> |
|||
{/if} |
|||
|
|||
<style> |
|||
div.ql-list { |
|||
margin: 1em 0; |
|||
} |
|||
|
|||
h2 { |
|||
font-weight: 100; |
|||
margin: 0; |
|||
padding-bottom: 0.125em; |
|||
} |
|||
</style> |
@ -0,0 +1,77 @@ |
|||
<script lang="ts"> |
|||
import type { ProjectResult } from "../models/project"; |
|||
import selectionStore from "../stores/selection"; |
|||
import Icon from "./Icon.svelte"; |
|||
import ProjectProgress from "./ProjectProgress.svelte"; |
|||
|
|||
export let project: ProjectResult; |
|||
|
|||
let selected: boolean; |
|||
let completed: boolean; |
|||
|
|||
function handleClick() { |
|||
window.location.hash = "#" + project.id; |
|||
selectionStore.change("hash", project.id); |
|||
} |
|||
|
|||
$: selected = $selectionStore.hash === project.id; |
|||
$: completed = !project.active; |
|||
</script> |
|||
|
|||
<div class="ql-list-item" on:click={handleClick} class:selected> |
|||
<div class="icon" class:completed> |
|||
<Icon block name={project.icon} /> |
|||
</div> |
|||
<div class="header"> |
|||
<div class="name">{project.name}</div> |
|||
<ProjectProgress project={project} /> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
div.ql-list-item { |
|||
display: flex; |
|||
flex-direction: row; |
|||
-webkit-user-select: none; |
|||
-moz-user-select: none; |
|||
|
|||
color: #777; |
|||
padding: 0.2em; |
|||
padding-bottom: 0.05em; |
|||
|
|||
cursor: pointer; |
|||
} |
|||
div.ql-list-item:hover { |
|||
color: #ccc; |
|||
background-color: #191919; |
|||
} |
|||
|
|||
div.ql-list-item.selected { |
|||
background-color: #222; |
|||
} |
|||
|
|||
div.icon { |
|||
color: #444; |
|||
|
|||
padding: 0.3em 0.5ch; |
|||
padding-right: 1ch; |
|||
} |
|||
div.ql-list-item.selected div.icon { |
|||
color: #666; |
|||
} |
|||
div.icon.completed { |
|||
color: #484; |
|||
} |
|||
div.ql-list-item.selected div.icon.completed { |
|||
color: #78ff78; |
|||
} |
|||
div.ql-list-item:hover div.icon { |
|||
color: #ccc; |
|||
} |
|||
|
|||
div.header { |
|||
flex-grow: 1; |
|||
flex-shrink: 0; |
|||
padding-right: 0.5ch; |
|||
} |
|||
</style> |
@ -0,0 +1,57 @@ |
|||
<script lang="ts"> |
|||
import type { ProjectResult } from "../models/project"; |
|||
import selectionStore from "../stores/selection"; |
|||
import ProjectEntry from "./ProjectEntry.svelte"; |
|||
import QlList from "./QLList.svelte"; |
|||
|
|||
export let projects: ProjectResult[]; |
|||
|
|||
let expiringProjects: ProjectResult[]; |
|||
let activeProjects: ProjectResult[]; |
|||
let completedProjects: ProjectResult[]; |
|||
let project: ProjectResult = null; |
|||
|
|||
$: project = $selectionStore.hash.startsWith("P") ? projects.find(p => p.id === $selectionStore.hash) : null; |
|||
$: expiringProjects = projects.filter(p => p.active && p.endTime).sort((a,b) => Date.parse(a.endTime) - Date.parse(b.endTime)); |
|||
$: activeProjects = projects.filter(p => p.active && !p.endTime).sort((a,b) => a.name.localeCompare(b.name)); |
|||
$: completedProjects = projects.filter(p => !p.active).sort((a,b) => a.name.localeCompare(b.name)); |
|||
|
|||
$: { |
|||
if (project === null && projects.length > 0) { |
|||
project = expiringProjects[0] || activeProjects[0] || completedProjects[0] || null; |
|||
if (project !== null) { |
|||
selectionStore.change("hash", project.id); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<div class="quest-log"> |
|||
<div class="list"> |
|||
<QlList label="Deadlines" projects={expiringProjects} /> |
|||
<QlList label="Acitve" projects={activeProjects} /> |
|||
<QlList label="Completed" projects={completedProjects} /> |
|||
</div> |
|||
<div class="body"> |
|||
{#if project != null} |
|||
<ProjectEntry hideIcon project={project} showAllOptions /> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
div.quest-log { |
|||
display: flex; |
|||
flex-direction: row; |
|||
} |
|||
|
|||
div.list { |
|||
flex-shrink: 0; |
|||
width: 32ch; |
|||
} |
|||
|
|||
div.body { |
|||
flex-grow: 1; |
|||
margin: 1em 1ch; |
|||
} |
|||
</style> |
@ -0,0 +1,8 @@ |
|||
<script lang="ts"> |
|||
import { onMount } from "svelte"; |
|||
import selectionStore from "../stores/selection"; |
|||
|
|||
onMount(() => { |
|||
selectionStore.refresh(); |
|||
}) |
|||
</script> |
@ -0,0 +1,33 @@ |
|||
<script lang="ts"> |
|||
import Boi from "../components/Boi.svelte"; |
|||
import QuestLog from "../components/QuestLog.svelte"; |
|||
import RefreshSelection from "../components/RefreshSelection.svelte"; |
|||
import type { ModalData } from "../stores/modal"; |
|||
import projectStore from "../stores/project"; |
|||
|
|||
const mdProjectAdd: ModalData = {name: "project.add"}; |
|||
|
|||
$: { |
|||
if (($projectStore.stale || $projectStore.filter.active != null) && !$projectStore.loading) { |
|||
projectStore.load({}); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<div class="page"> |
|||
<QuestLog projects={$projectStore.projects} /> |
|||
<Boi open={mdProjectAdd}>Add Project</Boi> |
|||
</div> |
|||
<RefreshSelection /> |
|||
|
|||
|
|||
<style> |
|||
div.page { |
|||
display: block; |
|||
margin: auto; |
|||
max-width: 100%; |
|||
width: 1600px; |
|||
margin-top: 0; |
|||
box-sizing: border-box; |
|||
} |
|||
</style> |
@ -0,0 +1,32 @@ |
|||
import { writable } from "svelte/store"; |
|||
|
|||
interface SelectionData { |
|||
path: string, |
|||
hash: string, |
|||
} |
|||
|
|||
function createSelectionStore() { |
|||
const {update, set, subscribe} = writable<SelectionData>({ |
|||
path: window.location.pathname, |
|||
hash: window.location.hash.slice(1), |
|||
}); |
|||
|
|||
return { |
|||
subscribe, |
|||
|
|||
refresh() { |
|||
set({ |
|||
path: window.location.pathname, |
|||
hash: window.location.hash.slice(1), |
|||
}); |
|||
}, |
|||
|
|||
change(key: keyof(SelectionData), value: string) { |
|||
update(d => ({...d, [key]: value})); |
|||
}, |
|||
} |
|||
} |
|||
|
|||
const selectionStore = createSelectionStore(); |
|||
|
|||
export default selectionStore; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue