@goodbones/browser 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/build/dts/index.d.ts +12 -0
  3. package/build/dts/index.d.ts.map +1 -0
  4. package/build/dts/main.d.ts +3 -0
  5. package/build/dts/main.d.ts.map +1 -0
  6. package/build/dts/model/atlas.d.ts +157 -0
  7. package/build/dts/model/atlas.d.ts.map +1 -0
  8. package/build/dts/model/campaigns.d.ts +141 -0
  9. package/build/dts/model/campaigns.d.ts.map +1 -0
  10. package/build/dts/server/collect.d.ts +16 -0
  11. package/build/dts/server/collect.d.ts.map +1 -0
  12. package/build/dts/server/compose.d.ts +13 -0
  13. package/build/dts/server/compose.d.ts.map +1 -0
  14. package/build/dts/server/export.d.ts +3 -0
  15. package/build/dts/server/export.d.ts.map +1 -0
  16. package/build/dts/server/handler.d.ts +6 -0
  17. package/build/dts/server/handler.d.ts.map +1 -0
  18. package/build/dts/server/serve.d.ts +15 -0
  19. package/build/dts/server/serve.d.ts.map +1 -0
  20. package/build/dts/server/site.d.ts +2 -0
  21. package/build/dts/server/site.d.ts.map +1 -0
  22. package/build/dts/server/source.d.ts +19 -0
  23. package/build/dts/server/source.d.ts.map +1 -0
  24. package/build/dts/server/vite-plugin.d.ts +8 -0
  25. package/build/dts/server/vite-plugin.d.ts.map +1 -0
  26. package/build/dts/server/watch.d.ts +10 -0
  27. package/build/dts/server/watch.d.ts.map +1 -0
  28. package/build/esm/index.js +14 -0
  29. package/build/esm/index.js.map +1 -0
  30. package/build/esm/main.js +134 -0
  31. package/build/esm/main.js.map +1 -0
  32. package/build/esm/model/atlas.js +431 -0
  33. package/build/esm/model/atlas.js.map +1 -0
  34. package/build/esm/model/campaigns.js +181 -0
  35. package/build/esm/model/campaigns.js.map +1 -0
  36. package/build/esm/server/collect.js +83 -0
  37. package/build/esm/server/collect.js.map +1 -0
  38. package/build/esm/server/compose.js +50 -0
  39. package/build/esm/server/compose.js.map +1 -0
  40. package/build/esm/server/export.js +19 -0
  41. package/build/esm/server/export.js.map +1 -0
  42. package/build/esm/server/handler.js +67 -0
  43. package/build/esm/server/handler.js.map +1 -0
  44. package/build/esm/server/serve.js +80 -0
  45. package/build/esm/server/serve.js.map +1 -0
  46. package/build/esm/server/site.js +6 -0
  47. package/build/esm/server/site.js.map +1 -0
  48. package/build/esm/server/source.js +67 -0
  49. package/build/esm/server/source.js.map +1 -0
  50. package/build/esm/server/vite-plugin.js +21 -0
  51. package/build/esm/server/vite-plugin.js.map +1 -0
  52. package/build/esm/server/watch.js +56 -0
  53. package/build/esm/server/watch.js.map +1 -0
  54. package/build/site/_astro/Browser.yxldcnQ3.js +3 -0
  55. package/build/site/_astro/client.Buyw3Q1S.js +9 -0
  56. package/build/site/_astro/index.DQrWs1cb.css +1 -0
  57. package/build/site/_astro/react.DJY1zw8Z.js +1 -0
  58. package/build/site/favicon.svg +1 -0
  59. package/build/site/index.html +8 -0
  60. package/package.json +78 -0
  61. package/src/app/components/Browser.tsx +153 -0
  62. package/src/app/components/architecture/ArchitectureBrowser.tsx +240 -0
  63. package/src/app/components/architecture/Detail.tsx +655 -0
  64. package/src/app/components/architecture/ManifestPane.tsx +178 -0
  65. package/src/app/components/architecture/Tree.tsx +205 -0
  66. package/src/app/components/architecture/rows.ts +181 -0
  67. package/src/app/components/architecture/selection.ts +6 -0
  68. package/src/app/components/campaigns/CampaignBrowser.tsx +146 -0
  69. package/src/app/components/campaigns/Detail.tsx +653 -0
  70. package/src/app/components/campaigns/Ladder.tsx +326 -0
  71. package/src/app/components/campaigns/pick.ts +7 -0
  72. package/src/app/components/lib/data.ts +132 -0
  73. package/src/app/pages/index.astro +34 -0
  74. package/src/app/public/favicon.svg +1 -0
  75. package/src/app/styles/theme.css +993 -0
  76. package/src/index.ts +37 -0
  77. package/src/main.ts +155 -0
  78. package/src/model/atlas.ts +751 -0
  79. package/src/model/campaigns.ts +376 -0
  80. package/src/server/collect.ts +132 -0
  81. package/src/server/compose.ts +79 -0
  82. package/src/server/export.ts +24 -0
  83. package/src/server/handler.ts +77 -0
  84. package/src/server/serve.ts +100 -0
  85. package/src/server/site.ts +6 -0
  86. package/src/server/source.ts +89 -0
  87. package/src/server/vite-plugin.ts +32 -0
  88. package/src/server/watch.ts +65 -0
@@ -0,0 +1,146 @@
1
+ import { useMemo, useState } from "react";
2
+
3
+ import type { CampaignCard, CampaignView } from "../../../model/campaigns.js";
4
+ import { Detail } from "./Detail.js";
5
+ import { Ladder } from "./Ladder.js";
6
+ import type { Pick } from "./pick.js";
7
+
8
+ // The Campaign Browser: the ladder the docs draw, for real. A campaign's
9
+ // phases in order, each recognized by its objectives; its sectors standing at
10
+ // the first phase with residue for them; the end state last. Hover a phase,
11
+ // an objective or a sector and the rest of the ladder says how they relate;
12
+ // the pane beside it says what `campaigns` and `check` would.
13
+
14
+ // The selection in the hash: `<campaign>` alone, or `<campaign>/<kind>:<id>`.
15
+ const parseSelection = (
16
+ view: CampaignView,
17
+ text: string | null,
18
+ ): { campaign: string | null; pick: Pick | null } => {
19
+ if (text === null) return { campaign: null, pick: null };
20
+ const at = text.indexOf("/");
21
+ const campaign = at === -1 ? text : text.slice(0, at);
22
+ if (!view.campaigns.some((one) => one.id === campaign)) return { campaign: null, pick: null };
23
+ if (at === -1) return { campaign, pick: null };
24
+ const rest = text.slice(at + 1);
25
+ const colon = rest.indexOf(":");
26
+ const kind = colon === -1 ? rest : rest.slice(0, colon);
27
+ const id = colon === -1 ? "" : rest.slice(colon + 1);
28
+ const pick: Pick | null =
29
+ kind === "campaign"
30
+ ? { kind: "campaign" }
31
+ : kind === "phase"
32
+ ? { kind: "phase", id }
33
+ : kind === "objective"
34
+ ? { kind: "objective", id }
35
+ : kind === "sector"
36
+ ? { kind: "sector", name: id }
37
+ : null;
38
+ return { campaign, pick };
39
+ };
40
+
41
+ export const selectionText = (campaign: string, pick: Pick | null): string =>
42
+ pick === null
43
+ ? campaign
44
+ : `${campaign}/${pick.kind}${pick.kind === "campaign" ? "" : `:${pick.kind === "sector" ? pick.name : pick.id}`}`;
45
+
46
+ type Props = {
47
+ readonly view: CampaignView;
48
+ readonly selection: string | null;
49
+ readonly onSelect: (selection: string | null) => void;
50
+ };
51
+
52
+ export const CampaignBrowser = ({ onSelect, selection, view }: Props): React.JSX.Element => {
53
+ const parsed = useMemo(() => parseSelection(view, selection), [view, selection]);
54
+ const campaign: CampaignCard | undefined =
55
+ view.campaigns.find((one) => one.id === parsed.campaign) ?? view.campaigns[0];
56
+ const [hover, setHover] = useState<Pick | null>(null);
57
+
58
+ if (campaign === undefined) {
59
+ return <div className="placeholder">This policy declares no campaigns.</div>;
60
+ }
61
+ const selected = parsed.campaign === campaign.id ? parsed.pick : null;
62
+ const pick = (next: Pick | null): void => {
63
+ onSelect(selectionText(campaign.id, next));
64
+ };
65
+ const nudged = view.nudge?.sectors.filter((one) => one.campaign === campaign.id) ?? [];
66
+
67
+ return (
68
+ <div className="camp">
69
+ <section className="pane ladder-pane" aria-label="Campaign ladder">
70
+ <div className="pane-head tabs-row">
71
+ {view.campaigns.map((one) => (
72
+ <button
73
+ key={one.id}
74
+ type="button"
75
+ className={one.id === campaign.id ? "tab active" : "tab"}
76
+ onClick={() => {
77
+ onSelect(selectionText(one.id, null));
78
+ }}
79
+ title={one.title ?? one.id}
80
+ >
81
+ <span className="mono">{one.id}</span>
82
+ <span className={`pct ${one.complete ? "good" : one.stalled ? "bad" : ""}`}>
83
+ {Math.round(one.progress * 100)}%
84
+ </span>
85
+ </button>
86
+ ))}
87
+ </div>
88
+ <div className="camp-head">
89
+ <h2>
90
+ {campaign.title ?? campaign.id}
91
+ {campaign.stalled ? <span className="flag bad">stalled</span> : null}
92
+ {campaign.complete ? <span className="flag good">complete</span> : null}
93
+ {campaign.missingLedger ? <span className="flag warn">no ledger</span> : null}
94
+ {!campaign.arithmetic ? <span className="flag bad">ledger arithmetic</span> : null}
95
+ </h2>
96
+ <div
97
+ className="progress"
98
+ title={`${String(Math.round(campaign.progress * 100))}% of the initial holdouts cleared`}
99
+ >
100
+ <div
101
+ className="progress-bar"
102
+ style={{ width: `${String(Math.round(campaign.progress * 100))}%` }}
103
+ />
104
+ </div>
105
+ <div className="muted small">
106
+ {campaign.count} holdout{campaign.count === 1 ? "" : "s"} left
107
+ {campaign.owner === null ? "" : ` · ${campaign.owner}`}
108
+ {campaign.staleAfterDays === null
109
+ ? ""
110
+ : ` · stale after ${String(campaign.staleAfterDays)}d`}
111
+ {" · scope "}
112
+ <span className="mono">{campaign.scope.join(", ")}</span>
113
+ {campaign.perimeter === null ? "" : ` · perimeter ${campaign.perimeter}`}
114
+ </div>
115
+ </div>
116
+ <div className="ladder-scroll">
117
+ <Ladder
118
+ campaign={campaign}
119
+ nudged={new Set(nudged.map((one) => one.sector))}
120
+ hover={hover}
121
+ selected={selected}
122
+ onHover={setHover}
123
+ onPick={pick}
124
+ />
125
+ </div>
126
+ <div className="legend">
127
+ <span className="key defined">defined phase</span>
128
+ <span className="key open">open phase</span>
129
+ <span className="key sector">sector</span>
130
+ <span className="key legacy">legacy</span>
131
+ <span className="key done">done</span>
132
+ <span className="key nudged">touched in the working tree</span>
133
+ </div>
134
+ </section>
135
+ <section className="pane detail-pane" aria-label="Details">
136
+ <Detail
137
+ view={view}
138
+ campaign={campaign}
139
+ shown={hover ?? selected ?? { kind: "campaign" }}
140
+ pinned={hover === null && selected !== null}
141
+ onPick={pick}
142
+ />
143
+ </section>
144
+ </div>
145
+ );
146
+ };