gtfs-garage-web 0.0.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.
- package/README.md +86 -0
- package/dist/dataset.d.ts +73 -0
- package/dist/dataset.js +64 -0
- package/dist/dom.d.ts +24 -0
- package/dist/gtfs-garage-web.css +1 -0
- package/dist/map/basemap.d.ts +23 -0
- package/dist/map/geo.d.ts +4 -0
- package/dist/map/layers.d.ts +28 -0
- package/dist/map/map.d.ts +60 -0
- package/dist/map/redraw.d.ts +29 -0
- package/dist/map-part.d.ts +10 -0
- package/dist/map.js +334 -0
- package/dist/markup.d.ts +24 -0
- package/dist/mount-Bpr3Nw4_.js +1064 -0
- package/dist/mount-dataset.d.ts +29 -0
- package/dist/mount.d.ts +79 -0
- package/dist/mount.js +7 -0
- package/dist/parquet.js +263 -0
- package/dist/react.d.ts +31 -0
- package/dist/react.js +36 -0
- package/dist/schema.d.ts +41 -0
- package/dist/sources/fixtures.d.ts +20 -0
- package/dist/sources/ndjson.d.ts +36 -0
- package/dist/sources/parquet.d.ts +85 -0
- package/dist/sources/rest.d.ts +35 -0
- package/dist/sources/types.d.ts +252 -0
- package/dist/state.d.ts +35 -0
- package/dist/ui/feed-loader.d.ts +35 -0
- package/dist/ui/feed-metrics.d.ts +47 -0
- package/dist/ui/filters.d.ts +18 -0
- package/dist/ui/history.d.ts +48 -0
- package/dist/ui/load-progress.d.ts +7 -0
- package/dist/ui/navigation.d.ts +60 -0
- package/dist/ui/sidebar.d.ts +21 -0
- package/dist/ui/table.d.ts +97 -0
- package/dist/ui/values.d.ts +61 -0
- package/package.json +94 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The row grid: the part that makes IDs navigable.
|
|
3
|
+
*
|
|
4
|
+
* A foreign-key column renders as a link to the referenced table, filtered to
|
|
5
|
+
* the matching row. A table's own id column additionally offers "related"
|
|
6
|
+
* buttons for the reverse direction (a route to its trips, say). The server
|
|
7
|
+
* only reports links whose target exists in the loaded feed, so nothing here
|
|
8
|
+
* leads to a file the feed does not contain.
|
|
9
|
+
*/
|
|
10
|
+
import { type Dom } from "../dom";
|
|
11
|
+
import type { Filter, PageResponse, RelatedLink } from "../sources/types";
|
|
12
|
+
import type { AppState } from "../state";
|
|
13
|
+
export interface RelatedButton {
|
|
14
|
+
link: RelatedLink;
|
|
15
|
+
label: string;
|
|
16
|
+
title: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* How to label the reverse links under an id.
|
|
20
|
+
*
|
|
21
|
+
* A table can point at the same id from more than one column - `transfers` and
|
|
22
|
+
* `pathways` each reference a stop as both `from_stop_id` and `to_stop_id` - so
|
|
23
|
+
* labelling by table name alone renders two buttons that look identical and go
|
|
24
|
+
* to different places. The column is added only where a table appears more than
|
|
25
|
+
* once, which keeps the common label short, and the tooltip always names it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function describeRelated(related: RelatedLink[]): RelatedButton[];
|
|
28
|
+
/**
|
|
29
|
+
* Showing a row on the map.
|
|
30
|
+
*
|
|
31
|
+
* Every one is optional, and absent means the viewer has no map: a host that
|
|
32
|
+
* mounts without the map part gets no map buttons rather than buttons that do
|
|
33
|
+
* nothing. Granular rather than one flag because they already differ - a route
|
|
34
|
+
* is only highlightable once its shapes are known.
|
|
35
|
+
*/
|
|
36
|
+
export interface HighlightCallbacks {
|
|
37
|
+
onHighlightStop: (stopId: string) => void;
|
|
38
|
+
onHighlightShape: (shapeId: string) => void;
|
|
39
|
+
onHighlightRoute: (routeId: string) => void;
|
|
40
|
+
onHighlightLocation: (locationId: string) => void;
|
|
41
|
+
}
|
|
42
|
+
export interface TableCallbacks extends Partial<HighlightCallbacks> {
|
|
43
|
+
onNavigate: (table: string, filters: Filter[]) => void;
|
|
44
|
+
onPage: (page: number) => void;
|
|
45
|
+
}
|
|
46
|
+
export interface RowAction {
|
|
47
|
+
label: string;
|
|
48
|
+
title: string;
|
|
49
|
+
run: () => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Which map buttons a row should carry.
|
|
53
|
+
*
|
|
54
|
+
* Pure, so the rules are testable without a document - the same split the rest
|
|
55
|
+
* of this package uses, where `values.ts` decides and `table.ts` draws. A row
|
|
56
|
+
* offers an action only when it has the id for it *and* something can act on
|
|
57
|
+
* it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function rowActions(values: Record<string, string | null>, context: {
|
|
60
|
+
table: string;
|
|
61
|
+
hasRouteShapes: boolean;
|
|
62
|
+
}, callbacks: Partial<HighlightCallbacks>): RowAction[];
|
|
63
|
+
export declare class TableView {
|
|
64
|
+
private readonly dom;
|
|
65
|
+
private readonly state;
|
|
66
|
+
private readonly callbacks;
|
|
67
|
+
/**
|
|
68
|
+
* The page currently on screen, so that a display change can redraw without
|
|
69
|
+
* asking the server again - on a large feed a refetch to toggle formatting
|
|
70
|
+
* would be a needless round trip over millions of rows.
|
|
71
|
+
*/
|
|
72
|
+
private lastPage;
|
|
73
|
+
constructor(dom: Dom, state: AppState, callbacks: TableCallbacks);
|
|
74
|
+
/** Draw the current page again, picking up a changed display preference. */
|
|
75
|
+
redraw(): void;
|
|
76
|
+
showMessage(text: string): void;
|
|
77
|
+
render(page: PageResponse): void;
|
|
78
|
+
private buildRow;
|
|
79
|
+
private buildCell;
|
|
80
|
+
/**
|
|
81
|
+
* Draw what `renderValue` decided.
|
|
82
|
+
*
|
|
83
|
+
* Every path here writes through `textContent`, never `innerHTML`, so a feed
|
|
84
|
+
* cannot inject markup - and an `href` is only ever set from a URL that
|
|
85
|
+
* module already restricted to http, https and mailto.
|
|
86
|
+
*/
|
|
87
|
+
private renderValue;
|
|
88
|
+
/**
|
|
89
|
+
* Whether any row could ever carry an action.
|
|
90
|
+
*
|
|
91
|
+
* Decides the column, not the button: with no map there is nothing to put in
|
|
92
|
+
* it, and an always-empty leading column is worse than no column at all.
|
|
93
|
+
*/
|
|
94
|
+
private get hasActions();
|
|
95
|
+
private buildRowActions;
|
|
96
|
+
private renderPager;
|
|
97
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How to draw one cell's value, given what GTFS says the field holds.
|
|
3
|
+
*
|
|
4
|
+
* This decides; `table.ts` draws. Keeping the decision pure is what makes the
|
|
5
|
+
* cases that matter testable at all, since vitest runs without a document and
|
|
6
|
+
* the cases that matter are the malformed ones.
|
|
7
|
+
*
|
|
8
|
+
* Two rules govern everything here:
|
|
9
|
+
*
|
|
10
|
+
* 1. **A value that does not conform is shown as the string the feed contains.**
|
|
11
|
+
* Never blanked, never corrected, never allowed to throw. This is the
|
|
12
|
+
* project's existing principle - a feed with a malformed number still opens,
|
|
13
|
+
* because type errors are what an inspection tool exists to reveal - applied
|
|
14
|
+
* to rendering rather than to loading.
|
|
15
|
+
*
|
|
16
|
+
* 2. **A feed is untrusted input.** Only `http:`, `https:` and `mailto:` ever
|
|
17
|
+
* become a link; anything else is text. Before this module, no feed value
|
|
18
|
+
* was ever used as an `href`, so links are a new attack surface and
|
|
19
|
+
* `javascript:` in a URL field must not survive it.
|
|
20
|
+
*/
|
|
21
|
+
import type { ColumnInfo } from "../sources/types";
|
|
22
|
+
export interface Rendered {
|
|
23
|
+
/** What to show. Always a string; never empty unless the value was. */
|
|
24
|
+
text: string;
|
|
25
|
+
kind: "plain" | "numeric" | "link" | "color";
|
|
26
|
+
/** Only ever an http, https or mailto URL. */
|
|
27
|
+
href?: string;
|
|
28
|
+
/** Only ever `#RRGGBB`, from six hex digits. */
|
|
29
|
+
swatch?: string;
|
|
30
|
+
/** What was expected, when the value does not conform. Shown on hover. */
|
|
31
|
+
malformed?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Set when the feed left this field empty and GTFS says what that means. The
|
|
34
|
+
* text is the implied value, not something the producer wrote, so it must be
|
|
35
|
+
* drawn in a way that cannot be mistaken for one.
|
|
36
|
+
*/
|
|
37
|
+
implied?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The only entry point, and the one place that can fail safely.
|
|
41
|
+
*
|
|
42
|
+
* Every formatter above is total, so the catch should never fire. It is here so
|
|
43
|
+
* that it cannot matter if a formatter added later is not - one malformed cell
|
|
44
|
+
* in a sixty-million-row feed must not blank a page.
|
|
45
|
+
*
|
|
46
|
+
* `raw` turns all of it off and returns the characters the file contains.
|
|
47
|
+
* Formatting is a reading aid, and a reading aid is the wrong thing when the
|
|
48
|
+
* question is what a producer actually wrote: `Aug 22, 2026` is easier to read
|
|
49
|
+
* than `20260822`, but only the second answers "is this field padded?". An
|
|
50
|
+
* inspection tool has to be able to stop interpreting.
|
|
51
|
+
*/
|
|
52
|
+
export declare function renderValue(value: string | null, column: ColumnInfo | undefined, raw?: boolean): Rendered;
|
|
53
|
+
/**
|
|
54
|
+
* What to say about a column in its header tooltip.
|
|
55
|
+
*
|
|
56
|
+
* The schema knows more about a column than its name shows: what GTFS says it
|
|
57
|
+
* holds, whether it must be filled in, and - for a conditionally required
|
|
58
|
+
* field - the condition in words. "Conditionally required" on its own is not
|
|
59
|
+
* something a reader can act on; the condition is.
|
|
60
|
+
*/
|
|
61
|
+
export declare function describeColumn(column: ColumnInfo | undefined): string;
|
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gtfs-garage-web",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Browse a GTFS feed: a mountable viewer with a table, filters and a map.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"build:lib": "vite build --config vite.lib.config.ts && yarn types",
|
|
13
|
+
"types": "tsc -p tsconfig.lib.json"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@duckdb/duckdb-wasm": "1.32.0",
|
|
17
|
+
"@types/node": "^22.20.2",
|
|
18
|
+
"@types/react": "^18.3.11",
|
|
19
|
+
"@types/react-dom": "^18.3.1",
|
|
20
|
+
"maplibre-gl": "^5.7.0",
|
|
21
|
+
"react": "^18.3.1",
|
|
22
|
+
"react-dom": "^18.3.1",
|
|
23
|
+
"typescript": "^5.7.2",
|
|
24
|
+
"vite": "^6.0.5",
|
|
25
|
+
"vitest": "^4.1.11"
|
|
26
|
+
},
|
|
27
|
+
"resolutions": {
|
|
28
|
+
"vite": "^6.0.5"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/MobilityData/gtfs-garage.git",
|
|
33
|
+
"directory": "web"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/MobilityData/gtfs-garage",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"gtfs",
|
|
38
|
+
"transit",
|
|
39
|
+
"viewer",
|
|
40
|
+
"maplibre"
|
|
41
|
+
],
|
|
42
|
+
"files": [
|
|
43
|
+
"dist"
|
|
44
|
+
],
|
|
45
|
+
"types": "./dist/mount.d.ts",
|
|
46
|
+
"module": "./dist/mount.js",
|
|
47
|
+
"sideEffects": [
|
|
48
|
+
"**/*.css"
|
|
49
|
+
],
|
|
50
|
+
"exports": {
|
|
51
|
+
".": {
|
|
52
|
+
"types": "./dist/mount.d.ts",
|
|
53
|
+
"import": "./dist/mount.js"
|
|
54
|
+
},
|
|
55
|
+
"./dataset": {
|
|
56
|
+
"types": "./dist/mount-dataset.d.ts",
|
|
57
|
+
"import": "./dist/dataset.js"
|
|
58
|
+
},
|
|
59
|
+
"./map": {
|
|
60
|
+
"types": "./dist/map-part.d.ts",
|
|
61
|
+
"import": "./dist/map.js"
|
|
62
|
+
},
|
|
63
|
+
"./parquet": {
|
|
64
|
+
"types": "./dist/sources/parquet.d.ts",
|
|
65
|
+
"import": "./dist/parquet.js"
|
|
66
|
+
},
|
|
67
|
+
"./react": {
|
|
68
|
+
"types": "./dist/react.d.ts",
|
|
69
|
+
"import": "./dist/react.js"
|
|
70
|
+
},
|
|
71
|
+
"./style.css": "./dist/gtfs-garage-web.css",
|
|
72
|
+
"./package.json": "./package.json"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@duckdb/duckdb-wasm": "^1.32.0",
|
|
76
|
+
"maplibre-gl": "^5.0.0",
|
|
77
|
+
"react": "^18 || ^19",
|
|
78
|
+
"react-dom": "^18 || ^19"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"@duckdb/duckdb-wasm": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"maplibre-gl": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"react": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"react-dom": {
|
|
91
|
+
"optional": true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|