Skip to content

Commit 3e3b945

Browse files
committed
convert item dependency tree to list
1 parent d59b147 commit 3e3b945

File tree

10 files changed

+237
-324
lines changed

10 files changed

+237
-324
lines changed

frontend/src/features/archived-items/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import("./crawl-pending-exclusions");
88
import("./crawl-queue");
99
import("./crawl-status");
1010
import("./file-uploader");
11-
import("./item-dependency-tree");
11+
import("./item-dependency-list");
1212
import("./item-dependents");
1313
import("./item-list-controls");
1414
import("./item-tags-input");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "./item-dependency-list";

frontend/src/features/archived-items/item-dependency-tree/item-dependency-tree.stylesheet.css renamed to frontend/src/features/archived-items/item-dependency-list/item-dependency-list.stylesheet.css

File renamed without changes.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import { localized, msg } from "@lit/localize";
2+
import { Task } from "@lit/task";
3+
import type { SlTree } from "@shoelace-style/shoelace";
4+
import { html, nothing, unsafeCSS } from "lit";
5+
import { customElement, property, query } from "lit/decorators.js";
6+
import { repeat } from "lit/directives/repeat.js";
7+
import queryString from "query-string";
8+
9+
import { collectionStatusIcon } from "../templates/collection-status-icon";
10+
11+
import stylesheet from "./item-dependency-list.stylesheet.css";
12+
13+
import { BtrixElement } from "@/classes/BtrixElement";
14+
import { dedupeIcon } from "@/features/collections/templates/dedupe-icon";
15+
import type { ArchivedItemSectionName } from "@/pages/org/archived-item-detail/archived-item-detail";
16+
import { OrgTab, WorkflowTab } from "@/routes";
17+
import type { APIPaginatedList } from "@/types/api";
18+
import type { ArchivedItem } from "@/types/crawler";
19+
import { isCrawl, renderName } from "@/utils/crawler";
20+
import { pluralOf } from "@/utils/pluralize";
21+
22+
const styles = unsafeCSS(stylesheet);
23+
24+
// FIXME Sometimes the API returns circular dependencies
25+
const dependenciesWithoutSelf = (item: ArchivedItem) =>
26+
item.requiresCrawls.filter((id) => id !== item.id);
27+
28+
/**
29+
* @cssPart tree
30+
*/
31+
@customElement("btrix-item-dependency-list")
32+
@localized()
33+
export class ItemDependencyTree extends BtrixElement {
34+
static styles = styles;
35+
36+
@property({ type: String })
37+
collectionId?: string;
38+
39+
@property({ type: Array })
40+
items?: ArchivedItem[];
41+
42+
@query("sl-tree")
43+
private readonly tree?: SlTree | null;
44+
45+
private readonly timerIds: number[] = [];
46+
47+
private readonly dependenciesMap = new Map<
48+
string,
49+
ArchivedItem | undefined
50+
>();
51+
52+
private readonly dependenciesTask = new Task(this, {
53+
task: async ([items], { signal }) => {
54+
if (!items?.length) return;
55+
56+
const itemsMap = new Map(items.map((item) => [item.id, item]));
57+
const newIds: string[] = [];
58+
59+
items.forEach((item) => {
60+
dependenciesWithoutSelf(item).forEach((id) => {
61+
if (!this.dependenciesMap.get(id)) {
62+
const cachedItem = itemsMap.get(id);
63+
if (cachedItem) {
64+
this.dependenciesMap.set(id, cachedItem);
65+
} else {
66+
newIds.push(id);
67+
}
68+
}
69+
});
70+
});
71+
72+
if (!newIds.length) return;
73+
74+
const query = queryString.stringify(
75+
{
76+
ids: newIds,
77+
},
78+
{
79+
arrayFormat: "none",
80+
},
81+
);
82+
83+
const { items: dependencies } = await this.api.fetch<
84+
APIPaginatedList<ArchivedItem>
85+
>(`/orgs/${this.orgId}/all-crawls?${query}`, { signal });
86+
87+
newIds.forEach((id) => {
88+
this.dependenciesMap.set(
89+
id,
90+
dependencies.find((item) => item.id === id),
91+
);
92+
});
93+
},
94+
args: () => [this.items] as const,
95+
});
96+
97+
disconnectedCallback(): void {
98+
this.timerIds.forEach(window.clearTimeout);
99+
super.disconnectedCallback();
100+
}
101+
102+
render() {
103+
if (!this.items?.length) return;
104+
105+
return html`
106+
<btrix-table
107+
style="--btrix-table-grid-template-columns: ${[
108+
"[clickable-start] min-content",
109+
"repeat(4, auto) [clickable-end]",
110+
].join(" ")}"
111+
>
112+
<btrix-table-head
113+
class="mb-2 [--btrix-table-cell-padding-x:var(--sl-spacing-x-small)]"
114+
>
115+
<btrix-table-header-cell>
116+
<span class="sr-only">${msg("Status")}</span>
117+
</btrix-table-header-cell>
118+
<btrix-table-header-cell class="pl-0"
119+
>${msg("Name")}</btrix-table-header-cell
120+
>
121+
<btrix-table-header-cell>
122+
${msg("Dependencies")}
123+
</btrix-table-header-cell>
124+
<btrix-table-header-cell>${msg("Date")}</btrix-table-header-cell>
125+
<btrix-table-header-cell>${msg("Size")}</btrix-table-header-cell>
126+
<btrix-table-header-cell>
127+
<span class="sr-only">${msg("Actions")}</span>
128+
</btrix-table-header-cell>
129+
</btrix-table-head>
130+
<btrix-table-body
131+
class="divide-y rounded border [--btrix-table-cell-padding-x:var(--sl-spacing-x-small)] [--btrix-table-cell-padding-y:var(--sl-spacing-2x-small)]"
132+
>
133+
${repeat(this.items, ({ id }) => id, this.renderItem)}
134+
</btrix-table-body>
135+
</btrix-table>
136+
`;
137+
}
138+
139+
private readonly renderItem = (item: ArchivedItem) => {
140+
return html`
141+
<btrix-table-row
142+
id=${item.id}
143+
class="h-10 cursor-pointer select-none whitespace-nowrap transition-colors duration-fast focus-within:bg-neutral-50 hover:bg-neutral-50"
144+
>
145+
${this.renderContent(item)}
146+
</btrix-table-row>
147+
`;
148+
};
149+
150+
private readonly renderContent = (item: ArchivedItem) => {
151+
const dependencies = dependenciesWithoutSelf(item);
152+
const crawled = isCrawl(item);
153+
const collectionId = this.collectionId;
154+
155+
const date = (value: string) =>
156+
this.localize.date(value, {
157+
month: "2-digit",
158+
year: "2-digit",
159+
day: "2-digit",
160+
hour: "2-digit",
161+
minute: "2-digit",
162+
});
163+
164+
return html`
165+
166+
<btrix-table-cell>
167+
${collectionStatusIcon({ item, collectionId })}
168+
</btrix-table-cell>
169+
<btrix-table-cell class="pl-0" rowClickTarget="a">
170+
<a class=""
171+
href=${
172+
crawled
173+
? `${this.navigate.orgBasePath}/${OrgTab.Workflows}/${item.cid}/${WorkflowTab.Crawls}/${item.id}#${"dependencies" as ArchivedItemSectionName}`
174+
: `${this.navigate.orgBasePath}/${OrgTab.Items}/${item.type}/${item.id}`
175+
}
176+
@click=${this.navigate.link}
177+
>
178+
${renderName(item)}
179+
</a>
180+
</btrix-table-cell>
181+
<btrix-table-cell class="flex items-center gap-1.5 truncate">
182+
${
183+
dependencies.length
184+
? html`
185+
${dedupeIcon({ hasDependencies: true, hasDependents: true })}
186+
${this.localize.number(dependencies.length)}
187+
${pluralOf("dependencies", dependencies.length)}
188+
`
189+
: nothing
190+
}
191+
</btrix-table-cell>
192+
193+
<btrix-table-cell class="flex items-center gap-1.5 truncate">
194+
${
195+
crawled
196+
? html`<sl-tooltip
197+
content=${msg("Date Finished")}
198+
placement="left"
199+
hoist
200+
>
201+
${item.finished
202+
? html`<sl-icon name="gear-wide-connected"></sl-icon> ${date(
203+
item.finished,
204+
)}`
205+
: html`<sl-icon name="play"></sl-icon> ${date(item.started)}`}
206+
</sl-tooltip>`
207+
: html`<sl-tooltip
208+
content=${msg("Date Uploaded")}
209+
placement="left"
210+
hoist
211+
>
212+
<sl-icon name="upload"></sl-icon>
213+
${date(item.started)}
214+
</sl-tooltip>`
215+
}
216+
</btrix-table-cell>
217+
218+
<btrix-table-cell class="flex items-center gap-1.5 truncate">
219+
<sl-icon name="file-earmark-binary"></sl-icon>
220+
${this.localize.bytes(item.fileSize || 0, { unitDisplay: "short" })}
221+
</btrix-table-cell>
222+
</div>`;
223+
};
224+
}

frontend/src/features/archived-items/item-dependency-tree/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)