Browse Source

add frontend for jump to requirements.

master
Gisle Aune 1 year ago
parent
commit
63674efde4
  1. 8
      frontend/src/lib/clients/sl3.ts
  2. 4
      frontend/src/lib/components/common/BurndownChart.svelte
  3. 20
      frontend/src/lib/components/common/Modal.svelte
  4. 17
      frontend/src/lib/components/contexts/ModalContext.svelte
  5. 52
      frontend/src/lib/components/contexts/RequirementListContext.svelte
  6. 6
      frontend/src/lib/components/controls/StatInput.svelte
  7. 16
      frontend/src/lib/components/layout/SubSection.svelte
  8. 7
      frontend/src/lib/components/project/RequirementReference.svelte
  9. 2
      frontend/src/lib/components/project/RequirementSection.svelte
  10. 52
      frontend/src/lib/components/project/RequirementSubSection.svelte
  11. 6
      frontend/src/lib/modals/DeletionModal.svelte
  12. 10
      frontend/src/lib/modals/ItemCreateModal.svelte
  13. 19
      frontend/src/lib/modals/RequirementCreateModal.svelte
  14. 160
      frontend/src/lib/modals/RequirementJumpModal.svelte
  15. 13
      frontend/src/lib/models/project.ts
  16. 1
      frontend/src/lib/utils/date.ts
  17. 7
      frontend/src/routes/__layout.svelte
  18. 4
      frontend/src/routes/index.svelte
  19. 2
      frontend/src/routes/login.svelte

8
frontend/src/lib/clients/sl3.ts

@ -2,7 +2,7 @@ import type { TimeInterval } from "$lib/models/common";
import type Item from "$lib/models/item";
import type { GroupItemsOptions, ItemFilter, ItemGroup, ItemInput } from "$lib/models/item";
import type Project from "$lib/models/project";
import type { ProjectEntry, ProjectInput, Requirement, RequirementInput } from "$lib/models/project";
import type { ProjectEntry, ProjectInput, Requirement, RequirementInput, RequirementWithoutItems } from "$lib/models/project";
import type { ScopeInput } from "$lib/models/scope";
import type Scope from "$lib/models/scope";
import type { SprintInput, SprintInputPart } from "$lib/models/sprint";
@ -55,6 +55,12 @@ export default class SL3APIClient {
return this.fetch<{projects: ProjectEntry[]}>("GET", `scopes/${scopeId}/projects`).then(r => r.projects);
}
async listRequirements(scopeId: number | null): Promise<RequirementWithoutItems[]> {
const scopePrefix = scopeId !== null ? `scopes/${scopeId}` : `all-scopes`;
return this.fetch<{requirements: RequirementWithoutItems[]}>("GET", `${scopePrefix}/requirements`).then(r => r.requirements);
}
async createRequirement(scopeId: number, projectId: number, input: RequirementInput): Promise<Requirement> {
return this.fetch<{requirement: Requirement}>("POST", `scopes/${scopeId}/projects/${projectId}/requirements`, input).then(r => r.requirement);
}

4
frontend/src/lib/components/common/BurndownChart.svelte

@ -16,7 +16,7 @@
import type { SprintBurndownDataPoint } from '$lib/models/sprint';
import { getTimeContext } from '../contexts/TimeContext.svelte';
import AxisX from './layercake/AxisX.svelte';
import AxisX from './layercake/AxisX.svelte';
const {now} = getTimeContext();
@ -38,7 +38,7 @@
$: fromTime = Math.floor(new Date(from).getTime() / 86400000) * 86400000;
$: toTime = Math.ceil(new Date(to).getTime() / 86400000) * 86400000;
$: after = Date.parse(to) < $now.getTime();
$: after = new Date(to).getTime() < $now.getTime();
$: today = Math.floor($now.getTime() / 86400000) * 86400000;
$: {

20
frontend/src/lib/components/common/Modal.svelte

@ -9,18 +9,19 @@
export let error: string | null = null;
export let closable: boolean = false;
export let disabled: boolean = false;
export let nobody: boolean = false;
const {closeModal} = getModalContext();
</script>
{#if show}
<div class="modal-background">
<div class="modal" class:wide>
<div class="header">
<div class="modal" class:wide class:nobody>
<div class="header" class:nobody>
<div class="title" class:noclose={!closable}>{verb} {noun}</div>
{#if (closable)}
<div class="x">
<div class="button" on:click={closeModal}>
<div class="button" on:click={closeModal} class:nobody>
<Icon name="times" />
</div>
</div>
@ -29,10 +30,10 @@
{#if (error != null)}
<div class="error">{error}</div>
{/if}
<div class="body">
<div class="body" class:nobody>
<slot></slot>
</div>
<div class="button-row">
<div class="button-row" class:nobody>
<button disabled={disabled} type="submit">{verb} {noun}</button>
<button disabled={!closable} on:click|preventDefault={closeModal} >Cancel</button>
</div>
@ -64,6 +65,10 @@
border-radius: 0.5em;
background: $color-entry1;
&.nobody {
background: none;
}
}
div.modal.wide {
max-width: 80ch;
@ -71,7 +76,6 @@
div.header {
padding: 1em;
margin-bottom: 0;
padding-bottom: 0;
}
@ -149,6 +153,10 @@
button:disabled {
color: $color-entry4;
}
&.nobody {
display: none;
}
}
div.modal :global(label) {

17
frontend/src/lib/components/contexts/ModalContext.svelte

@ -3,16 +3,16 @@
export type ModalData =
| { name: "closed" }
| { name: "scope.create" }
| { name: "scope.edit", scope: Scope }
| { name: "scope.delete", scope: Scope }
| { name: "item.create", requirement?: Requirement }
| { name: "scope.create" }
| { name: "scope.edit", scope: Scope }
| { name: "scope.delete", scope: Scope }
| { name: "item.create", requirement?: RequirementWithoutItems }
| { name: "item.edit", item: Item }
| { name: "item.acquire", item: Item }
| { name: "item.delete", item: Item }
| { name: "requirement.create", project: ProjectEntry }
| { name: "requirement.edit", requirement: Requirement }
| { name: "requirement.delete", requirement: Requirement }
| { name: "requirement.edit", requirement: RequirementWithoutItems }
| { name: "requirement.delete", requirement: RequirementWithoutItems }
| { name: "project.create" }
| { name: "project.edit", project: Project }
| { name: "project.delete", project: Project }
@ -22,6 +22,7 @@
| { name: "sprint.create" }
| { name: "sprint.edit", sprint: Sprint }
| { name: "sprint.delete", sprint: Sprint }
| { name: "requirement.jump" }
interface ModalContextData {
currentModal: Readable<ModalData>
@ -37,12 +38,12 @@
<script lang="ts">
import { writable, type Readable } from "svelte/store";
import { getContext, onMount, setContext } from "svelte";
import type { ProjectEntry, Requirement } from "$lib/models/project";
import type { ProjectEntry, Requirement, RequirementWithoutItems } from "$lib/models/project";
import type Item from "$lib/models/item";
import type Project from "$lib/models/project";
import type Stat from "$lib/models/stat";
import type Sprint from "$lib/models/sprint";
import type Scope from "$lib/models/scope";
import type Scope from "$lib/models/scope";
let store = writable<ModalData>({name: "closed"});

52
frontend/src/lib/components/contexts/RequirementListContext.svelte

@ -0,0 +1,52 @@
<script lang="ts" context="module">
const contextKey = {ctx: "requirementListCtx"};
interface RequirementListContextData {
requirements: Readable<RequirementWithoutItems[]>,
reloadRequirementList(): Promise<void>,
};
const fallback: RequirementListContextData = {
requirements: readable([]),
reloadRequirementList: () => Promise.resolve()
};
export function getRequirementListContext() {
return getContext(contextKey) as RequirementListContextData || fallback
}
</script>
<script lang="ts">
import { readable, writable, type Readable } from "svelte/store";
import { getContext, onMount, setContext } from "svelte";
import { sl3 } from "$lib/clients/sl3";
import { getScopeContext } from "./ScopeContext.svelte";
import type { RequirementWithoutItems } from "$lib/models/project";
const {scope} = getScopeContext();
let requirementsWritable = writable<RequirementWithoutItems[]>([]);
let loading = false;
setContext<RequirementListContextData>(contextKey, {
requirements: {subscribe: requirementsWritable.subscribe},
reloadRequirementList,
});
async function reloadRequirementList() {
if (loading) {
return
}
loading = true;
try {
const newProjects = await sl3(fetch).listRequirements($scope?.id || null)
requirementsWritable.set(newProjects);
} catch(_) {}
loading = false;
}
</script>
<slot></slot>

6
frontend/src/lib/components/controls/StatInput.svelte

@ -1,9 +1,9 @@
<script lang="ts">
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
import { getScopeListContext } from "../contexts/ScopeListContext.svelte";
import type { StatValueInput } from "$lib/models/stat";
import Checkbox from "../layout/Checkbox.svelte";
import type Scope from "$lib/models/scope";
import { getScopeContext } from "../contexts/ScopeContext.svelte";
export let value: StatValueInput[] = [];
export let showAcquired = false;
@ -14,8 +14,8 @@
export let initOnList: number[] = [];
export let overrideScopeId: number = null;
const {scope} = getScopeContext();
const {scopes} = getScopeListContext();
const {scope} = getScopeContext();
let acquiredMap: Record<number, number>;
let requiredMap: Record<number, number>;
@ -45,7 +45,7 @@
}
}
$: actualScope = overrideScopeId !== null && $scopes.length > 0 ? $scopes.find(s => s.id === overrideScopeId) : $scope;
$: actualScope = $scopes.find(s => s.id === overrideScopeId) || $scope;
$: {
if (acquiredMap == null) {

16
frontend/src/lib/components/layout/SubSection.svelte

@ -13,6 +13,7 @@
export let icon: IconName = null;
export let status: Status = null;
export let event: string = "";
export let href: string | null = null;
let blankEvent: boolean
@ -29,7 +30,13 @@
</StatusColor>
{/if}
</div>
<Title value={title} big={big} small={small} sub={subtitle} />
{#if href != null}
<a href={href}>
<Title value={title} big={big} small={small} sub={subtitle} />
</a>
{:else}
<Title value={title} big={big} small={small} sub={subtitle} />
{/if}
<div class="right" class:noProgress>
<slot name="right"></slot>
</div>
@ -51,6 +58,13 @@
:global(div.sub-section)
background: $color-entry2-transparent
a
text-decoration: none
opacity: 0.9
&:hover
opacity: 1
div.entry-icon
opacity: 0.6
margin-right: 1.5ch

7
frontend/src/lib/components/project/RequirementReference.svelte

@ -13,6 +13,7 @@
export let project: IDAndName
export let requirement: IDAndName
export let scopeId: number
export let projectOnly: boolean = false;
const {scope} = getScopeContext();
const {scopes} = getScopeListContext();
@ -33,8 +34,10 @@
<div class="reference">
<a href={projectLink}>{project.name}</a>
<span>&gt;</span>
<a href={requirementLink}>{requirement.name}</a>
{#if !projectOnly}
<span>&gt;</span>
<a href={requirementLink}>{requirement.name}</a>
{/if}
</div>
<style lang="sass">

2
frontend/src/lib/components/project/RequirementSection.svelte

@ -35,7 +35,7 @@
{/if}
{/each}
</LabeledProgressRow>
{#each requirement.items as item (item.id)}
{#each (requirement.items||[]) as item (item.id)}
<ItemEntry item={item} />
{/each}
{#if requirement.aggregateRequired > 0}

52
frontend/src/lib/components/project/RequirementSubSection.svelte

@ -0,0 +1,52 @@
<script lang="ts">
import Markdown from "$lib/components/common/Markdown.svelte";
import Progress from "$lib/components/common/Progress.svelte";
import Option from "$lib/components/layout/Option.svelte";
import OptionsRow from "$lib/components/layout/OptionsRow.svelte";
import type { RequirementWithoutItems } from "$lib/models/project";
import { STATUS_ICONS } from "../common/StatusIcon.svelte";
import Icon from "../layout/Icon.svelte";
import { projectPrettyId, scopePrettyId } from "$lib/utils/prettyIds";
import TagRow from "../common/TagRow.svelte";
import SubSection from "../layout/SubSection.svelte";
import RequirementReference from "./RequirementReference.svelte";
import { getScopeContext } from "../contexts/ScopeContext.svelte";
import { getScopeListContext } from "../contexts/ScopeListContext.svelte";
export let requirement: RequirementWithoutItems;
const {scope} = getScopeContext();
const {scopes} = getScopeListContext();
let tags: string[]
$: {
tags = [...requirement.tags, ...(requirement.project?.tags||[])].sort().filter((t, i, a) => a[i-1] !== t);
}
let projectLink: string
let requirementLink: string
$: {
let actualScope = $scope;
if (actualScope == null || actualScope.id !== requirement.project.scopeId) {
actualScope = $scopes.find(s => s.id === requirement.project.scopeId);
}
projectLink = `/${scopePrettyId(actualScope)}/projects/${projectPrettyId(requirement.project)}`
requirementLink = `${projectLink}#${projectPrettyId(requirement)}`
}
</script>
<div id={projectPrettyId(requirement)} />
<SubSection href={requirementLink} small title={requirement.name} icon={STATUS_ICONS[requirement.status]} status={requirement.status}>
<Progress alwaysSmooth titlePercentageOnly thin green status={requirement.status} count={requirement.totalAcquired} target={requirement.totalRequired} />
<Progress alwaysSmooth titlePercentageOnly thinner gray count={requirement.totalPlanned} target={requirement.totalRequired} />
<RequirementReference projectOnly project={requirement.project} requirement={requirement} scopeId={requirement.project.scopeId} />
<TagRow names={tags} />
<Markdown source={requirement.description} />
<OptionsRow slot="right">
<Option open={{name: "item.create", requirement}}><Icon name="plus" /></Option>
<Option open={{name: "requirement.edit", requirement}}><Icon name="pen" /></Option>
<Option open={{name: "requirement.delete", requirement}} color="red"><Icon name="trash" /></Option>
</OptionsRow>
</SubSection>

6
frontend/src/lib/modals/DeletionModal.svelte

@ -19,6 +19,7 @@ import { getStores } from "$app/stores";
import { getProjectListContext } from "$lib/components/contexts/ProjectListContext.svelte";
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
import type Item from "$lib/models/item";
import type Stat from "$lib/models/stat";
import { scopePrettyId } from "$lib/utils/prettyIds";
@ -72,11 +73,12 @@ import { getStores } from "$app/stores";
break;
case "requirement.delete":
init(`projects/${$project.id}/requirements/${$currentModal.requirement.id}`,
$currentModal.requirement.items.map(i => ({
init(`projects/${$currentModal.requirement.project?.id || $project.id}/requirements/${$currentModal.requirement.id}`,
(($currentModal.requirement as any).items||[]).map((i: Item) => ({
op: "Detach item",
name: i.name,
})),
$currentModal.requirement.project?.scopeId || void(0)
);
noun = "Requirement";
name = $currentModal.requirement.name;

10
frontend/src/lib/modals/ItemCreateModal.svelte

@ -13,13 +13,13 @@
import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
import AcquiredTimeInput from "$lib/components/controls/AcquiredTimeInput.svelte";
import RequirementSelect from "$lib/components/controls/RequirementSelect.svelte";
import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svelte";
import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svelte";
import StatInput from "$lib/components/controls/StatInput.svelte";
import TagInput from "$lib/components/controls/TagInput.svelte";
import TagInput from "$lib/components/controls/TagInput.svelte";
import Checkbox from "$lib/components/layout/Checkbox.svelte";
import type Item from "$lib/models/item";
import type { ItemInput } from "$lib/models/item";
import type { Requirement } from "$lib/models/project";
import type { Requirement, RequirementWithoutItems } from "$lib/models/project";
import type Scope from "$lib/models/scope";
import { formatFormTime } from "$lib/utils/date";
import { statDiff } from "$lib/utils/stat";
@ -66,7 +66,7 @@ import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svel
addAnother = false;
}
function initCreate(scope: Scope, requirement?: Requirement) {
function initCreate(scope: Scope, requirement?: RequirementWithoutItems) {
let stats = requirement?.stats.map(s => ({statId: s.id, required: 0, acquired: 0}));
if (stats == null) {
stats = scope.stats.map(s => ({statId: s.id, required: 0, acquired: 0}))
@ -85,7 +85,7 @@ import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svel
}
op = "Create"
scopeId = scope.id;
scopeId = requirement.project?.scopeId || scope?.id;
openedDate = new Date();
requirementName = requirement?.name;
show = true;

19
frontend/src/lib/modals/RequirementCreateModal.svelte

@ -1,25 +1,22 @@
<script lang="ts">
import { getStores } from "$app/stores";
import { sl3 } from "$lib/clients/sl3";
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte";
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
import ProjectSelect from "$lib/components/controls/ProjectSelect.svelte";
import ProjectSelect from "$lib/components/controls/ProjectSelect.svelte";
import StatInput from "$lib/components/controls/StatInput.svelte";
import StatusSelect from "$lib/components/controls/StatusSelect.svelte";
import TagInput from "$lib/components/controls/TagInput.svelte";
import TagInput from "$lib/components/controls/TagInput.svelte";
import Checkbox from "$lib/components/layout/Checkbox.svelte";
import type { ProjectEntry, Requirement, RequirementInput } from "$lib/models/project";
import type { ProjectEntry, RequirementInput, RequirementWithoutItems } from "$lib/models/project";
import Status from "$lib/models/status";
import { statDiff } from "$lib/utils/stat";
const {currentModal, closeModal} = getModalContext();
const {scope} = getScopeContext();
const {project, reloadProject} = getProjectContext();
const {page} = getStores();
let requirement: RequirementInput
let projectId: number
@ -27,6 +24,7 @@
let oldStats: RequirementInput["stats"]
let showAggregateRequired: boolean
let oldTags: string[]
let scopeId: number
let error: string
let loading: boolean
@ -68,7 +66,7 @@
show = true;
}
function initEdit(current: Requirement, project: ProjectEntry) {
function initEdit(current: RequirementWithoutItems, project: ProjectEntry) {
requirement = {
name: current.name,
description: current.description,
@ -82,8 +80,9 @@
oldTags = [...current.tags];
oldStats = [...requirement.stats];
projectId = project.id;
projectId = current.project?.id || project.id;
requirementId = current.id;
scopeId = current.project?.scopeId || $scope?.id;
show = true;
}
@ -109,7 +108,7 @@
projectId: requirement.projectId !== projectId ? requirement.projectId : void(0),
};
await sl3(fetch).updateRequirement($scope.id, projectId, requirementId, submission);
await sl3(fetch).updateRequirement(scopeId || $scope.id, projectId, requirementId, submission);
break;
}
@ -149,7 +148,7 @@
<label for="stats">Status</label>
<StatusSelect bind:status={requirement.status} />
<label for="stats">Stats</label>
<StatInput showRequired bind:value={requirement.stats} />
<StatInput overrideScopeId={scopeId} showRequired bind:value={requirement.stats} />
<Checkbox bind:checked={requirement.isCoarse} label="Hide 0-requirement stats." />
</ModalBody>
</Modal>

160
frontend/src/lib/modals/RequirementJumpModal.svelte

@ -0,0 +1,160 @@
<script lang="ts">
import Modal from "$lib/components/common/Modal.svelte";
import ModalBody from "$lib/components/common/ModalBody.svelte";
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
import { getRequirementListContext } from "$lib/components/contexts/RequirementListContext.svelte";
import RequirementSubSection from "$lib/components/project/RequirementSubSection.svelte";
import type { RequirementWithoutItems } from "$lib/models/project";
import Status from "$lib/models/status";
import { onMount, tick } from "svelte";
const {currentModal, closeModal} = getModalContext();
const {requirements, reloadRequirementList} = getRequirementListContext();
let error: string
let loading: boolean;
let show: boolean;
let search: string = "";
let searchRaw: string = "";
let searchBar: HTMLInputElement;
let filteredRequirements: RequirementWithoutItems[] = [];
let searchTimeout: any = null;
$: if ($currentModal.name !== "closed") {
show = false;
}
$: {
clearTimeout(searchTimeout);
let newSearch = searchRaw.slice();
searchTimeout = setTimeout(() => {
search = newSearch;
}, 100);
}
$: {
filteredRequirements = $requirements.filter(r => r.status > Status.Blocked && r.status < Status.Completed);
if (search.length > 0) {
const scoreMap = new Map<number, number>();
const searchLc = search.toLocaleLowerCase();
const searchWords = searchLc.split(" ");
for (const req of filteredRequirements) {
const projectNameLc = req.project.name?.toLowerCase() || "";
const nameLc = req.name.toLowerCase();
const nameWords = nameLc.split(" ");
let score = 0;
// prefix
if (nameLc.startsWith(searchLc)) {
score += 30;
}
// prefix
if (projectNameLc.startsWith(searchLc)) {
score += 25;
}
// abbreviation
if (searchWords.length === 1) {
const nameAbbr = nameWords.map(w => w.slice(0, 1)).filter(w => w.length).join("");
if (nameAbbr == searchLc) {
score += 30;
}
if (nameAbbr.startsWith(searchLc)) {
score += 25;
}
}
// tags
const allTags = [...req.tags, ...(req.project?.tags||[])];
for (const tag of allTags) {
const tagLc = tag.toLowerCase();
if (tagLc === tag) {
score += 30
} else if (tagLc.startsWith(searchLc)) {
score += 30 - (allTags.length * 3)
}
}
// project partial match
if (projectNameLc.includes(searchLc)) {
score += 15;
}
// description partial match
if (req.description.toLocaleLowerCase().includes(searchLc)) {
score += 4 * searchLc.length;
}
// letters in order
let j = 0;
let b = 1;
for (let i = 0; i < nameLc.length; i++) {
if (nameLc.charAt(i) == searchLc.charAt(j)) {
score += b;
j += 1;
b += 1;
if (searchLc.charAt(j) === " ") {
j += 1;
}
} else {
b = 1;
}
}
score -= (searchLc.length - j) * 2
scoreMap.set(req.id, score);
}
filteredRequirements = filteredRequirements
.filter(r => scoreMap.get(r.id) > searchLc.length )
.sort((a,b) => scoreMap.get(b.id) - scoreMap.get(a.id));
} else if (filteredRequirements.length > 3) {
filteredRequirements = [];
}
}
onMount(() => {
function onHotkey(e: KeyboardEvent) {
if ($currentModal.name === "closed") {
if (e.shiftKey && e.key.toLowerCase() === "r") {
show = true;
tick().then(() => searchBar.focus());
reloadRequirementList();
closeModal();
} else if (e.key.toLowerCase() === "escape") {
show = false;
}
}
}
window.addEventListener("keyup", onHotkey);
return () => window.removeEventListener("keyup", onHotkey);
})
async function submit() {}
</script>
<form on:submit|preventDefault={submit}>
<Modal wide nobody show={show} verb="Jump" noun={"to Requrirement"} disabled={loading} error={error}>
<ModalBody>
<input bind:value={searchRaw} bind:this={searchBar} placeholder="type to search requirements" />
<div class="requirement-list">
{#each filteredRequirements as req (req.id)}
<RequirementSubSection requirement={req} />
{/each}
</div>
</ModalBody>
</Modal>
</form>
<style lang="sass">
@import "../css/colors"
input
font-size: 1.5em
background-color: $color-entry1 !important
</style>

13
frontend/src/lib/models/project.ts

@ -32,7 +32,7 @@ export interface ProjectInput {
removeTags?: string[]
}
export interface Requirement {
export interface RequirementWithoutItems {
id: number
name: string
description: string
@ -44,7 +44,18 @@ export interface Requirement {
isCoarse: boolean
aggregateRequired: number
stats: StatProgressWithPlanned[]
tags: string[]
project?: RequirementProjectLink
}
export interface Requirement extends RequirementWithoutItems {
items: Item[]
}
export interface RequirementProjectLink {
id: number
scopeId: number
name: string
tags: string[]
}

1
frontend/src/lib/utils/date.ts

@ -57,7 +57,6 @@ export function formatWeekdayTitle(now: Date, time: Date | string | number): str
}
const diff = Math.floor((time.getTime() - now.getTime()) / (86400000));
console.log(diff);
if (diff === 0) {
return "Today"
} else if (diff === -1) {

7
frontend/src/routes/__layout.svelte

@ -38,6 +38,7 @@
import Background from "$lib/components/layout/Background.svelte"
import TimeContext from "$lib/components/contexts/TimeContext.svelte";
import ModalContext from "$lib/components/contexts/ModalContext.svelte";
import RequirementListContext from "$lib/components/contexts/RequirementListContext.svelte";
let opacity = 0.175;
@ -48,7 +49,9 @@
<ModalContext>
<TimeContext>
<Background src={backgroundImage} initialOrientation="landscape" opacity={opacity} />
<slot></slot>
<RequirementListContext>
<Background src={backgroundImage} initialOrientation="landscape" opacity={opacity} />
<slot></slot>
</RequirementListContext>
</TimeContext>
</ModalContext>

4
frontend/src/routes/index.svelte

@ -64,6 +64,8 @@
import SprintListContext from "$lib/components/contexts/SprintListContext.svelte";
import type Sprint from "$lib/models/sprint";
import Icon from "$lib/components/layout/Icon.svelte";
import RequirementJumpModal from "$lib/modals/RequirementJumpModal.svelte";
import RequirementCreateModal from "$lib/modals/RequirementCreateModal.svelte";
export let scopes: Scope[] = [];
export let acquiredItems: Item[];
@ -107,6 +109,8 @@
<DeletionModal />
<ScopeCreateUpdateModal />
<SprintCreateUpdateModal />
<RequirementCreateModal />
<RequirementJumpModal />
</SprintListContext>
</ScopeListContext>
</ItemMultiListContext>

2
frontend/src/routes/login.svelte

@ -25,13 +25,11 @@
try {
if (changeMode) {
const res = await Auth.completeNewPassword(changeUser, newPassword)
console.log(res);
changeMode = false;
password = "";
} else {
const res = await Auth.signIn(username, password);
console.log(res);
if (res.challengeName == "NEW_PASSWORD_REQUIRED") {
newPassword = "";

Loading…
Cancel
Save