@inflexa-ai/harness 0.31.1 → 0.33.0
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.
- package/dist/agents/conversation-agent.d.ts +15 -0
- package/dist/agents/conversation-agent.js +16 -6
- package/dist/agents/report-session-agent.d.ts +9 -0
- package/dist/agents/report-session-agent.js +10 -2
- package/dist/agents/sandbox/shared.d.ts +13 -1
- package/dist/agents/sandbox/shared.js +22 -5
- package/dist/app/chat-turn.d.ts +11 -0
- package/dist/app/chat-turn.js +17 -1
- package/dist/app/spawn-report-session.d.ts +12 -0
- package/dist/app/spawn-report-session.js +21 -1
- package/dist/config/environment-stores.d.ts +53 -5
- package/dist/contracts/report-blocks.d.ts +8 -8
- package/dist/contracts/schemas/chat-parts.d.ts +6 -6
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -0
- package/dist/prompts/briefing.d.ts +1 -1
- package/dist/prompts/briefing.js +4 -0
- package/dist/prompts/planner.js +13 -0
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +5 -5
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +4 -3
- package/dist/prompts/sandbox-standards.d.ts +20 -0
- package/dist/prompts/sandbox-standards.js +65 -0
- package/dist/provenance/seam.d.ts +203 -0
- package/dist/provenance/seam.js +71 -0
- package/dist/reference-data/catalog.js +11 -9
- package/dist/report-model/authoring-grammar.d.ts +2 -2
- package/dist/report-render/assets.d.ts +10 -0
- package/dist/report-render/assets.js +14 -0
- package/dist/report-render/design.js +256 -0
- package/dist/report-render/page.d.ts +75 -0
- package/dist/report-render/page.js +793 -1
- package/dist/report-render/provenance-data.d.ts +42 -0
- package/dist/report-render/provenance-data.js +62 -0
- package/dist/report-render/render.d.ts +20 -1
- package/dist/report-render/render.js +23 -10
- package/dist/report-render/types.d.ts +3 -0
- package/dist/report-render/views/chart-view.d.ts +5 -1
- package/dist/report-render/views/chart-view.js +6 -2
- package/dist/report-render/views/lineage.d.ts +93 -0
- package/dist/report-render/views/lineage.js +86 -0
- package/dist/report-render/views/page-view.d.ts +22 -1
- package/dist/report-render/views/page-view.js +12 -3
- package/dist/report-render/views/prose.d.ts +7 -1
- package/dist/report-render/views/prose.js +10 -6
- package/dist/report-render/views/references-view.d.ts +17 -1
- package/dist/report-render/views/references-view.js +12 -4
- package/dist/report-render/views/values.d.ts +17 -4
- package/dist/report-render/views/values.js +21 -8
- package/dist/runtime/assemble.d.ts +22 -0
- package/dist/runtime/assemble.js +4 -0
- package/dist/sandbox/create-sandbox.d.ts +20 -4
- package/dist/sandbox/create-sandbox.js +4 -0
- package/dist/sandbox/docker-client.d.ts +16 -6
- package/dist/sandbox/docker-client.js +47 -31
- package/dist/sandbox/farm.d.ts +95 -0
- package/dist/sandbox/farm.js +127 -0
- package/dist/sandbox/k8s-client.d.ts +24 -10
- package/dist/sandbox/k8s-client.js +85 -122
- package/dist/sandbox/mount-plan.d.ts +23 -1
- package/dist/sandbox/mount-plan.js +44 -7
- package/dist/sandbox/sandbox-error.d.ts +12 -0
- package/dist/sandbox/sandbox-error.js +2 -0
- package/dist/sandbox/types.d.ts +118 -0
- package/dist/schemas/artifact-manifest.d.ts +4 -4
- package/dist/schemas/plan-schemas.d.ts +3 -0
- package/dist/schemas/plan-schemas.js +1 -1
- package/dist/schemas/validate-plan.js +20 -1
- package/dist/schemas/workflow-state.d.ts +4 -0
- package/dist/schemas/workflow-state.js +10 -0
- package/dist/tasks/data-profile.d.ts +15 -0
- package/dist/tasks/data-profile.js +36 -26
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +18 -0
- package/dist/tools/execute-analysis.js +87 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +16 -1
- package/dist/tools/report-authoring/authoring-tools.js +58 -16
- package/dist/tools/report-session/derive-table.d.ts +3 -0
- package/dist/tools/report-session/derive-table.js +15 -0
- package/dist/tools/report-session/preview-report.d.ts +13 -4
- package/dist/tools/report-session/preview-report.js +23 -6
- package/dist/tools/report-session/record-version.d.ts +3 -0
- package/dist/tools/report-session/record-version.js +7 -3
- package/dist/tools/research/generate-plan.js +12 -2
- package/dist/tools/sandbox/index.d.ts +1 -0
- package/dist/tools/sandbox/index.js +1 -0
- package/dist/tools/sandbox/link-packages.d.ts +27 -0
- package/dist/tools/sandbox/link-packages.js +52 -0
- package/dist/tools/sandbox/list-available-packages.d.ts +50 -30
- package/dist/tools/sandbox/list-available-packages.js +131 -40
- package/dist/tools/start-report-session.d.ts +7 -0
- package/dist/tools/start-report-session.js +1 -0
- package/dist/workflows/execute-analysis.d.ts +15 -69
- package/dist/workflows/execute-analysis.js +5 -4
- package/package.json +2 -1
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* that it needs is interpolated at build time. The style rules, the ECharts theme, and the grid theme
|
|
6
6
|
* parameters live in `design.ts`.
|
|
7
7
|
*/
|
|
8
|
-
import { AG_GRID_ASSET, assetSource, ECHARTS_ASSET } from "./assets.js";
|
|
8
|
+
import { AG_GRID_ASSET, assetSource, ECHARTS_ASSET, TSPROV_ASSET } from "./assets.js";
|
|
9
9
|
import { CHART_SOURCE_MEMBER, POINT_LABEL } from "./chart.js";
|
|
10
10
|
import { ECHARTS_THEME_NAME, GRID_HEADER_BORDER_PX, GRID_HEADER_HEIGHT_PX, GRID_MIN_COLUMN_WIDTH_PX, GRID_PRINT_ROW_CAP, GRID_ROW_HEIGHT_PX, GRID_THEME_PARAMS, GRID_TOOLTIP_DELAY_MS, GRID_VISIBLE_ROWS, } from "./design.js";
|
|
11
|
+
import { REPORT_PROVENANCE_GLOBAL } from "./provenance-data.js";
|
|
11
12
|
import { scriptJson } from "./script-json.js";
|
|
12
13
|
import { TABLE_DATA_GLOBAL } from "./table-data.js";
|
|
14
|
+
import { LINEAGE_BLOCK_ATTRIBUTE, LINEAGE_CONTROL_CLASS, LINEAGE_KEY_ATTRIBUTE, LINEAGE_KEYS_ATTRIBUTE } from "./views/lineage.js";
|
|
15
|
+
import { CHAIN_HASH_CHARS } from "./views/references-view.js";
|
|
13
16
|
import { GRID_COUNT_CLASS, GRID_MOUNT_ATTRIBUTE, GRID_NOTE_CLASS, GRID_ROWS_WORD } from "./views/values.js";
|
|
14
17
|
/**
|
|
15
18
|
* The head reference of the chart runtime. The page loads it from the sibling assets directory, thus the
|
|
@@ -28,6 +31,14 @@ export const ASSET_HEAD = `<script src="${assetSource(ECHARTS_ASSET)}"></script>
|
|
|
28
31
|
* whole directory.
|
|
29
32
|
*/
|
|
30
33
|
export const GRID_ASSET_HEAD = `<script src="${assetSource(AG_GRID_ASSET)}"></script>`;
|
|
34
|
+
/**
|
|
35
|
+
* The head reference of the provenance library.
|
|
36
|
+
*
|
|
37
|
+
* The library walks the chain of one pin, and a page with no provenance document holds no pin to walk.
|
|
38
|
+
* Thus the skeleton writes this tag only for a page that carries the document, and the manifest still
|
|
39
|
+
* stages the file for the whole directory.
|
|
40
|
+
*/
|
|
41
|
+
export const LINEAGE_ASSET_HEAD = `<script src="${assetSource(TSPROV_ASSET)}"></script>`;
|
|
31
42
|
/**
|
|
32
43
|
* The name of the readiness event, and the name of the window sentinel that guards a late listener. The
|
|
33
44
|
* bootstrap below emits both names, thus this module owns them. A capture that waits for the page reads the
|
|
@@ -877,3 +888,784 @@ export const GRID_BOOTSTRAP = `(function () {
|
|
|
877
888
|
}
|
|
878
889
|
}
|
|
879
890
|
})();`;
|
|
891
|
+
/**
|
|
892
|
+
* The name of the provenance library on the page.
|
|
893
|
+
*
|
|
894
|
+
* The browser bundle of the package declares this one global, and the page and the library meet there and
|
|
895
|
+
* nowhere else. The renderer imports no API of the library, thus the two ship apart.
|
|
896
|
+
*
|
|
897
|
+
* A page whose staged libraries were stripped registers no such global. That absence is a normal
|
|
898
|
+
* condition: the popover still opens, and it shows the pin as the last hop under an explicit note.
|
|
899
|
+
*/
|
|
900
|
+
export const TSPROV_GLOBAL = "tsprov";
|
|
901
|
+
/**
|
|
902
|
+
* The attribute names of the dialect that the rail reads.
|
|
903
|
+
*
|
|
904
|
+
* The writer of the document stamps these names, and the popover reads them and nothing else. A file
|
|
905
|
+
* entity carries the path and the hash. A command activity carries the command line, the argument
|
|
906
|
+
* vector, and the path of a script that bound to no bytes. A step activity carries the step id.
|
|
907
|
+
*
|
|
908
|
+
* A command activity carries no step id of its own. It names its step through the `wasInformedBy` edge
|
|
909
|
+
* that the writer stamps beside it, thus the rail reads the step from the activity at the end of that
|
|
910
|
+
* edge and never from the identifier of the command.
|
|
911
|
+
*/
|
|
912
|
+
const PIN_PATH_ATTRIBUTE = "inflexa:path";
|
|
913
|
+
const PIN_HASH_ATTRIBUTE = "inflexa:hash";
|
|
914
|
+
const COMMAND_ATTRIBUTE = "inflexa:command";
|
|
915
|
+
const COMMAND_ARGS_ATTRIBUTE = "inflexa:args";
|
|
916
|
+
const UNRESOLVED_SCRIPT_ATTRIBUTE = "inflexa:unresolvedScript";
|
|
917
|
+
const TOOL_ATTRIBUTE = "inflexa:tool";
|
|
918
|
+
const STEP_ID_ATTRIBUTE = "inflexa:stepId";
|
|
919
|
+
/**
|
|
920
|
+
* The dialect types that name an execution.
|
|
921
|
+
*
|
|
922
|
+
* A command runs a script, and a file tool writes content that an agent authored. Every other activity of
|
|
923
|
+
* the document is bookkeeping: a step, a run, and a lifecycle action. Thus the rail expands these two
|
|
924
|
+
* types alone, and no bookkeeping node becomes a row.
|
|
925
|
+
*/
|
|
926
|
+
const DIALECT_TYPE_PREFIX = "inflexa:";
|
|
927
|
+
const DIALECT_COMMAND_TYPE = "Command";
|
|
928
|
+
const DIALECT_FILE_TOOL_TYPE = "FileToolWrite";
|
|
929
|
+
/** The heading of the popover. It names what the panel holds, above the rail. */
|
|
930
|
+
const LINEAGE_TITLE_TEXT = "Lineage";
|
|
931
|
+
/** The note of a page that carries no lineage library. The chain then stops at the pin of the block. */
|
|
932
|
+
export const LINEAGE_NO_LIBRARY_NOTE = "The lineage library is not on this page. Thus the chain stops at this pin.";
|
|
933
|
+
/** The note of a library call that gave no answer. The pin stays the last hop that the page knows. */
|
|
934
|
+
export const LINEAGE_NO_ANSWER_NOTE = "The lineage library gave no answer for this pin. Thus the chain stops here.";
|
|
935
|
+
/** The note of a pin that the document holds no node for. */
|
|
936
|
+
export const LINEAGE_NO_NODE_NOTE = "The document holds no node for this pin. Thus the chain stops here.";
|
|
937
|
+
/** The note of a chain that the library cut at its own depth bound. */
|
|
938
|
+
export const LINEAGE_TRUNCATED_NOTE = "The library cut this chain at its depth bound. Thus the chain continues past the last hop.";
|
|
939
|
+
/** The note of an external record. A paper is no artifact, thus no data chain reaches it. */
|
|
940
|
+
export const LINEAGE_RECORD_NOTE = "An external record has no data chain. Thus this reference ends at the record.";
|
|
941
|
+
/**
|
|
942
|
+
* The footer note of a chain that reached the raw data.
|
|
943
|
+
*
|
|
944
|
+
* The signed form states the attestation, and the page carries one only where the source held one. Thus
|
|
945
|
+
* the footer never claims a signature that the page does not hold.
|
|
946
|
+
*/
|
|
947
|
+
export const LINEAGE_COMPLETE_NOTE = "The chain is complete · each hop is content-addressed";
|
|
948
|
+
export const LINEAGE_SIGNED_NOTE = "The chain is complete · each hop is content-addressed · an attestation covers it";
|
|
949
|
+
/**
|
|
950
|
+
* The tag of each row form.
|
|
951
|
+
*
|
|
952
|
+
* The pinned row takes a neutral tag and never the kind of its block. One marker of a claim can pin a
|
|
953
|
+
* table that sits in a different section, thus a tag such as "THIS TABLE" would name the wrong element of
|
|
954
|
+
* the page. "PINNED" names the one relation that always holds: the marker pins these bytes.
|
|
955
|
+
*
|
|
956
|
+
* A file takes the raw tag where the rail found no command that made it. That is what ends a branch, thus
|
|
957
|
+
* the tag and the terminal tint state the same fact.
|
|
958
|
+
*/
|
|
959
|
+
const LINEAGE_PIN_TAG = "PINNED";
|
|
960
|
+
const LINEAGE_ARTIFACT_TAG = "ARTIFACT";
|
|
961
|
+
const LINEAGE_RAW_TAG = "RAW INPUT";
|
|
962
|
+
const LINEAGE_RECORD_TAG = "CITATION";
|
|
963
|
+
/** The connector label above a producer row. */
|
|
964
|
+
const LINEAGE_MADE_BY_LABEL = "MADE BY";
|
|
965
|
+
/** The name of the close control for a reader who hears the page instead of seeing it. */
|
|
966
|
+
const LINEAGE_CLOSE_LABEL = "Close the lineage panel";
|
|
967
|
+
/** The glyph of the close control. */
|
|
968
|
+
const LINEAGE_CLOSE_GLYPH = "✕";
|
|
969
|
+
/**
|
|
970
|
+
* The classes of the popover. The script emits each one, and the design sheet holds the matching rule.
|
|
971
|
+
*
|
|
972
|
+
* The rail carries three row forms over one base row: the pinned artifact, a producer, and a raw input.
|
|
973
|
+
* A row that carries no modifier is an artifact that the rail continues past, thus the base rule is the
|
|
974
|
+
* form that the panel shows most.
|
|
975
|
+
*/
|
|
976
|
+
const LINEAGE_PANEL_CLASS = "report-lineage-popover";
|
|
977
|
+
const LINEAGE_HEADER_CLASS = "report-lineage-header";
|
|
978
|
+
const LINEAGE_PANEL_TITLE_CLASS = "report-lineage-title";
|
|
979
|
+
const LINEAGE_COUNT_CLASS = "report-lineage-count";
|
|
980
|
+
const LINEAGE_CLOSE_CLASS = "report-lineage-close";
|
|
981
|
+
const LINEAGE_BODY_CLASS = "report-lineage-body";
|
|
982
|
+
const LINEAGE_ROW_CLASS = "report-lineage-row";
|
|
983
|
+
const LINEAGE_ROW_PIN_CLASS = "report-lineage-row-pin";
|
|
984
|
+
const LINEAGE_ROW_PRODUCER_CLASS = "report-lineage-row-producer";
|
|
985
|
+
const LINEAGE_ROW_RAW_CLASS = "report-lineage-row-raw";
|
|
986
|
+
const LINEAGE_TAG_CLASS = "report-lineage-tag";
|
|
987
|
+
const LINEAGE_TAG_PIN_CLASS = "report-lineage-tag-pin";
|
|
988
|
+
const LINEAGE_TAG_RAW_CLASS = "report-lineage-tag-raw";
|
|
989
|
+
const LINEAGE_PATH_CLASS = "report-lineage-path";
|
|
990
|
+
const LINEAGE_DIR_CLASS = "report-lineage-dir";
|
|
991
|
+
const LINEAGE_HASH_CLASS = "report-lineage-hash";
|
|
992
|
+
const LINEAGE_PROMPT_CLASS = "report-lineage-prompt";
|
|
993
|
+
const LINEAGE_SCRIPT_CLASS = "report-lineage-script";
|
|
994
|
+
const LINEAGE_META_CLASS = "report-lineage-meta";
|
|
995
|
+
const LINEAGE_LINK_CLASS = "report-lineage-link";
|
|
996
|
+
const LINEAGE_RAIL_CLASS = "report-lineage-rail";
|
|
997
|
+
const LINEAGE_RAIL_PIN_CLASS = "report-lineage-rail-pin";
|
|
998
|
+
const LINEAGE_LINK_LABEL_CLASS = "report-lineage-link-label";
|
|
999
|
+
const LINEAGE_MORE_CLASS = "report-lineage-more";
|
|
1000
|
+
const LINEAGE_FOOTER_CLASS = "report-lineage-footer";
|
|
1001
|
+
const LINEAGE_CHECK_CLASS = "report-lineage-check";
|
|
1002
|
+
const LINEAGE_NOTE_CLASS = "report-lineage-note";
|
|
1003
|
+
/** The space between the control and the panel, and between the panel and the top and bottom edges, in pixels. */
|
|
1004
|
+
const LINEAGE_PANEL_GAP_PX = 8;
|
|
1005
|
+
/** The space between the panel and the left and right edges of the viewport, in pixels. */
|
|
1006
|
+
const LINEAGE_PANEL_MARGIN_PX = 12;
|
|
1007
|
+
/** The head of the run directory of the storage layout. A path under it carries the run id in its next segment. */
|
|
1008
|
+
const RUN_DIRECTORY_HEAD = "runs/";
|
|
1009
|
+
/**
|
|
1010
|
+
* The head of a path that the row shows in place of the segments that it cut.
|
|
1011
|
+
*
|
|
1012
|
+
* The cut takes whole segments off the front, thus the mark stands where a directory stood and the reader
|
|
1013
|
+
* sees that the row shows a tail. The hover then gives the whole path.
|
|
1014
|
+
*/
|
|
1015
|
+
const LINEAGE_CUT_MARK = "…";
|
|
1016
|
+
/**
|
|
1017
|
+
* The longest tail that reads as an extension, and the tail of a name that carries none, in characters.
|
|
1018
|
+
*
|
|
1019
|
+
* The extension states the kind of the file, thus it is the part of a cut name that must survive. A dot far
|
|
1020
|
+
* from the end belongs to the name itself, and a tail from that dot would leave no start. Thus a tail longer
|
|
1021
|
+
* than the bound falls back to a fixed count of characters, and the cut still reads from both ends.
|
|
1022
|
+
*/
|
|
1023
|
+
const LINEAGE_EXTENSION_MAX_CHARS = 8;
|
|
1024
|
+
const LINEAGE_NAME_TAIL_CHARS = 4;
|
|
1025
|
+
/**
|
|
1026
|
+
* The indent of one rail level, and the inset of a connector, in pixels.
|
|
1027
|
+
*
|
|
1028
|
+
* A row of one level sits at its own indent, and the connector above it sits between the two levels. Thus
|
|
1029
|
+
* the rail of a connector reads as the line that joins the row above to the rows below.
|
|
1030
|
+
*/
|
|
1031
|
+
const LINEAGE_INDENT_PX = 24;
|
|
1032
|
+
const LINEAGE_RAIL_INSET_PX = 14;
|
|
1033
|
+
/**
|
|
1034
|
+
* The page-side script of the lineage popover.
|
|
1035
|
+
*
|
|
1036
|
+
* A grounded block stamps its block id and its keys. A control beside a marker names the place of its key
|
|
1037
|
+
* in that stamp. Thus the script reads the markup for everything that it shows, and it holds no copy of the
|
|
1038
|
+
* document model.
|
|
1039
|
+
*
|
|
1040
|
+
* One delegated listener serves the whole page. It opens the panel of the clicked control, it closes the
|
|
1041
|
+
* open panel when a second control opens its own, and it closes on a click outside the panel and on the
|
|
1042
|
+
* `Escape` key. As a result at most one panel stands at one time, and the page needs no listener for each
|
|
1043
|
+
* control.
|
|
1044
|
+
*
|
|
1045
|
+
* The panel is a child of the body, positioned against the document. A card clips its own overflow, thus a
|
|
1046
|
+
* panel inside a card would show as a strip. The position reads the box of the control, and it holds the
|
|
1047
|
+
* panel inside the viewport on each side. The panel opens under the control, and it opens over the control
|
|
1048
|
+
* where the space under it is short. It never covers the control that opened it: where neither side holds
|
|
1049
|
+
* the whole panel, the body shrinks instead. A resize invalidates the measure, thus a resize places the
|
|
1050
|
+
* open panel again.
|
|
1051
|
+
*
|
|
1052
|
+
* The rail comes from the edges of the walk and never from its node set. A `generated` edge binds an
|
|
1053
|
+
* artifact to the command that made it, and a `used` edge binds that command to a file that it read. Each
|
|
1054
|
+
* input file continues the rail with its own producer, down to a file that no command made. Thus the panel
|
|
1055
|
+
* shows the chain and it shows nothing else: a sibling output collapses behind one count row, and a step,
|
|
1056
|
+
* a run, and an agent never become a row.
|
|
1057
|
+
*
|
|
1058
|
+
* The panel takes the width of its longest row, up to the cap of the sheet. Thus a normal window shows each
|
|
1059
|
+
* name whole, and the cut answers a window too narrow for the row. The fit measures against the width of the
|
|
1060
|
+
* laid-out panel, which the open pins before the first cut. Without the pin a cut would shrink the panel and
|
|
1061
|
+
* the next measure would read a narrower box, thus the rows would cut each other down.
|
|
1062
|
+
*
|
|
1063
|
+
* The hover of a row gives the whole path, and it answers for the run prefix and for a cut alike.
|
|
1064
|
+
*
|
|
1065
|
+
* The script builds each node and it writes each string as text. Thus a hostile path, a hostile command,
|
|
1066
|
+
* and a hostile step of the document reach the panel as text and never as markup.
|
|
1067
|
+
*
|
|
1068
|
+
* The page makes no request. The document rides the page already, and the library reads it in memory. The
|
|
1069
|
+
* graph builds one time, on the first click, thus a reader who opens no panel pays nothing for the parse.
|
|
1070
|
+
*/
|
|
1071
|
+
export const LINEAGE_POPOVER = `(function () {
|
|
1072
|
+
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
1073
|
+
var openPanel = null;
|
|
1074
|
+
var openControl = null;
|
|
1075
|
+
var graph = null;
|
|
1076
|
+
var graphFailed = false;
|
|
1077
|
+
function elementOf(node) {
|
|
1078
|
+
while (node && node.nodeType !== 1) {
|
|
1079
|
+
node = node.parentNode;
|
|
1080
|
+
}
|
|
1081
|
+
return node;
|
|
1082
|
+
}
|
|
1083
|
+
function controlOf(node) {
|
|
1084
|
+
var element = elementOf(node);
|
|
1085
|
+
while (element) {
|
|
1086
|
+
if (element.classList && element.classList.contains("${LINEAGE_CONTROL_CLASS}")) {
|
|
1087
|
+
return element;
|
|
1088
|
+
}
|
|
1089
|
+
element = element.parentElement;
|
|
1090
|
+
}
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
function stampOf(control) {
|
|
1094
|
+
var element = control.parentElement;
|
|
1095
|
+
while (element) {
|
|
1096
|
+
if (element.hasAttribute("${LINEAGE_BLOCK_ATTRIBUTE}")) {
|
|
1097
|
+
return element;
|
|
1098
|
+
}
|
|
1099
|
+
element = element.parentElement;
|
|
1100
|
+
}
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
function keyOf(control) {
|
|
1104
|
+
var stamp = stampOf(control);
|
|
1105
|
+
if (!stamp) {
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
var keys;
|
|
1109
|
+
try {
|
|
1110
|
+
keys = JSON.parse(stamp.getAttribute("${LINEAGE_KEYS_ATTRIBUTE}") || "[]");
|
|
1111
|
+
} catch (cause) {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
var place = parseInt(control.getAttribute("${LINEAGE_KEY_ATTRIBUTE}") || "", 10);
|
|
1115
|
+
if (!isFinite(place) || place < 0 || place >= keys.length) {
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
return keys[place];
|
|
1119
|
+
}
|
|
1120
|
+
function markerOf(control) {
|
|
1121
|
+
// The control sits inside the marker, beside the link that carries the shown number. Thus the header
|
|
1122
|
+
// names the number that the reader clicked, and no attribute of the markup repeats it.
|
|
1123
|
+
var marker = control.parentElement;
|
|
1124
|
+
var link = marker ? marker.querySelector("a") : null;
|
|
1125
|
+
return link ? String(link.textContent || "") : "";
|
|
1126
|
+
}
|
|
1127
|
+
function graphOf(library, text) {
|
|
1128
|
+
if (graph === null && !graphFailed) {
|
|
1129
|
+
try {
|
|
1130
|
+
// The read takes no format name. The writer of the document owns the format, thus the library
|
|
1131
|
+
// detects it and a document of another serialization still opens.
|
|
1132
|
+
graph = library.provToGraph(library.read(text));
|
|
1133
|
+
} catch (cause) {
|
|
1134
|
+
graphFailed = true;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return graph;
|
|
1138
|
+
}
|
|
1139
|
+
function attrText(record, name) {
|
|
1140
|
+
var values = record.getAttribute(name);
|
|
1141
|
+
if (!values || values.length === 0) {
|
|
1142
|
+
return "";
|
|
1143
|
+
}
|
|
1144
|
+
var value = values[0];
|
|
1145
|
+
// A typed value arrives inside a literal wrapper, and a plain string arrives bare.
|
|
1146
|
+
return String(value !== null && typeof value === "object" && "value" in value ? value.value : value);
|
|
1147
|
+
}
|
|
1148
|
+
function dialectType(record) {
|
|
1149
|
+
var types = record.getAssertedTypes();
|
|
1150
|
+
for (var i = 0; i < types.length; i++) {
|
|
1151
|
+
var text = String(types[i]);
|
|
1152
|
+
if (text.indexOf("${DIALECT_TYPE_PREFIX}") === 0) {
|
|
1153
|
+
return text.slice(${DIALECT_TYPE_PREFIX.length});
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return "";
|
|
1157
|
+
}
|
|
1158
|
+
function chainOf(library, built, walked, key) {
|
|
1159
|
+
var made = Object.create(null);
|
|
1160
|
+
var read = Object.create(null);
|
|
1161
|
+
var ranIn = Object.create(null);
|
|
1162
|
+
var cut = Object.create(null);
|
|
1163
|
+
for (var e = 0; e < walked.edges.length; e++) {
|
|
1164
|
+
var edge = walked.edges[e];
|
|
1165
|
+
// A generation edge runs from the artifact to its command, a usage edge from a command to a file
|
|
1166
|
+
// that it read, and a communication edge from a command to its step. The three classes carry the
|
|
1167
|
+
// whole rail, thus every other traversed edge stays out of it.
|
|
1168
|
+
if (edge.relation instanceof library.ProvGeneration) {
|
|
1169
|
+
made[edge.from] = edge.to;
|
|
1170
|
+
} else if (edge.relation instanceof library.ProvUsage) {
|
|
1171
|
+
if (read[edge.from] === undefined) {
|
|
1172
|
+
read[edge.from] = [];
|
|
1173
|
+
}
|
|
1174
|
+
read[edge.from].push(edge.to);
|
|
1175
|
+
} else if (edge.relation instanceof library.ProvCommunication) {
|
|
1176
|
+
ranIn[edge.from] = edge.to;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
for (var f = 0; f < walked.frontier.length; f++) {
|
|
1180
|
+
cut[walked.frontier[f].uri] = true;
|
|
1181
|
+
}
|
|
1182
|
+
function fileParts(uri) {
|
|
1183
|
+
var node = built.getNode(uri);
|
|
1184
|
+
if (!node) {
|
|
1185
|
+
return { path: "", hash: "" };
|
|
1186
|
+
}
|
|
1187
|
+
return { path: attrText(node.element, "${PIN_PATH_ATTRIBUTE}"), hash: attrText(node.element, "${PIN_HASH_ATTRIBUTE}") };
|
|
1188
|
+
}
|
|
1189
|
+
function commandOf(uri) {
|
|
1190
|
+
var node = built.getNode(uri);
|
|
1191
|
+
if (!node) {
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
var type = dialectType(node.element);
|
|
1195
|
+
if (type !== "${DIALECT_COMMAND_TYPE}" && type !== "${DIALECT_FILE_TOOL_TYPE}") {
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
var stepUri = ranIn[uri];
|
|
1199
|
+
var stepNode = stepUri === undefined ? undefined : built.getNode(stepUri);
|
|
1200
|
+
return {
|
|
1201
|
+
label: type === "${DIALECT_COMMAND_TYPE}" ? attrText(node.element, "${COMMAND_ATTRIBUTE}") : attrText(node.element, "${TOOL_ATTRIBUTE}"),
|
|
1202
|
+
args: attrText(node.element, "${COMMAND_ARGS_ATTRIBUTE}"),
|
|
1203
|
+
unresolved: attrText(node.element, "${UNRESOLVED_SCRIPT_ATTRIBUTE}"),
|
|
1204
|
+
step: stepNode ? attrText(stepNode.element, "${STEP_ID_ATTRIBUTE}") : ""
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function producerOf(uri) {
|
|
1208
|
+
// A file that a step generated names no execution, thus the rail treats it the same as a file that
|
|
1209
|
+
// the document never generated: the branch ends there.
|
|
1210
|
+
var commandUri = made[uri];
|
|
1211
|
+
if (commandUri === undefined || commandOf(commandUri) === null) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
return commandUri;
|
|
1215
|
+
}
|
|
1216
|
+
function scriptOf(command, inputs) {
|
|
1217
|
+
// The writer bound the script by an exact match of one path against the command line and its
|
|
1218
|
+
// arguments. The panel repeats that match, thus the row that names the script and the row that
|
|
1219
|
+
// names a data input never swap.
|
|
1220
|
+
var tokens = (command.label + " " + command.args).split(/\\s+/);
|
|
1221
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
1222
|
+
var parts = fileParts(inputs[i]);
|
|
1223
|
+
for (var t = 0; t < tokens.length; t++) {
|
|
1224
|
+
if (parts.path !== "" && tokens[t] === parts.path) {
|
|
1225
|
+
return { uri: inputs[i], path: parts.path, hash: parts.hash };
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
// A script that bound to no bytes rides the activity by its path alone. Thus the row still names it,
|
|
1230
|
+
// and it carries no hash because the document holds none.
|
|
1231
|
+
return command.unresolved === "" ? null : { uri: "", path: command.unresolved, hash: "" };
|
|
1232
|
+
}
|
|
1233
|
+
function otherOutputs(commandUri, onRail) {
|
|
1234
|
+
// The walk runs backward, thus it never traverses the generation edge of a sibling output. The graph
|
|
1235
|
+
// holds every one of them, and the count states what the rail leaves out.
|
|
1236
|
+
var into = built.inEdges(commandUri);
|
|
1237
|
+
var total = 0;
|
|
1238
|
+
for (var i = 0; i < into.length; i++) {
|
|
1239
|
+
if (into[i].relation instanceof library.ProvGeneration) {
|
|
1240
|
+
total += 1;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return total - onRail;
|
|
1244
|
+
}
|
|
1245
|
+
var rows = [{ form: "pin", level: 0, path: String(key.path), hash: String(key.hash) }];
|
|
1246
|
+
var seenFile = Object.create(null);
|
|
1247
|
+
var seenCommand = Object.create(null);
|
|
1248
|
+
var deepest = 0;
|
|
1249
|
+
function expand(uris, level) {
|
|
1250
|
+
var order = [];
|
|
1251
|
+
var onRail = Object.create(null);
|
|
1252
|
+
for (var u = 0; u < uris.length; u++) {
|
|
1253
|
+
var producerUri = producerOf(uris[u]);
|
|
1254
|
+
if (producerUri === null) {
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (onRail[producerUri] === undefined) {
|
|
1258
|
+
onRail[producerUri] = 0;
|
|
1259
|
+
order.push(producerUri);
|
|
1260
|
+
}
|
|
1261
|
+
onRail[producerUri] += 1;
|
|
1262
|
+
}
|
|
1263
|
+
for (var c = 0; c < order.length; c++) {
|
|
1264
|
+
var commandUri = order[c];
|
|
1265
|
+
// The visited set bounds a document whose edges lead back to a command that the rail already
|
|
1266
|
+
// showed. Without it such a document would build rows forever.
|
|
1267
|
+
if (seenCommand[commandUri]) {
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
seenCommand[commandUri] = true;
|
|
1271
|
+
var command = commandOf(commandUri);
|
|
1272
|
+
var inputs = read[commandUri] === undefined ? [] : read[commandUri];
|
|
1273
|
+
var script = scriptOf(command, inputs);
|
|
1274
|
+
rows.push({ form: "link", level: level, label: ${JSON.stringify(LINEAGE_MADE_BY_LABEL)}, pin: level === 0 });
|
|
1275
|
+
rows.push({
|
|
1276
|
+
form: "producer",
|
|
1277
|
+
level: level,
|
|
1278
|
+
name: baseName(script === null ? command.label : script.path),
|
|
1279
|
+
hash: script === null ? "" : script.hash,
|
|
1280
|
+
command: script === null ? "" : command.label,
|
|
1281
|
+
step: command.step
|
|
1282
|
+
});
|
|
1283
|
+
var next = [];
|
|
1284
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
1285
|
+
if (script !== null && inputs[i] === script.uri) {
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
if (seenFile[inputs[i]]) {
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
seenFile[inputs[i]] = true;
|
|
1292
|
+
next.push(inputs[i]);
|
|
1293
|
+
}
|
|
1294
|
+
if (next.length > 0) {
|
|
1295
|
+
rows.push({ form: "link", level: level, label: readLabel(next.length), pin: false });
|
|
1296
|
+
deepest = level + 1 > deepest ? level + 1 : deepest;
|
|
1297
|
+
}
|
|
1298
|
+
for (var n = 0; n < next.length; n++) {
|
|
1299
|
+
var parts = fileParts(next[n]);
|
|
1300
|
+
rows.push({
|
|
1301
|
+
form: "file",
|
|
1302
|
+
level: level + 1,
|
|
1303
|
+
path: parts.path,
|
|
1304
|
+
hash: parts.hash,
|
|
1305
|
+
raw: producerOf(next[n]) === null && cut[next[n]] !== true
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
var others = otherOutputs(commandUri, onRail[commandUri]);
|
|
1309
|
+
if (others > 0) {
|
|
1310
|
+
rows.push({ form: "more", level: level + 1, count: others, step: command.step });
|
|
1311
|
+
}
|
|
1312
|
+
expand(next, level + 1);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
var root = walked.roots.length > 0 ? walked.roots[0] : null;
|
|
1316
|
+
if (root === null) {
|
|
1317
|
+
return { rows: [], note: ${JSON.stringify(LINEAGE_NO_NODE_NOTE)}, hops: 1 };
|
|
1318
|
+
}
|
|
1319
|
+
seenFile[root] = true;
|
|
1320
|
+
expand([root], 0);
|
|
1321
|
+
return { rows: rows, note: walked.frontier.length > 0 ? ${JSON.stringify(LINEAGE_TRUNCATED_NOTE)} : "", hops: deepest + 1 };
|
|
1322
|
+
}
|
|
1323
|
+
function walk(key) {
|
|
1324
|
+
var library = window.${TSPROV_GLOBAL};
|
|
1325
|
+
var carrier = window.${REPORT_PROVENANCE_GLOBAL};
|
|
1326
|
+
if (!library || typeof library.lineage !== "function" || !carrier || typeof carrier.document !== "string") {
|
|
1327
|
+
return { rows: [], note: ${JSON.stringify(LINEAGE_NO_LIBRARY_NOTE)}, hops: 1 };
|
|
1328
|
+
}
|
|
1329
|
+
var built = graphOf(library, carrier.document);
|
|
1330
|
+
if (!built) {
|
|
1331
|
+
// A document that the library refuses is exactly the fault that a look must diagnose, thus it must
|
|
1332
|
+
// never leave the reader with a control that does nothing.
|
|
1333
|
+
return { rows: [], note: ${JSON.stringify(LINEAGE_NO_ANSWER_NOTE)}, hops: 1 };
|
|
1334
|
+
}
|
|
1335
|
+
var walked = null;
|
|
1336
|
+
var found = null;
|
|
1337
|
+
try {
|
|
1338
|
+
found = library.resolveUnique(built, {
|
|
1339
|
+
type: library.ProvEntity,
|
|
1340
|
+
attributes: [
|
|
1341
|
+
{ name: "${PIN_PATH_ATTRIBUTE}", equals: key.path },
|
|
1342
|
+
{ name: "${PIN_HASH_ATTRIBUTE}", equals: key.hash }
|
|
1343
|
+
]
|
|
1344
|
+
});
|
|
1345
|
+
if (found.kind === "resolved") {
|
|
1346
|
+
walked = library.lineage(built, found.record, { direction: "backward", relations: "dataflow" });
|
|
1347
|
+
}
|
|
1348
|
+
} catch (cause) {
|
|
1349
|
+
return { rows: [], note: ${JSON.stringify(LINEAGE_NO_ANSWER_NOTE)}, hops: 1 };
|
|
1350
|
+
}
|
|
1351
|
+
if (walked === null) {
|
|
1352
|
+
// Two nodes of one pin answer no better than none: the page cannot state which chain it shows.
|
|
1353
|
+
return { rows: [], note: found.kind === "ambiguous" ? ${JSON.stringify(LINEAGE_NO_ANSWER_NOTE)} : ${JSON.stringify(LINEAGE_NO_NODE_NOTE)}, hops: 1 };
|
|
1354
|
+
}
|
|
1355
|
+
return chainOf(library, built, walked, key);
|
|
1356
|
+
}
|
|
1357
|
+
function part(tag, className, text) {
|
|
1358
|
+
var node = document.createElement(tag);
|
|
1359
|
+
node.className = className;
|
|
1360
|
+
node.textContent = text;
|
|
1361
|
+
return node;
|
|
1362
|
+
}
|
|
1363
|
+
function boxed(tag, base, extra) {
|
|
1364
|
+
var node = document.createElement(tag);
|
|
1365
|
+
node.className = extra === "" ? base : base + " " + extra;
|
|
1366
|
+
return node;
|
|
1367
|
+
}
|
|
1368
|
+
function glyph(className, size, width, paths) {
|
|
1369
|
+
// A stroke drawing takes the current color, thus one rule of the sheet colors the glyph and its row.
|
|
1370
|
+
var svg = document.createElementNS(SVG_NAMESPACE, "svg");
|
|
1371
|
+
svg.setAttribute("class", className);
|
|
1372
|
+
svg.setAttribute("width", size);
|
|
1373
|
+
svg.setAttribute("height", size);
|
|
1374
|
+
svg.setAttribute("viewBox", "0 0 16 16");
|
|
1375
|
+
svg.setAttribute("fill", "none");
|
|
1376
|
+
svg.setAttribute("stroke", "currentColor");
|
|
1377
|
+
svg.setAttribute("stroke-width", width);
|
|
1378
|
+
svg.setAttribute("aria-hidden", "true");
|
|
1379
|
+
for (var i = 0; i < paths.length; i++) {
|
|
1380
|
+
var path = document.createElementNS(SVG_NAMESPACE, "path");
|
|
1381
|
+
path.setAttribute("d", paths[i]);
|
|
1382
|
+
svg.appendChild(path);
|
|
1383
|
+
}
|
|
1384
|
+
return svg;
|
|
1385
|
+
}
|
|
1386
|
+
function hashHead(hash) {
|
|
1387
|
+
return hash.slice(hash.lastIndexOf(":") + 1).slice(0, ${CHAIN_HASH_CHARS});
|
|
1388
|
+
}
|
|
1389
|
+
function baseName(path) {
|
|
1390
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
1391
|
+
}
|
|
1392
|
+
function readLabel(count) {
|
|
1393
|
+
return count === 1 ? "READ 1 FILE" : "READ " + count + " FILES";
|
|
1394
|
+
}
|
|
1395
|
+
function moreText(count, step) {
|
|
1396
|
+
var files = count === 1 ? "1 other file" : count + " other files";
|
|
1397
|
+
var origin = step === "" ? "the same command" : "Step " + step;
|
|
1398
|
+
return "▸ " + files + " came from " + origin + " — not on this chain";
|
|
1399
|
+
}
|
|
1400
|
+
function metaText(row) {
|
|
1401
|
+
var parts = [];
|
|
1402
|
+
if (row.hash !== "") {
|
|
1403
|
+
parts.push(hashHead(row.hash));
|
|
1404
|
+
}
|
|
1405
|
+
if (row.command !== "") {
|
|
1406
|
+
parts.push(row.command);
|
|
1407
|
+
}
|
|
1408
|
+
if (row.step !== "") {
|
|
1409
|
+
parts.push("Step " + row.step);
|
|
1410
|
+
}
|
|
1411
|
+
return parts.join(" · ");
|
|
1412
|
+
}
|
|
1413
|
+
function countText(marker, hops, complete) {
|
|
1414
|
+
var text = (hops === 1 ? "1 hop" : hops + " hops") + (complete ? " to the raw data" : "");
|
|
1415
|
+
return marker === "" ? text : marker + " · " + text;
|
|
1416
|
+
}
|
|
1417
|
+
function completeNote() {
|
|
1418
|
+
var carrier = window.${REPORT_PROVENANCE_GLOBAL};
|
|
1419
|
+
var signed = carrier && typeof carrier.attestation === "string";
|
|
1420
|
+
return signed ? ${JSON.stringify(LINEAGE_SIGNED_NOTE)} : ${JSON.stringify(LINEAGE_COMPLETE_NOTE)};
|
|
1421
|
+
}
|
|
1422
|
+
function runPrefix(path) {
|
|
1423
|
+
// Every hop of one chain sits under the run directory of the pinned artifact. The head repeats on each
|
|
1424
|
+
// row and the tail holds the meaning, thus the rows drop the head. A path outside the run, for example
|
|
1425
|
+
// a raw input under the data directory, matches no prefix and keeps its whole form.
|
|
1426
|
+
if (path.indexOf("${RUN_DIRECTORY_HEAD}") !== 0) {
|
|
1427
|
+
return "";
|
|
1428
|
+
}
|
|
1429
|
+
var cut = path.indexOf("/", ${RUN_DIRECTORY_HEAD.length});
|
|
1430
|
+
return cut < 0 ? "" : path.slice(0, cut + 1);
|
|
1431
|
+
}
|
|
1432
|
+
function shownPath(path, prefix) {
|
|
1433
|
+
return prefix !== "" && path.indexOf(prefix) === 0 ? path.slice(prefix.length) : path;
|
|
1434
|
+
}
|
|
1435
|
+
function writePath(node, shown) {
|
|
1436
|
+
// The directory prefix dims, thus the file name reads first and the whole tail stays on the row. The fit
|
|
1437
|
+
// writes the same node again with fewer segments, thus the write clears what stands there.
|
|
1438
|
+
while (node.firstChild) {
|
|
1439
|
+
node.removeChild(node.firstChild);
|
|
1440
|
+
}
|
|
1441
|
+
var cut = shown.lastIndexOf("/");
|
|
1442
|
+
if (cut >= 0) {
|
|
1443
|
+
node.appendChild(part("span", "${LINEAGE_DIR_CLASS}", shown.slice(0, cut + 1)));
|
|
1444
|
+
}
|
|
1445
|
+
node.appendChild(document.createTextNode(shown.slice(cut + 1)));
|
|
1446
|
+
}
|
|
1447
|
+
function pathNode(shown, full) {
|
|
1448
|
+
var node = document.createElement("span");
|
|
1449
|
+
node.className = "${LINEAGE_PATH_CLASS}";
|
|
1450
|
+
// The title carries the whole path on each row, cut or not. Thus the hover answers for the run prefix
|
|
1451
|
+
// that the row drops and for a segment that the fit cuts.
|
|
1452
|
+
node.setAttribute("title", full);
|
|
1453
|
+
writePath(node, shown);
|
|
1454
|
+
return node;
|
|
1455
|
+
}
|
|
1456
|
+
function nameTail(name) {
|
|
1457
|
+
// The extension states the kind of the file, thus the tail starts at the last dot. A dot far from the
|
|
1458
|
+
// end belongs to the name, and a tail from there would leave no start, thus the fixed count answers.
|
|
1459
|
+
var dot = name.lastIndexOf(".");
|
|
1460
|
+
var extension = dot > 0 ? name.length - dot : 0;
|
|
1461
|
+
return name.slice(name.length - (extension > 0 && extension <= ${LINEAGE_EXTENSION_MAX_CHARS} ? extension : ${LINEAGE_NAME_TAIL_CHARS}));
|
|
1462
|
+
}
|
|
1463
|
+
function fitName(node, head, name) {
|
|
1464
|
+
// One name that overflows the row on its own has no segment left to cut. The start names the file and
|
|
1465
|
+
// the tail carries the extension, thus the mark stands between them and both ends stay on the row. Two
|
|
1466
|
+
// siblings that differ in their extension alone then read apart.
|
|
1467
|
+
var tail = nameTail(name);
|
|
1468
|
+
var keep = name.length - tail.length;
|
|
1469
|
+
while (keep > 1 && node.scrollWidth > node.clientWidth) {
|
|
1470
|
+
keep -= 1;
|
|
1471
|
+
writePath(node, head + name.slice(0, keep) + ${JSON.stringify(LINEAGE_CUT_MARK)} + tail);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
function fitPath(node) {
|
|
1475
|
+
// A row holds one line. A cut at the end would take the file name, which is the part that names the
|
|
1476
|
+
// file, thus the cut takes whole segments off the front and the end stays. The measure needs a laid-out
|
|
1477
|
+
// row, thus the fit runs after the panel joins the document.
|
|
1478
|
+
var parts = String(node.textContent || "").split("/");
|
|
1479
|
+
var whole = parts.length;
|
|
1480
|
+
while (parts.length > 1 && node.scrollWidth > node.clientWidth) {
|
|
1481
|
+
parts.shift();
|
|
1482
|
+
writePath(node, ${JSON.stringify(`${LINEAGE_CUT_MARK}/`)} + parts.join("/"));
|
|
1483
|
+
}
|
|
1484
|
+
if (parts.length === 1) {
|
|
1485
|
+
// The segments are gone and the row can still overflow. The mark of the segment cut stands where the
|
|
1486
|
+
// directories stood, thus the name cut writes it again in front of the name that it cuts.
|
|
1487
|
+
fitName(node, whole > 1 ? ${JSON.stringify(`${LINEAGE_CUT_MARK}/`)} : "", parts[0]);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function fitPaths(panel) {
|
|
1491
|
+
var nodes = panel.querySelectorAll(".${LINEAGE_PATH_CLASS}");
|
|
1492
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
1493
|
+
fitPath(nodes[i]);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
function fileRow(row, prefix) {
|
|
1497
|
+
var kind = row.form === "pin" ? "${LINEAGE_ROW_PIN_CLASS}" : row.raw ? "${LINEAGE_ROW_RAW_CLASS}" : "";
|
|
1498
|
+
var node = boxed("div", "${LINEAGE_ROW_CLASS}", kind);
|
|
1499
|
+
node.style.marginLeft = row.level * ${LINEAGE_INDENT_PX} + "px";
|
|
1500
|
+
var tag = row.form === "pin" ? "${LINEAGE_TAG_PIN_CLASS}" : row.raw ? "${LINEAGE_TAG_RAW_CLASS}" : "";
|
|
1501
|
+
var text = row.form === "record" ? ${JSON.stringify(LINEAGE_RECORD_TAG)} : row.form === "pin" ? ${JSON.stringify(LINEAGE_PIN_TAG)} : row.raw ? ${JSON.stringify(LINEAGE_RAW_TAG)} : ${JSON.stringify(LINEAGE_ARTIFACT_TAG)};
|
|
1502
|
+
node.appendChild(part("span", tag === "" ? "${LINEAGE_TAG_CLASS}" : "${LINEAGE_TAG_CLASS}" + " " + tag, text));
|
|
1503
|
+
node.appendChild(pathNode(shownPath(row.path, prefix), row.path));
|
|
1504
|
+
if (row.hash !== "") {
|
|
1505
|
+
node.appendChild(part("code", "${LINEAGE_HASH_CLASS}", hashHead(row.hash)));
|
|
1506
|
+
}
|
|
1507
|
+
return node;
|
|
1508
|
+
}
|
|
1509
|
+
function rowNode(row, prefix) {
|
|
1510
|
+
if (row.form === "link") {
|
|
1511
|
+
var link = boxed("div", "${LINEAGE_LINK_CLASS}", "");
|
|
1512
|
+
link.style.paddingLeft = ${LINEAGE_RAIL_INSET_PX} + row.level * ${LINEAGE_INDENT_PX} + "px";
|
|
1513
|
+
link.appendChild(boxed("div", "${LINEAGE_RAIL_CLASS}", row.pin ? "${LINEAGE_RAIL_PIN_CLASS}" : ""));
|
|
1514
|
+
link.appendChild(part("span", "${LINEAGE_LINK_LABEL_CLASS}", row.label));
|
|
1515
|
+
return link;
|
|
1516
|
+
}
|
|
1517
|
+
if (row.form === "more") {
|
|
1518
|
+
var more = part("div", "${LINEAGE_MORE_CLASS}", moreText(row.count, row.step));
|
|
1519
|
+
more.style.marginLeft = row.level * ${LINEAGE_INDENT_PX} + "px";
|
|
1520
|
+
return more;
|
|
1521
|
+
}
|
|
1522
|
+
if (row.form === "producer") {
|
|
1523
|
+
var producer = boxed("div", "${LINEAGE_ROW_CLASS}", "${LINEAGE_ROW_PRODUCER_CLASS}");
|
|
1524
|
+
producer.style.marginLeft = row.level * ${LINEAGE_INDENT_PX} + "px";
|
|
1525
|
+
producer.appendChild(glyph("${LINEAGE_PROMPT_CLASS}", "14", "1.5", ["M4 3l4 5-4 5", "M9 13h4"]));
|
|
1526
|
+
producer.appendChild(part("span", "${LINEAGE_SCRIPT_CLASS}", row.name));
|
|
1527
|
+
producer.appendChild(part("span", "${LINEAGE_META_CLASS}", metaText(row)));
|
|
1528
|
+
return producer;
|
|
1529
|
+
}
|
|
1530
|
+
return fileRow(row, prefix);
|
|
1531
|
+
}
|
|
1532
|
+
function recordName(key) {
|
|
1533
|
+
return String(key.idKind) + ":" + String(key.id);
|
|
1534
|
+
}
|
|
1535
|
+
function panelOf(key, marker) {
|
|
1536
|
+
var pinned = typeof key.path === "string";
|
|
1537
|
+
var walked = pinned ? walk(key) : { rows: [], note: ${JSON.stringify(LINEAGE_RECORD_NOTE)}, hops: 1 };
|
|
1538
|
+
var rows = walked.rows;
|
|
1539
|
+
if (!pinned) {
|
|
1540
|
+
rows = [{ form: "record", level: 0, path: recordName(key), hash: "" }];
|
|
1541
|
+
} else if (rows.length === 0) {
|
|
1542
|
+
// The pin is the last hop that the page knows on its own. A walk that gave nothing still names it,
|
|
1543
|
+
// thus the reader sees what the block binds and reads the note against it.
|
|
1544
|
+
rows = [{ form: "pin", level: 0, path: String(key.path), hash: String(key.hash) }];
|
|
1545
|
+
}
|
|
1546
|
+
var panel = document.createElement("div");
|
|
1547
|
+
panel.className = "${LINEAGE_PANEL_CLASS}";
|
|
1548
|
+
panel.setAttribute("role", "dialog");
|
|
1549
|
+
panel.setAttribute("aria-label", pinned ? String(key.path) : recordName(key));
|
|
1550
|
+
var header = boxed("div", "${LINEAGE_HEADER_CLASS}", "");
|
|
1551
|
+
header.appendChild(part("span", "${LINEAGE_PANEL_TITLE_CLASS}", ${JSON.stringify(LINEAGE_TITLE_TEXT)}));
|
|
1552
|
+
header.appendChild(part("span", "${LINEAGE_COUNT_CLASS}", countText(marker, walked.hops, walked.note === "")));
|
|
1553
|
+
var closer = part("button", "${LINEAGE_CLOSE_CLASS}", ${JSON.stringify(LINEAGE_CLOSE_GLYPH)});
|
|
1554
|
+
closer.setAttribute("type", "button");
|
|
1555
|
+
closer.setAttribute("aria-label", ${JSON.stringify(LINEAGE_CLOSE_LABEL)});
|
|
1556
|
+
closer.addEventListener("click", close);
|
|
1557
|
+
header.appendChild(closer);
|
|
1558
|
+
panel.appendChild(header);
|
|
1559
|
+
var body = boxed("div", "${LINEAGE_BODY_CLASS}", "");
|
|
1560
|
+
// The pinned artifact names the run of the whole chain. Thus one prefix serves each row, and a chain
|
|
1561
|
+
// that the pin does not root under a run keeps each path whole.
|
|
1562
|
+
var prefix = pinned ? runPrefix(String(key.path)) : "";
|
|
1563
|
+
for (var i = 0; i < rows.length; i++) {
|
|
1564
|
+
body.appendChild(rowNode(rows[i], prefix));
|
|
1565
|
+
}
|
|
1566
|
+
panel.appendChild(body);
|
|
1567
|
+
var footer = boxed("div", "${LINEAGE_FOOTER_CLASS}", "");
|
|
1568
|
+
if (walked.note === "") {
|
|
1569
|
+
footer.appendChild(glyph("${LINEAGE_CHECK_CLASS}", "12", "1.8", ["M2 8l4 4 8-9"]));
|
|
1570
|
+
}
|
|
1571
|
+
footer.appendChild(part("span", "${LINEAGE_NOTE_CLASS}", walked.note === "" ? completeNote() : walked.note));
|
|
1572
|
+
panel.appendChild(footer);
|
|
1573
|
+
return panel;
|
|
1574
|
+
}
|
|
1575
|
+
function place(panel, control) {
|
|
1576
|
+
var box = control.getBoundingClientRect();
|
|
1577
|
+
var root = document.documentElement;
|
|
1578
|
+
var body = panel.querySelector(".${LINEAGE_BODY_CLASS}");
|
|
1579
|
+
// A prior placement can hold a cap of its own. The reset measures the natural height again, thus a
|
|
1580
|
+
// resize into a taller window never keeps a cap that the new viewport does not need.
|
|
1581
|
+
body.style.maxHeight = "";
|
|
1582
|
+
var left = box.left;
|
|
1583
|
+
var last = root.clientWidth - panel.offsetWidth - ${LINEAGE_PANEL_MARGIN_PX};
|
|
1584
|
+
if (left > last) {
|
|
1585
|
+
left = last;
|
|
1586
|
+
}
|
|
1587
|
+
if (left < ${LINEAGE_PANEL_MARGIN_PX}) {
|
|
1588
|
+
left = ${LINEAGE_PANEL_MARGIN_PX};
|
|
1589
|
+
}
|
|
1590
|
+
// The panel opens under the control, because a reader reads down from what was clicked. It opens over
|
|
1591
|
+
// the control where the space under it is short and the space over it is larger.
|
|
1592
|
+
var below = root.clientHeight - box.bottom - ${LINEAGE_PANEL_GAP_PX} * 2;
|
|
1593
|
+
var above = box.top - ${LINEAGE_PANEL_GAP_PX} * 2;
|
|
1594
|
+
var over = panel.offsetHeight > below && above > below;
|
|
1595
|
+
var room = over ? above : below;
|
|
1596
|
+
if (panel.offsetHeight > room) {
|
|
1597
|
+
// Neither side holds the whole panel. The body scrolls already, thus the cap shrinks the body to the
|
|
1598
|
+
// larger side. An overlap would hide the control that the reader clicked, and the shrink prevents it.
|
|
1599
|
+
var chrome = panel.offsetHeight - body.offsetHeight;
|
|
1600
|
+
body.style.maxHeight = (room - chrome > 0 ? room - chrome : 0) + "px";
|
|
1601
|
+
}
|
|
1602
|
+
var top = over ? box.top - panel.offsetHeight - ${LINEAGE_PANEL_GAP_PX} : box.bottom + ${LINEAGE_PANEL_GAP_PX};
|
|
1603
|
+
panel.style.left = left + window.pageXOffset + "px";
|
|
1604
|
+
panel.style.top = top + window.pageYOffset + "px";
|
|
1605
|
+
}
|
|
1606
|
+
function close() {
|
|
1607
|
+
if (openPanel && openPanel.parentNode) {
|
|
1608
|
+
openPanel.parentNode.removeChild(openPanel);
|
|
1609
|
+
}
|
|
1610
|
+
if (openControl) {
|
|
1611
|
+
openControl.setAttribute("aria-expanded", "false");
|
|
1612
|
+
}
|
|
1613
|
+
openPanel = null;
|
|
1614
|
+
openControl = null;
|
|
1615
|
+
}
|
|
1616
|
+
function openFor(control) {
|
|
1617
|
+
var key = keyOf(control);
|
|
1618
|
+
if (!key) {
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
var panel = panelOf(key, markerOf(control));
|
|
1622
|
+
document.body.appendChild(panel);
|
|
1623
|
+
// The sheet sizes the panel to its longest row, up to a cap. A cut shortens that row, thus the panel
|
|
1624
|
+
// would shrink under the fit and each row would then measure against a narrower box. The pin holds the
|
|
1625
|
+
// resolved width of the laid-out panel, and the cap of the sheet still bounds it in a narrow window.
|
|
1626
|
+
panel.style.width = panel.getBoundingClientRect().width + "px";
|
|
1627
|
+
// The fit measures a row against its width, thus it runs on the panel that stands in the document. It
|
|
1628
|
+
// runs before the place, because a cut row can change the height that the place reads.
|
|
1629
|
+
fitPaths(panel);
|
|
1630
|
+
place(panel, control);
|
|
1631
|
+
control.setAttribute("aria-expanded", "true");
|
|
1632
|
+
openPanel = panel;
|
|
1633
|
+
openControl = control;
|
|
1634
|
+
}
|
|
1635
|
+
function insidePanel(node) {
|
|
1636
|
+
var element = elementOf(node);
|
|
1637
|
+
while (element) {
|
|
1638
|
+
if (element === openPanel) {
|
|
1639
|
+
return true;
|
|
1640
|
+
}
|
|
1641
|
+
element = element.parentElement;
|
|
1642
|
+
}
|
|
1643
|
+
return false;
|
|
1644
|
+
}
|
|
1645
|
+
document.addEventListener("click", function (event) {
|
|
1646
|
+
var control = controlOf(event.target);
|
|
1647
|
+
if (control) {
|
|
1648
|
+
var same = control === openControl;
|
|
1649
|
+
close();
|
|
1650
|
+
if (!same) {
|
|
1651
|
+
openFor(control);
|
|
1652
|
+
}
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
if (!insidePanel(event.target)) {
|
|
1656
|
+
close();
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
document.addEventListener("keydown", function (event) {
|
|
1660
|
+
if (event.key === "Escape") {
|
|
1661
|
+
close();
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
window.addEventListener("resize", function () {
|
|
1665
|
+
if (openPanel !== null && openControl !== null) {
|
|
1666
|
+
// The place reads the box of the control, and a resize moves that box. Thus the panel follows the
|
|
1667
|
+
// control instead of standing where the control was.
|
|
1668
|
+
place(openPanel, openControl);
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1671
|
+
})();`;
|