add project groups to frontend.
continuous-integration/drone/push Build is passing

This commit is contained in:
2021-07-18 22:11:16 +02:00
parent ea01af1091
commit 83eb9862fc
29 changed files with 524 additions and 64 deletions
+4 -2
View File
@@ -8,6 +8,7 @@
interface ActualParent {
id: string
groupId: string
name: string
}
@@ -30,6 +31,7 @@
interface EntryCommonSub {
id?: string
groupId?: string
name: string
icon: IconName
project?: EntryCommonSub
@@ -64,12 +66,12 @@
<Icon block name="link" />
</div>
<div class="actual-parent">
<a href={`/questlog#${actualParent.id}`}>{actualParent.name}</a>
<a href={`/questlog/${actualParent.groupId}/${actualParent.id}`}>{actualParent.name}</a>
</div>
{/if}
{:else if (entry.task && entry.task.project != null)}
<div class="actual-parent">
<a href={`/questlog#${entry.task.project.id}`}>{entry.task.project.name}</a>
<a href={`/questlog/${entry.task.project.groupId}/${entry.task.project.id}`}>{entry.task.project.name}</a>
</div>
{/if}
<div class="name">{displayName}</div>
@@ -0,0 +1,17 @@
<script lang="ts">
import StatusColor from "./StatusColor.svelte";
export let selected: boolean = false;
export let status: string = "to do";
export let number: number | null | undefined = void(0);
let entry: {statusTag: string}
$: entry = {statusTag: status};
</script>
{#if (!!number)}
<StatusColor selected={selected} affects="project" entry={entry}>
<div class="sccfg">{number}</div>
</StatusColor>
{/if}
+5 -1
View File
@@ -1,9 +1,13 @@
<script lang="ts">
import { onMount } from "svelte";
import markStale from "../stores/markStale";
let lastFocus = Date.now();
function handleFocus() {
markStale("*");
if ((Date.now() - lastFocus) > 60000) {
markStale("*");
lastFocus = Date.now();
}
}
onMount(() => {
+3
View File
@@ -169,6 +169,9 @@ div.modal :global(input), div.modal :global(select), div.modal :global(textarea)
div.modal :global(select) {
padding-left: 0.5ch;
}
div.modal :global(input)::placeholder {
opacity: 0.5;
}
div.modal :global(select:disabled) {
background: #4a4a4a;
color: #fff;
+10 -1
View File
@@ -1,4 +1,8 @@
<div class="option-row">
<script lang="ts">
export let centered = false;
</script>
<div class="option-row" class:centered>
<slot></slot>
</div>
@@ -7,4 +11,9 @@
margin-left: -0.5ch;
margin-right: -0.5ch;
}
div.option-row.centered {
text-align: center;
padding-bottom: 0.5em;
}
</style>
@@ -0,0 +1,111 @@
<script lang="ts">
import { tick } from "svelte";
import { navigate } from "svelte-routing";
import type { ProjectGroupResult } from "../models/projectgroup";
import modalStore from "../stores/modal";
import projectGroupStore from "../stores/projectGroup";
import { sortProjects } from "../utils/sorters";
import ColoredNumber from "./ColoredNumber.svelte";
export let groups: ProjectGroupResult[] = [];
export let selected: string;
let failedCount = 0;
function onClickAdd() {
modalStore.set({name: "projectgroup.add"});
}
function onNavigate(group: ProjectGroupResult) {
if (group.projects.length === 0) {
navigate(`/questlog/${group.id}`);
} else {
const projetcs = [...group.projects].sort(sortProjects);
navigate(`/questlog/${group.id}/${projetcs[0].id}`);
// There's some weirdness with navigate. This hack will just do a
// groups = [...groups] in the sttore to get the page to update.
tick().then(() => {
projectGroupStore.fakeRefresh();
})
}
}
</script>
<div class="group-menu">
{#each groups as group (group.id)}
<div class="group-entry" on:click={() => onNavigate(group)} class:selected={selected === group.id}>
<div class="name">{group.abbreviation}</div>
<div class="counts">
<ColoredNumber selected={selected === group.id} status="active" number={group.projectCounts["active"]} />
<ColoredNumber selected={selected === group.id} status="background" number={group.projectCounts["background"]} />
<ColoredNumber selected={selected === group.id} status="progress" number={group.projectCounts["progress"]} />
<ColoredNumber selected={selected === group.id} status="to do" number={group.projectCounts["to do"]} />
<ColoredNumber selected={selected === group.id} status="on hold" number={group.projectCounts["on hold"]} />
</div>
</div>
{/each}
<div class="group-entry add" on:click={onClickAdd}>
<div>+</div>
</div>
</div>
<style>
div.group-menu {
display: flex;
flex-direction: row;
padding: 0 2ch;
border-bottom: 1px solid #333;
margin-bottom: 0.5em;
-webkit-user-select: none;
-moz-user-select: none;
}
div.group-entry {
padding: 0.05em 1ch;
padding-bottom: 0.15em;
margin-right: 2ch;
border: 1px solid #333;
border-bottom: none;
border-top-left-radius: 0.5em;
border-top-right-radius: 0.5em;
cursor: pointer;
color: #777;
font-weight: 300;
}
div.group-entry.add {
color: #333;
}
div.group-entry.add > div {
font-size: 2em;
line-height: 1em;
}
div.group-entry.selected {
background-color: #191919;
color: #AAA;
}
div.group-entry:hover {
color: #CCC;
background-color: #222222;
}
div.name {
text-align: center;
padding: 0em 0.4ch;
}
div.counts {
font-size: 0.666em;
font-weight: 400;
text-align: center;
}
div.counts :global(div) {
display: inline-block;
padding: 0em 0.2ch;
}
</style>
@@ -0,0 +1,19 @@
<script lang="ts">
import projectGroupStore from "../stores/projectGroup";
export let value = "";
export let name = "";
export let disabled = false;
export let optional = false;
</script>
<select name={name} bind:value={value} disabled={disabled || $projectGroupStore.loading}>
{#if optional}
<option value={""} selected={"" === value}>None</option>
{/if}
{#each $projectGroupStore.groups as group (group.id)}
{#if group.id !== "META_UNGROUPED"}
<option value={group.id} selected={group.id === value}>{group.name}</option>
{/if}
{/each}
</select>
@@ -1,7 +1,6 @@
<script lang="ts">
import type Project from "../models/project";
import projectStore from "../stores/project";
interface OptGroup {
status: string
projects: Project[]
+2 -1
View File
@@ -4,13 +4,14 @@
export let projects: ProjectResult[];
export let label: string = "";
export let selected: string | null = null;
</script>
{#if projects.length > 0}
<div class="ql-list">
<h2>{label}</h2>
{#each projects as project (project.id)}
<QlListItem project={project} />
<QlListItem selected={project.id === selected} project={project} />
{/each}
</div>
{/if}
+6 -8
View File
@@ -1,26 +1,24 @@
<script lang="ts">
import App from "../App.svelte";
import type { ProjectResult } from "../models/project";
import selectionStore from "../stores/selection";
import { navigate } from "svelte-routing";
import type { ProjectResult } from "../models/project";
import DaysLeft from "./DaysLeft.svelte";
import Icon from "./Icon.svelte";
import ProjectIcon from "./ProjectIcon.svelte";
import ProjectProgress from "./ProjectProgress.svelte";
import StatusColor from "./StatusColor.svelte";
import Tag from "./Tag.svelte";
import Tag from "./Tag.svelte";
import TimeProgress from "./TimeProgress.svelte";
export let project: ProjectResult;
export let selected: boolean;
let selected: boolean;
let completed: boolean;
let deadline: boolean;
function handleClick() {
selectionStore.change("hash", project.id);
navigate(`/questlog/${project.groupId || "META_UNGROUPED"}/${project.id}`);
}
$: selected = $selectionStore.hash === project.id;
$: completed = !project.active;
$: deadline = !!project.endTime;
</script>
+51 -33
View File
@@ -9,11 +9,21 @@
import QlList from "./QLList.svelte";
import Boi from "../components/Boi.svelte";
import type { ModalData } from "../stores/modal";
import type { ProjectGroupResult } from "../models/projectgroup";
import ProjectGroupMenu from "./ProjectGroupMenu.svelte";
import OptionRow from "./OptionRow.svelte";
import Option from "./Option.svelte";
import type ProjectGroup from "../models/projectgroup";
export let projects: ProjectResult[];
export let groups: ProjectGroupResult[];
export let projectId = "";
export let groupId = "";
const mdProjectAdd: ModalData = {name: "project.add"};
let mdProjectAdd: ModalData = {name: "project.add", groupId: null};
let mdGroupEdit: ModalData = {name: "projectgroup.edit", projectGroup: {} as ProjectGroup};
let mdGroupDelete: ModalData = {name: "projectgroup.delete", projectGroup: {} as ProjectGroup};
let projects: ProjectResult[] = [];
let expiringProjects: ProjectResult[];
let activeProjects: ProjectResult[];
let inactiveProjects: ProjectResult[];
@@ -22,16 +32,29 @@
let onholdProjects: ProjectResult[];
let ideaProjects: ProjectResult[];
let project: ProjectResult = null;
let selectedGroup: ProjectGroupResult | null = null;
function sortProjects(a: ProjectResult, b: ProjectResult) {
const aName = `${a.tags.slice(0, 1).map(t => t+":").join("")} ${a.name}`.trim();
const bName = `${b.tags.slice(0, 1).map(t => t+":").join("")} ${b.name}`.trim();
console.log(aName, bName)
return aName.localeCompare(bName);
}
// Ensure a selection.
$: {
if (groupId == "" && groups.length > 0) {
groupId = groups[0].id;
}
}
$: selectedGroup = groups.find(g => g.id === groupId)
$: projects = selectedGroup?.projects || [];
$: mdProjectAdd = { name: "project.add", groupId: groupId }
$: mdGroupEdit = { name: "projectgroup.edit", projectGroup: selectedGroup }
$: mdGroupDelete = { name: "projectgroup.delete", projectGroup: selectedGroup }
$: 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(sortProjects);
@@ -43,40 +66,28 @@
$: backgroundProjects = inactiveProjects.filter(p => p.statusTag === "background").sort(sortProjects);
$: progressProjects = inactiveProjects.filter(p => p.statusTag === "progress").sort(sortProjects);
$: {
if (project === null && projects.length > 0) {
if (lastQuest !== "") {
project = projects.find(p => p.id === lastQuest) || null;
}
if (project === null) {
project = expiringProjects[0] || activeProjects[0] || completedProjects[0] || null;
}
if (project !== null) {
selectionStore.change("hash", project.id);
}
}
}
$: {
if ($selectionStore.hash.startsWith("P")) {
lastQuest = $selectionStore.hash;
}
}
$: project = selectedGroup?.projects.find(p => p.id === projectId) || null;
</script>
<ProjectGroupMenu selected={groupId} groups={groups} />
<div class="quest-log">
<div class="list">
<h2>{selectedGroup?.name || ""}</h2>
{#if !!groupId && groupId !== "META_UNGROUPED"}
<OptionRow centered>
<Option open={mdGroupEdit}>Edit Group</Option>
<Option open={mdGroupDelete}>Delete Group</Option>
</OptionRow>
{/if}
<Boi compacter open={mdProjectAdd}>Add Project</Boi>
<QlList label="Deadlines" projects={expiringProjects} />
<QlList label="Active" projects={activeProjects} />
<QlList label="Background" projects={backgroundProjects} />
<QlList label="Progress" projects={progressProjects} />
<QlList label="To Do" projects={ideaProjects} />
<QlList label="On Hold" projects={onholdProjects} />
<QlList label="Completed" projects={completedProjects} />
<QlList label="Failed" projects={failedProjects} />
<QlList selected={project?.id} label={selectedGroup?.categoryNames["deadlines"] || "Deadlines"} projects={expiringProjects} />
<QlList selected={project?.id} label={selectedGroup?.categoryNames["active"] || "Active"} projects={activeProjects} />
<QlList selected={project?.id} label={selectedGroup?.categoryNames["background"] || "Background"} projects={backgroundProjects} />
<QlList selected={project?.id} label={selectedGroup?.categoryNames["progress"] || "Progress"} projects={progressProjects} />
<QlList selected={project?.id} label={selectedGroup?.categoryNames["to do"] || "To Do"} projects={ideaProjects} />
<QlList selected={project?.id} label={selectedGroup?.categoryNames["on hold"] || "On Hold"} projects={onholdProjects} />
<QlList selected={project?.id} label={selectedGroup?.categoryNames["completed"] || "Completed"} projects={completedProjects} />
<QlList selected={project?.id} label={selectedGroup?.categoryNames["failed"] || "Failed"} projects={failedProjects} />
</div>
<div class="body">
{#if project != null}
@@ -96,6 +107,13 @@
width: 32ch;
}
h2 {
font-weight: 200;
margin: 0;
padding-bottom: 0.2em;
text-align: center;
}
div.body {
flex-grow: 1;
margin: 1em 1ch;
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
interface EntryCommon {
active: boolean
active?: boolean
statusTag?: string
}
-2
View File
@@ -10,8 +10,6 @@
hash = value.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
console.log(value, hash);
style = `color: hsl(${(hash << 4) % 360}, ${40 + (hash % 8)}%, ${50+(hash % 4)}%)`
}