This commit is contained in:
@@ -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}
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
interface EntryCommon {
|
||||
active: boolean
|
||||
active?: boolean
|
||||
statusTag?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -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)}%)`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user