Skip to content

Commit

Permalink
Add patch timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
rudolfs committed Jan 11, 2025
1 parent 432db1c commit aa8428d
Show file tree
Hide file tree
Showing 3 changed files with 318 additions and 13 deletions.
100 changes: 99 additions & 1 deletion src/components/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
styleDisplay?: string;
styleVerticalAlign?: string;
name:
| "attachment"
| "arrow-left"
| "arrow-right"
| "arrow-right-hollow"
| "attachment"
| "checkmark"
| "chevron-down"
| "chevron-right"
| "collapse-panel"
| "comment"
| "comment-checkmark"
| "comment-cross"
| "copy"
| "cross"
| "dashboard"
Expand All @@ -30,6 +33,7 @@
| "issue"
| "lock"
| "markdown"
| "merge"
| "moon"
| "more-vertical"
| "none"
Expand Down Expand Up @@ -115,6 +119,24 @@
<path d="M10 3H9.00003V13H10V3Z" />
<path d="M13 6H12V7H13V6Z" />
<path d="M14 7H13V8H14V7Z" />
{:else if name === "arrow-right-hollow"}
<path d="M9 9L3 9L3 10L9 10L9 9Z" />
<path d="M9 6L3 6L3 7L9 7L9 6Z" />
<path d="M11 4L10 4L10 5L11 5L11 4Z" />
<path d="M10 4L9 4L9 5L10 5L10 4Z" />
<path d="M10 3L9 3L9 6L10 6L10 3Z" />
<path d="M12 5L11 5L11 6L12 6L12 5Z" />
<path d="M13 6L12 6L12 7L13 7L13 6Z" />
<path d="M13 9L12 9L12 10L13 10L13 9Z" />
<path d="M14 8L13 8L13 9L14 9L14 8Z" />
<path d="M14 7L13 7L13 8L14 8L14 7Z" />
<path d="M12 10L11 10L11 11L12 11L12 10Z" />
<path d="M13 9L12 9L12 10L13 10L13 9Z" />
<path d="M11 11L10 11L10 12L11 12L11 11Z" />
<path d="M12 10L11 10L11 11L12 11L12 10Z" />
<path d="M10 11L9 11L9 12L10 12L10 11Z" />
<path d="M10 10L9 10L9 13L10 13L10 10Z" />
<path d="M2 6L3 6L3 10L2 10L2 6Z" />
{:else if name === "attachment"}
<path d="M4 4H12V5H4V4Z" />
<path d="M4 11H11V12H4V11Z" />
Expand Down Expand Up @@ -188,6 +210,72 @@
<path d="M2 4L3 4L3 13H2V4Z" />
<path d="M5 5H11V6H5V5Z" />
<path d="M5 8H11V9H5V8Z" />
{:else if name === "comment-checkmark"}
<path d="M4 2L10 2L10 3L4 3L4 2Z" />
<path d="M3 3L4 3L4 4L3 4L3 3Z" />
<path d="M12 2L13 2L13 3L12 3L12 2Z" />
<path d="M13 2L14 2L14 3L13 3L13 2Z" />
<path d="M12 10L13 10L13 11L12 11L12 10Z" />
<path d="M5 11L12 11L12 12L5 12L5 11Z" />
<path d="M3 13L4 13L4 14L3 14L3 13Z" />
<path d="M4 12L5 12L5 13L4 13L4 12Z" />
<path d="M5 11L6 11L6 12L5 12L5 11Z" />
<path d="M13 5L14 5L14 10L13 10L13 5Z" />
<path d="M2 8L3 8L3 13L2 13L2 8Z" />
<path d="M5 6L6 6L6 7L5 7L5 6Z" />
<path d="M4 5L5 5L5 6L4 6L4 5Z" />
<path d="M6 7L7 7L7 8L6 8L6 7Z" />
<path d="M7 8L8 8L8 9L7 9L7 8Z" />
<path d="M8 7L9 7L9 8L8 8L8 7Z" />
<path d="M9 6L10 6L10 7L9 7L9 6Z" />
<path d="M10 5L11 5L11 6L10 6L10 5Z" />
<path d="M11 4L12 4L12 5L11 5L11 4Z" />
<path d="M12 3L13 3L13 4L12 4L12 3Z" />
<path d="M11 3L12 3L12 4L11 4L11 3Z" />
<path d="M10 4L11 4L11 5L10 5L10 4Z" />
<path d="M9 5L10 5L10 6L9 6L9 5Z" />
<path d="M8 6L9 6L9 7L8 7L8 6Z" />
<path d="M7 7L8 7L8 8L7 8L7 7Z" />
<path d="M5 5L6 5L6 6L5 6L5 5Z" />
<path d="M6 6L7 6L7 7L6 7L6 6Z" />
<path d="M2 4L3 4L3 8L2 8L2 4Z" />
{:else if name === "comment-cross"}
<path d="M3 3L4 3L4 4L3 4L3 3Z" />
<path d="M12 2L13 2L13 3L12 3L12 2Z" />
<path d="M13 2L14 2L14 3L13 3L13 2Z" />
<path d="M5 11L12 11L12 12L5 12L5 11Z" />
<path d="M3 13L4 13L4 14L3 14L3 13Z" />
<path d="M4 12L5 12L5 13L4 13L4 12Z" />
<path d="M5 11L6 11L6 12L5 12L5 11Z" />
<path d="M2 8L3 8L3 13L2 13L2 8Z" />
<path d="M7 3L8 3L8 4L7 4L7 3Z" />
<path d="M6 2L7 2L7 3L6 3L6 2Z" />
<path d="M8 4L9 4L9 5L8 5L8 4Z" />
<path d="M10 4L11 4L11 5L10 5L10 4Z" />
<path d="M10 6L11 6L11 7L10 7L10 6Z" />
<path d="M11 6L12 6L12 7L11 7L11 6Z" />
<path d="M11 7L12 7L12 8L11 8L11 7Z" />
<path d="M12 7L13 7L13 8L12 8L12 7Z" />
<path d="M12 8L13 8L13 9L12 9L12 8Z" />
<path d="M13 8L14 8L14 9L13 9L13 8Z" />
<path d="M7 8L8 8L8 9L7 9L7 8Z" />
<path d="M6 8L7 8L7 9L6 9L6 8Z" />
<path d="M8 7L9 7L9 8L8 8L8 7Z" />
<path d="M10 6L11 6L11 7L10 7L10 6Z" />
<path d="M9 5L11 5L11 7L9 7L9 5Z" />
<path d="M11 4L12 4L12 5L11 5L11 4Z" />
<path d="M12 3L13 3L13 4L12 4L12 3Z" />
<path d="M11 3L12 3L12 4L11 4L11 3Z" />
<path d="M9 4L10 4L10 5L9 5L9 4Z" />
<path d="M8 6L12 6L12 7L8 7L8 6Z" />
<path d="M8 6L9 6L9 7L8 7L8 6Z" />
<path d="M7 7L8 7L8 8L7 8L7 7Z" />
<path d="M7 2L8 2L8 3L7 3L7 2Z" />
<path d="M8 3L9 3L9 4L8 4L8 3Z" />
<path d="M2 4L3 4L3 8L2 8L2 4Z" />
<path d="M12 10L13 10L13 11L12 11L12 10Z" />
<path d="M6 2L7 2L7 3L6 3L6 2Z" />
<path d="M4 2L5 2L5 3L4 3L4 2Z" />
{:else if name === "copy"}
<path d="M6.5 2H13.5V3H6.5V2Z" />
<path d="M3.5 5H4.5V6H3.5V5Z" />
Expand Down Expand Up @@ -415,6 +503,16 @@
<path d="M8 11H9V12H8V11Z" />
<path d="M7 10H8V12H7V10Z" />
<path d="M3 4.00003H4V6.00003H3V4.00003Z" />
{:else if name === "merge"}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M13 11H14V14H11L11 11H12V7H13V11ZM12 13L12 12H13L13 13H12Z" />
<path d="M12 7L9 7L9 9H8L8 8L7 8V7H6V6L7 6V5H8L8 4L9 4V6L12 6L12 7Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3 5H2L2 2L5 2L5 5H4L4 11H5L5 14H2L2 11H3L3 5ZM4 4V3H3L3 4L4 4ZM3 12H4L4 13H3V12Z" />
{:else if name === "moon"}
<path d="M4 3H6V4H4V3Z" />
<path d="M3 4L4 4L4 6H3V4Z" />
Expand Down
187 changes: 187 additions & 0 deletions src/components/PatchTimeline.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<script lang="ts">
import type { Action } from "@bindings/cob/patch/Action";
import type { Operation } from "@bindings/cob/Operation";
import type { Author } from "@bindings/cob/Author";
type FlatAction = Action & {
id: string;
author: Author;
timestamp: number;
};
type FlatActionWithPrevious = Action & {
previous?: Action;
};
type StateTracker = Record<Action["type"], Action>;
import Label from "@app/components/Label.svelte";
import PatchStateBadge from "./PatchStateBadge.svelte";
import NodeId from "./NodeId.svelte";
import { authorForNodeId } from "@app/lib/utils";
import Id from "./Id.svelte";
import Icon from "./Icon.svelte";
interface Props {
activity: Operation<Action>[];
}
/* eslint-disable prefer-const */
let { activity }: Props = $props();
/* eslint-enable prefer-const */
console.log({ activity });
let timeline = $derived(enrichActivity(flattenActivity(activity)));
function flattenActivity(activity: Operation<Action>[]): FlatAction[] {
return activity.flatMap(operation =>
operation.actions.map(action => ({
...action,
id: operation.id,
author: operation.author,
timestamp: operation.timestamp,
})),
);
}
function enrichActivity(
flatActivity: FlatAction[],
): FlatActionWithPrevious[] {
let result: FlatActionWithPrevious[] = [];
let timelineStateTracker: StateTracker = {} as StateTracker;
flatActivity.forEach(entry => {
if (timelineStateTracker[entry.type]) {
result.push({ ...entry, previous: timelineStateTracker[entry.type] });
} else {
result.push(entry);
}
timelineStateTracker[entry.type] = entry;
});
return result;
}
function itemDiff(previousState: string[], newState: string[]) {
const removed = previousState.filter(x => !newState.includes(x));
const added = newState.filter(x => !previousState.includes(x));
return { removed, added };
}
</script>

<style>
.timeline {
display: flex;
gap: 0.75rem;
flex-direction: column;
}
.timeline-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
</style>

<div class="timeline txt-small">
{#each timeline as op}
{#if op.type === "revision"}
<div class="timeline-item">
<Icon name="revision" />
<NodeId {...authorForNodeId(op.author)} />
<div>created a new revision <Id id={op.id} variant="oid" /></div>
</div>
{:else if op.type === "lifecycle"}
<div class="timeline-item">
<Icon name="arrow-right-hollow" />
<NodeId {...authorForNodeId(op.author)} />
{#if op.previous}
changed state from
<PatchStateBadge state={op.previous.state} />
->
<PatchStateBadge state={op.state} />
{:else}
changed state from
<PatchStateBadge state={{ status: "open" }} />
->
<PatchStateBadge state={op.state} />
{/if}
</div>
{:else if op.type === "label"}
{@const changed = itemDiff(op.previous?.labels ?? [], op.labels)}
{#if changed.added.length || changed.removed.length}
<div class="timeline-item">
<NodeId {...authorForNodeId(op.author)} />
{#if changed.added.length}
{#each changed.added as label}
<Label {label} />
{/each}
{/if}
{#if changed.removed.length}
removed labels
{#each changed.removed as label}
<Label {label} />
{/each}
{/if}
</div>
{/if}
{:else if op.type === "assign"}
{@const changed = itemDiff(op.previous?.assignees ?? [], op.assignees)}
{#if changed.added.length || changed.removed.length}
<div class="timeline-item">
<NodeId {...authorForNodeId(op.author)} />
{#if changed.added.length}
assigned
{#each changed.added as assignee}
{assignee}
{/each}
{/if}
{#if changed.removed.length}
unassigned
{#each changed.removed as assignee}
{assignee}
{/each}
{/if}
</div>
{/if}
{:else if op.type === "merge"}
<div class="timeline-item">
<div style:color="var(--color-fill-primary)">
<Icon name="merge" />
</div>
<NodeId {...authorForNodeId(op.author)} />
<div>
merged this patch at revision <Id id={op.revision} variant="oid" />
</div>
</div>
{:else if op.type === "edit"}
{#if op.previous}
<div class="timeline-item">
<Icon name="pen" />
<NodeId {...authorForNodeId(op.author)} />
changed title
<s>
{op.previous.title}
</s>
-> {op.title}
</div>
{/if}
{:else if op.type === "review"}
<div class="timeline-item">
{#if op.verdict === "accept"}
<div style:color="var(--color-foreground-success)">
<Icon name="comment-checkmark" />
</div>
<NodeId {...authorForNodeId(op.author)} />
reviewed and accepted revision <Id id={op.revision} variant="oid" />
{:else}
<div style:color="var(--color-foreground-red)">
<Icon name="comment-cross" />
</div>
<NodeId {...authorForNodeId(op.author)} />
reviewed and rejected revision <Id id={op.revision} variant="oid" />
{/if}
</div>
{/if}
{/each}
</div>
Loading

0 comments on commit aa8428d

Please sign in to comment.