Browse Source

add table of contents and #links.

main
Gisle Aune 4 years ago
parent
commit
748814b43c
  1. 2
      svelte-ui/src/components/Checkbox.svelte
  2. 20
      svelte-ui/src/components/GroupEntry.svelte
  3. 17
      svelte-ui/src/components/ItemEntry.svelte
  4. 7
      svelte-ui/src/components/LogEntry.svelte
  5. 26
      svelte-ui/src/components/ProjectEntry.svelte
  6. 146
      svelte-ui/src/components/TableOfContent.svelte
  7. 23
      svelte-ui/src/components/TaskEntry.svelte
  8. 4
      svelte-ui/src/external/icons.ts
  9. 2
      svelte-ui/src/pages/FrontPage.svelte
  10. 2
      svelte-ui/src/pages/GroupPage.svelte
  11. 2
      svelte-ui/src/pages/ProjectPage.svelte

2
svelte-ui/src/components/Checkbox.svelte

@ -27,6 +27,8 @@
flex-shrink: 0; flex-shrink: 0;
margin-top: 0.5em; margin-top: 0.5em;
margin-bottom: 1em; margin-bottom: 1em;
-webkit-user-select: none;
-moz-user-select: none;
} }
div.checkbox.centered { div.checkbox.centered {
margin: auto margin: auto

20
svelte-ui/src/components/GroupEntry.svelte

@ -1,13 +1,12 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte";
import type { IconName } from "../external/icons"; import type { IconName } from "../external/icons";
import type { GroupResult } from "../models/group"; import type { GroupResult } from "../models/group";
import type { ModalData } from "../stores/modal"; import type { ModalData } from "../stores/modal";
import DaysLeft from "./DaysLeft.svelte";
import Icon from "./Icon.svelte"; import Icon from "./Icon.svelte";
import ItemEntry from "./ItemEntry.svelte";
import ItemEntry from "./ItemEntry.svelte";
import Option from "./Option.svelte"; import Option from "./Option.svelte";
import OptionRow from "./OptionRow.svelte"; import OptionRow from "./OptionRow.svelte";
import TaskEntry from "./TaskEntry.svelte";
export let group: GroupResult = null; export let group: GroupResult = null;
export let showAllOptions: boolean = false; export let showAllOptions: boolean = false;
@ -15,7 +14,14 @@ import ItemEntry from "./ItemEntry.svelte";
let iconName: IconName = "question"; let iconName: IconName = "question";
let mdItemAdd: ModalData; let mdItemAdd: ModalData;
let mdGroupEdit: ModalData; let mdGroupEdit: ModalData;
let mdGroupDelete: ModalData;
let mdGroupDelete: ModalData;
let linkHook: HTMLElement;
onMount(() => {
if (window.location.hash === "#" + group.id) {
window.scrollTo({top: linkHook.getBoundingClientRect().top});
}
});
$: iconName = group.icon as IconName; $: iconName = group.icon as IconName;
$: mdItemAdd = {name:"item.add", group}; $: mdItemAdd = {name:"item.add", group};
@ -24,6 +30,7 @@ import ItemEntry from "./ItemEntry.svelte";
</script> </script>
<div class="group"> <div class="group">
<div class="link-hook" id={group.id} bind:this={linkHook}></div>
<div class="icon"><Icon block name={iconName} /></div> <div class="icon"><Icon block name={iconName} /></div>
<div class="body"> <div class="body">
<div class="header"> <div class="header">
@ -48,6 +55,11 @@ import ItemEntry from "./ItemEntry.svelte";
</div> </div>
<style> <style>
div.link-hook {
position: relative;
top: -2em;
}
div.group { div.group {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

17
svelte-ui/src/components/ItemEntry.svelte

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { IconName } from "../external/icons";
import { onMount } from "svelte";
import type { GroupResult } from "../models/group"; import type { GroupResult } from "../models/group";
import type { default as Item } from "../models/item"; import type { default as Item } from "../models/item";
import type { ModalData } from "../stores/modal"; import type { ModalData } from "../stores/modal";
@ -11,12 +11,20 @@
let mdItemEdit: ModalData; let mdItemEdit: ModalData;
let mdItemDelete: ModalData; let mdItemDelete: ModalData;
let linkHook: HTMLElement;
onMount(() => {
if (window.location.hash === "#" + item.id) {
window.scrollTo({top: linkHook.getBoundingClientRect().top});
}
});
$: mdItemEdit = {name: "item.edit", item, group}; $: mdItemEdit = {name: "item.edit", item, group};
$: mdItemDelete = {name: "item.delete", item, group}; $: mdItemDelete = {name: "item.delete", item, group};
</script> </script>
<div class="item"> <div class="item">
<div class="link-hook" id={item.id} bind:this={linkHook}></div>
<div class="body"> <div class="body">
<div class="header"> <div class="header">
<div class="icon"> <div class="icon">
@ -36,6 +44,11 @@
</div> </div>
<style> <style>
div.link-hook {
position: relative;
top: -2em;
}
div.item { div.item {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

7
svelte-ui/src/components/LogEntry.svelte

@ -35,7 +35,9 @@
<div class="item-icon"> <div class="item-icon">
<Icon name={itemIconName} /> <Icon name={itemIconName} />
</div> </div>
<div class="item-name">{log.item.name} ({log.item.groupWeight})</div>
<div class="item-name">
<a href="/items#{log.item.groupId}">{log.item.name} ({log.item.groupWeight})</a>
</div>
</div> </div>
<OptionRow> <OptionRow>
<Option open={mdLogEdit}>Edit Log</Option> <Option open={mdLogEdit}>Edit Log</Option>
@ -107,6 +109,9 @@
margin-bottom: 0em; margin-bottom: 0em;
font-size: 0.75em; font-size: 0.75em;
} }
div.item a {
color: inherit;
}
div.item div.item-icon { div.item div.item-icon {
padding: 0.25em 0.5ch 0.25em 0; padding: 0.25em 0.5ch 0.25em 0;
} }

26
svelte-ui/src/components/ProjectEntry.svelte

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte";
import type { ProjectResult } from "../models/project"; import type { ProjectResult } from "../models/project";
import type { ModalData } from "../stores/modal"; import type { ModalData } from "../stores/modal";
import DaysLeft from "./DaysLeft.svelte"; import DaysLeft from "./DaysLeft.svelte";
@ -11,12 +12,20 @@
export let project: ProjectResult = null; export let project: ProjectResult = null;
export let showAllOptions: boolean = false; export let showAllOptions: boolean = false;
export let hideInactive: boolean = false; export let hideInactive: boolean = false;
export let linkProject: boolean = false;
let mdAddTask: ModalData; let mdAddTask: ModalData;
let mdProjectEdit: ModalData; let mdProjectEdit: ModalData;
let mdProjectDelete: ModalData; let mdProjectDelete: ModalData;
let progressAmount: number; let progressAmount: number;
let progressTarget: number; let progressTarget: number;
let linkHook: HTMLElement;
onMount(() => {
if (window.location.hash === "#" + project.id) {
window.scrollTo({top: linkHook.getBoundingClientRect().top});
}
});
$: mdAddTask = {name:"task.add", project}; $: mdAddTask = {name:"task.add", project};
$: mdProjectEdit = {name:"project.edit", project}; $: mdProjectEdit = {name:"project.edit", project};
@ -26,10 +35,17 @@
</script> </script>
<div class="project"> <div class="project">
<div class="link-hook" id={project.id} bind:this={linkHook}></div>
<div class="icon" class:inactive={!project.active}><Icon block name={project.icon} /></div> <div class="icon" class:inactive={!project.active}><Icon block name={project.icon} /></div>
<div class="body"> <div class="body">
<div class="header"> <div class="header">
<div class="name">{project.name}</div>
<div class="name">
{#if linkProject}
<a href="/projects#{project.id}">{project.name}</a>
{:else}
{project.name}
{/if}
</div>
{#if (project.endTime != null)} {#if (project.endTime != null)}
<div class="times"> <div class="times">
<DaysLeft startTime={project.createdTime} endTime={project.endTime} /> <DaysLeft startTime={project.createdTime} endTime={project.endTime} />
@ -58,6 +74,11 @@
</div> </div>
<style> <style>
div.link-hook {
position: relative;
top: -2em;
}
div.project { div.project {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -89,6 +110,9 @@
margin: auto 0; margin: auto 0;
vertical-align: middle; vertical-align: middle;
} }
div.name a {
color: inherit;
}
div.times { div.times {
margin-left: auto; margin-left: auto;
margin-right: 0.25ch; margin-right: 0.25ch;

146
svelte-ui/src/components/TableOfContent.svelte

@ -0,0 +1,146 @@
<script lang="ts" context="module">
import type { IconName } from "../external/icons";
interface ToCGroup {
icon: IconName
href: string
name: string
completed: boolean
items: ToCItem[]
}
interface ToCItem {
href: string
name: string
completed: boolean
}
</script>
<script lang="ts">
import type { GroupResult } from "../models/group";
import type { ProjectResult } from "../models/project";
import Icon from "./Icon.svelte";
export let projects: ProjectResult[] = [];
export let groups: GroupResult[] = [];
export let hideInactive: boolean = false;
let tocGroups: ToCGroup[];
let show = false;
function toggleShow() {
show = !show;
}
$: {
tocGroups = [];
for (const project of projects) {
tocGroups.push({
icon: project.icon,
name: project.name,
href: `/projects#${project.id}`,
completed: !project.active,
items: project.tasks.filter(t => !hideInactive || t.active).map(task => ({
name: task.name,
completed: !task.active,
href: `/projects#${task.id}`,
})),
})
}
for (const group of groups) {
tocGroups.push({
icon: group.icon,
name: group.name,
href: `/items#${group.id}`,
completed: false,
items: group.items.map(item => ({
name: item.name,
completed: false,
href: `/items#${item.id}`,
})),
})
}
}
</script>
<div class="toc" class:show={show}>
<div class="show-button" on:click={toggleShow}>
<div class="icon"><Icon name={show ? "chevron_down" : "chevron_right"} /></div>
<div class="text">{show ? "Hide" : "Show"} Table of Contents</div>
</div>
{#if show}
{#each tocGroups as tocGroup (tocGroup.href)}
<div class="toc-group" class:completed={tocGroup.completed}>
<a href={tocGroup.href}>
<div class="icon">
<Icon name={tocGroup.icon} />
</div>
<div class="text">{tocGroup.name}</div>
</a>
</div>
{#each tocGroup.items as tocItem (tocItem.href)}
<div class="toc-item" class:completed={tocItem.completed}>
<a href={tocItem.href}>
<div class="text">{tocItem.name}</div>
</a>
</div>
{/each}
{/each}
{/if}
</div>
<style>
div.toc {
padding: 1em;
color: #777;
margin: 0 auto;
}
div.toc.show {
padding-bottom: 2em;
}
div.toc-item {
margin-left: 1.5ch;
padding-left: 1ch;
border-left: 2px solid #222;
}
div.toc-item + div.toc-group, div.toc-group + div.toc-group {
padding-top: 1em;
}
a {
color: inherit;
display: flex;
flex-direction: row;
padding: 0.125em 0.5ch;
}
a > div.icon {
padding: 0.125em 0.5ch;
}
a > div.text {
vertical-align: middle;
}
div.show-button {
display: flex;
flex-direction: row;
-webkit-user-select: none;
-moz-user-select: none;
padding-bottom: 1em;
}
div.show-button div.icon {
margin-left: auto;
padding: 0 1ch;
}
div.show-button div.text {
margin-right: auto;
margin-top: auto;
margin-bottom: auto;
}
div.completed {
color: #484;
}
</style>

23
svelte-ui/src/components/TaskEntry.svelte

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { IconName } from "../external/icons";
import { onMount } from "svelte";
import { link } from "svelte-routing";
import type { TaskResult } from "../models/task"; import type { TaskResult } from "../models/task";
import type { ModalData } from "../stores/modal"; import type { ModalData } from "../stores/modal";
import DateSpan from "./DateSpan.svelte"; import DateSpan from "./DateSpan.svelte";
@ -15,6 +16,13 @@
let mdLogAdd: ModalData; let mdLogAdd: ModalData;
let mdTaskEdit: ModalData; let mdTaskEdit: ModalData;
let mdTaskDelete: ModalData; let mdTaskDelete: ModalData;
let linkHook: HTMLElement;
onMount(() => {
if (window.location.hash === "#" + task.id) {
window.scrollTo({top: linkHook.getBoundingClientRect().top});
}
});
function toggleShowLogs() { function toggleShowLogs() {
showLogs = !showLogs; showLogs = !showLogs;
@ -26,6 +34,7 @@
</script> </script>
<div class="task"> <div class="task">
<div class="link-hook" id={task.id} bind:this={linkHook}></div>
<div class="body"> <div class="body">
<div class="header"> <div class="header">
{#if !task.active} {#if !task.active}
@ -53,7 +62,9 @@
<div class="item-icon"> <div class="item-icon">
<Icon name={task.item.icon} /> <Icon name={task.item.icon} />
</div> </div>
<div class="item-name">{task.item.name} ({task.item.groupWeight})</div>
<div class="item-name">
<a href="/items#{task.item.groupId}">{task.item.name} ({task.item.groupWeight})</a>
</div>
</div> </div>
<OptionRow> <OptionRow>
{#if task.logs.length > 0} {#if task.logs.length > 0}
@ -80,6 +91,11 @@
</div> </div>
<style> <style>
div.link-hook {
position: relative;
top: -2em;
}
div.task { div.task {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -174,4 +190,7 @@
div.item div.item-name { div.item div.item-name {
padding: 0.125em; padding: 0.125em;
} }
div.item a {
color: inherit;
}
</style> </style>

4
svelte-ui/src/external/icons.ts

@ -70,6 +70,8 @@ import { faThList } from "@fortawesome/free-solid-svg-icons/faThList";
import { faBars } from "@fortawesome/free-solid-svg-icons/faBars"; import { faBars } from "@fortawesome/free-solid-svg-icons/faBars";
import { faMoon } from "@fortawesome/free-solid-svg-icons/faMoon"; import { faMoon } from "@fortawesome/free-solid-svg-icons/faMoon";
import { faCloudMoon } from "@fortawesome/free-solid-svg-icons/faCloudMoon"; import { faCloudMoon } from "@fortawesome/free-solid-svg-icons/faCloudMoon";
import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
const icons = { const icons = {
"question": faQuestion, "question": faQuestion,
@ -144,6 +146,8 @@ const icons = {
"bars": faBars, "bars": faBars,
"moon": faMoon, "moon": faMoon,
"cloud_moon": faCloudMoon, "cloud_moon": faCloudMoon,
"chevron_right": faChevronRight,
"chevron_down": faChevronDown,
}; };
export type IconName = keyof typeof icons; export type IconName = keyof typeof icons;

2
svelte-ui/src/pages/FrontPage.svelte

@ -80,7 +80,7 @@
<h1>Upcoming Deadlines</h1> <h1>Upcoming Deadlines</h1>
{/if} {/if}
{#each sortedProjects as project (project.id)} {#each sortedProjects as project (project.id)}
<ProjectEntry hideInactive project={project} />
<ProjectEntry linkProject={project != fakeProject} hideInactive project={project} />
{/each} {/each}
{#if fakeProject.tasks.length === 0 && !$fpProjectStore.loading && $fpProjectStore.projects.length === 0} {#if fakeProject.tasks.length === 0 && !$fpProjectStore.loading && $fpProjectStore.projects.length === 0}
<EmptyList icon="check" text="All good!" /> <EmptyList icon="check" text="All good!" />

2
svelte-ui/src/pages/GroupPage.svelte

@ -3,6 +3,7 @@
import GroupEntry from "../components/GroupEntry.svelte"; import GroupEntry from "../components/GroupEntry.svelte";
import type { ModalData } from "../stores/modal"; import type { ModalData } from "../stores/modal";
import groupStore from "../stores/group"; import groupStore from "../stores/group";
import TableOfContent from "../components/TableOfContent.svelte";
const mdGroupAdd: ModalData = {name: "group.add"}; const mdGroupAdd: ModalData = {name: "group.add"};
@ -14,6 +15,7 @@
</script> </script>
<div class="page"> <div class="page">
<TableOfContent groups={$groupStore.groups} />
{#each $groupStore.groups as group (group.id)} {#each $groupStore.groups as group (group.id)}
<GroupEntry showAllOptions group={group} /> <GroupEntry showAllOptions group={group} />
{/each} {/each}

2
svelte-ui/src/pages/ProjectPage.svelte

@ -2,6 +2,7 @@
import Boi from "../components/Boi.svelte"; import Boi from "../components/Boi.svelte";
import Checkbox from "../components/Checkbox.svelte"; import Checkbox from "../components/Checkbox.svelte";
import ProjectEntry from "../components/ProjectEntry.svelte"; import ProjectEntry from "../components/ProjectEntry.svelte";
import TableOfContent from "../components/TableOfContent.svelte";
import type { ModalData } from "../stores/modal"; import type { ModalData } from "../stores/modal";
import projectStore from "../stores/project"; import projectStore from "../stores/project";
@ -30,6 +31,7 @@
<div class="options"> <div class="options">
<Checkbox centered bind:checked={showInactive} label="Show completed tasks and projects." /> <Checkbox centered bind:checked={showInactive} label="Show completed tasks and projects." />
</div> </div>
<TableOfContent hideInactive={!showInactive} projects={$projectStore.projects} />
{#each $projectStore.projects as project (project.id)} {#each $projectStore.projects as project (project.id)}
<ProjectEntry hideInactive={!showInactive} showAllOptions project={project} /> <ProjectEntry hideInactive={!showInactive} showAllOptions project={project} />
{/each} {/each}

Loading…
Cancel
Save