@lukekaalim/act-insight 0.0.2-alpha.6 → 1.1.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.
@@ -0,0 +1,61 @@
1
+ import { Component, h, useEffect, useState } from '@lukekaalim/act';
2
+ import { ReconcilerDebugController, ReconcilerDebugEventBus, ScheduleController, ScheduleEventBus } from '@lukekaalim/act-debug';
3
+ import { InsightAppState } from './InsightApp';
4
+
5
+ export type ScheduleControlsProps = {
6
+ controller: ScheduleController,
7
+ bus: ScheduleEventBus,
8
+
9
+ reconciler: ReconcilerDebugController,
10
+
11
+ state: InsightAppState,
12
+ onStateChange?: (newState: InsightAppState) => void,
13
+ };
14
+
15
+ export const ScheduleControls: Component<ScheduleControlsProps> = ({ controller, bus, reconciler, state, onStateChange = () => {} }) => {
16
+ useEffect(() => {
17
+ bus.onInterceptStart = () => {
18
+ onStateChange({ ...state, paused: true });
19
+ //onPauseChange(false)
20
+ }
21
+ bus.onInterceptEnd = () => {
22
+ onStateChange({ ...state, paused: false });
23
+ //onPauseChange(false)
24
+ }
25
+ bus.onAfterCallbackExecute = () => {
26
+ //reconciler.getThread();
27
+ }
28
+ }, [bus, reconciler, state])
29
+
30
+
31
+ const onStepClick = () => {
32
+ controller.step();
33
+ }
34
+ const onResumeClick = () => {
35
+ controller.cancelIntercept();
36
+ }
37
+ const onChangeBreakBeforeUpdate = (event: Event) => {
38
+ onStateChange({ ...state, breakOnBeforeUpdate: (event.target as HTMLInputElement).checked });
39
+ }
40
+ const onChangeBreakAfterUpdate = (event: Event) => {
41
+ onStateChange({ ...state, breakOnAfterUpdate: (event.target as HTMLInputElement).checked });
42
+ }
43
+
44
+ return h('div', { style: { background: state.paused ? 'red' : 'white', padding: '8px', display: 'flex', gap: '12px' }}, [
45
+ h('div', { style: { display: 'flex', 'flex-direction': 'column' } }, [
46
+ h('label', { style: { 'margin': 'auto 0' } }, [
47
+ h('span', {}, `Break Before Update`),
48
+ h('input', { type: 'checkbox', checked: state.breakOnBeforeUpdate, onChange: onChangeBreakBeforeUpdate }),
49
+ ]),
50
+ h('label', { style: { 'margin': 'auto 0' } }, [
51
+ h('span', {}, `Break After Update`),
52
+ h('input', { type: 'checkbox', checked: state.breakOnAfterUpdate, onChange: onChangeBreakAfterUpdate }),
53
+ ]),
54
+ ]),
55
+ h('button', { onClick: onStepClick, disabled: !state.paused, style: { padding: '8px' } }, 'Step'),
56
+ h('button', { onClick: onResumeClick, disabled: !state.paused, style: { padding: '8px' } }, 'Resume'),
57
+ h('span', {
58
+ style: { border: `2px solid ${state.paused ? 'orange' : 'black'}`, 'border-radius': '8px', padding: '8px' }
59
+ }, state.paused ? `Paused` : `Ready`),
60
+ ])
61
+ };
@@ -10,10 +10,18 @@
10
10
  font-family: monospace;
11
11
  display: inline;
12
12
 
13
- padding: 2px;
13
+ padding: 4px;
14
14
  border-radius: 8px;
15
15
 
16
16
  margin: 2px;
17
+
18
+ border: 2px solid rgba(255, 255, 255, 0);
19
+ white-space: nowrap;
20
+ }
21
+ .elementName:hover {
22
+ font-weight: bold;
23
+ cursor: pointer;
24
+ border: 2px solid white;
17
25
  }
18
26
 
19
27
  .commit {
@@ -31,6 +39,7 @@
31
39
  }
32
40
 
33
41
  .commitList {
42
+ flex: 1;
34
43
  display: flex;
35
44
  flex-direction: column;
36
45
  list-style: none;
package/TreeViewer.ts CHANGED
@@ -1,35 +1,35 @@
1
1
  import { Component, h, Node } from "@lukekaalim/act";
2
- import { Commit, CommitID, CommitTree } from "@lukekaalim/act-recon";
3
2
  import { hs } from "@lukekaalim/act-web";
4
- import { getElementName } from "./utils";
5
3
  import stringHash from '@sindresorhus/string-hash';
6
4
 
7
5
  import classes from './TreeViewer.module.css';
8
- import { CommitAttributeTag } from "./AttributeTag";
6
+ //import { CommitAttributeTag } from "./AttributeTag";
9
7
  import { CommitReport, TreeReport } from "@lukekaalim/act-debug";
8
+ import { CommitID } from "@lukekaalim/act-recon";
9
+ import { CommitAttributeTag } from './AttributeTag';
10
10
 
11
11
  export type TreeViewerProps = {
12
- tree: TreeReport,
12
+ //commits: Map<CommitID, CommitReport>,
13
+ roots: CommitID[],
13
14
 
14
- renderCommit: (commit: CommitReport) => Node,
15
+ renderCommit: (commitId: CommitID) => Node,
15
16
  }
16
17
 
17
18
  export const TreeViewer: Component<TreeViewerProps> = ({
18
- tree, renderCommit
19
+ //commits,
20
+ roots,
21
+ renderCommit
19
22
  }) => {
20
- const rootCommits = tree.roots
21
- .map(root => tree.commits.get(root.id))
22
- .filter(Boolean) as CommitReport[];
23
+ //const rootCommits = roots.map(root => commits.get(root)).filter(x => !!x)
23
24
 
24
25
  const className = [classes.commitList, classes.top].join(' ')
25
26
 
26
- return h('ol', { className }, rootCommits.map(root =>
27
- h('li', {}, renderCommit(root))));
27
+ return h('ol', { className }, roots.map(root =>
28
+ h('li', { key: root }, renderCommit(root))));
28
29
  };
29
30
 
30
31
  export type CommitPreviewProps = {
31
32
  commit: CommitReport,
32
- tree: TreeReport,
33
33
 
34
34
  attributes?: [string, string][],
35
35
 
@@ -37,33 +37,46 @@ export type CommitPreviewProps = {
37
37
 
38
38
  depth?: number,
39
39
 
40
- renderCommit: (commit: CommitReport) => Node,
40
+ renderCommit?: (commit: CommitID) => Node,
41
41
  onClick?: () => void,
42
42
  }
43
43
 
44
44
  export const CommitPreview: Component<CommitPreviewProps> = ({
45
- commit, tree, depth = 0,
45
+ commit, depth = 0,
46
46
  attributes = [],
47
47
  renderCommit,
48
48
  color,
49
49
  onClick,
50
50
  }) => {
51
- const children = commit.children
52
- .map(childRef => tree.commits.get(childRef.id)).filter(c => !!c);
53
-
54
51
  const background = `hsl(${(depth * 22.3) % 360}deg, 50%, 80%)`;
55
52
  const elementBackground = color || `hsl(${stringHash(commit.element.type) % 360}deg, 60%, 80%)`;
53
+ const lineColor = `hsl(${stringHash(commit.id.toString()) % 360}, 100%, 20%)`
54
+
56
55
 
56
+ return hs('div', { className: classes.commit, style: { position: 'relative' }, id: `commit:${commit.id}` }, [
57
+ commit.children.length > 0 &&
58
+ h('div', { style: {
59
+ position: 'absolute',
60
+ top: '5px',
61
+ height: 'calc(100% - 18px)', width: '1px', background: lineColor, transform: `translate(20px, 0px)`
62
+ } }),
57
63
 
58
- return hs('div', { className: classes.commit, style: { background } }, [
59
- hs('div', { className: [classes.elementBar].join(' ') }, [
64
+ hs('div', { className: [classes.elementBar].join(' '), style: { 'position': 'relative' } }, [
60
65
  hs('button', { onClick, className: classes.elementName, style: { background: elementBackground } },
61
66
  commit.element.type),
62
- //h(CommitAttributeTag, { name: 'Id', value: commit.id.toString() }),
67
+ h(CommitAttributeTag, { name: 'Id', value: commit.id.toString() }),
63
68
  attributes.map(([name, value]) => h(CommitAttributeTag, { name, value }))
64
69
  //h(CommitAttributeTag, { name: 'Version', value: commit.version.toString() }),
65
70
  ]),
66
- hs('ol', { className: classes.commitList }, children.map(child => h('li', {}, renderCommit(child)))),
71
+
72
+ !!renderCommit && hs('ol', { className: classes.commitList }, commit.children.map(childId => h('li', { key: childId, style: { position: 'relative' } }, [
73
+ renderCommit(childId),
74
+ h('div', { style: {
75
+ top: 0,
76
+ width: '25px', height: '1px', 'border-top': '2px dotted black', position: 'absolute',
77
+ transform: `translate(-22px, 15px)`
78
+ }})
79
+ ]))),
67
80
  ])
68
81
  };
69
82
 
package/Virtual.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { Component, h, Node, ReadonlyRef, useEffect, useRef, useState } from "@lukekaalim/act";
2
+ import { debounce } from 'lodash-es';
3
+
4
+ export type VirtualTreeItem = {
5
+ depth: number,
6
+
7
+ }
8
+
9
+ export type VirtualTreeProps = {
10
+ chunkSize: number,
11
+ chunkCount: number,
12
+
13
+ windowRange: number,
14
+
15
+ renderChunk(index: number): Node,
16
+
17
+ viewportRef?: ReadonlyRef<HTMLElement | null>,
18
+ }
19
+
20
+ export const Virtual1D: Component<VirtualTreeProps> = ({ chunkSize, chunkCount, renderChunk, viewportRef: propViewportRef, windowRange }) => {
21
+ const [start, setStart] = useState(0);
22
+ const [end, setEnd] = useState(0);
23
+
24
+ const localViewportRef = useRef<HTMLElement | null>(null);
25
+ const viewportRef = propViewportRef || localViewportRef;
26
+
27
+ const listRef = useRef<HTMLElement | null>(null);
28
+
29
+ useEffect(() => {
30
+ if (!viewportRef.current)
31
+ return;
32
+
33
+ const viewport = viewportRef.current;
34
+
35
+ const setViewport = () => {
36
+ const rect = viewport.getBoundingClientRect();
37
+
38
+ setStart(Math.floor((viewport.scrollTop) / chunkSize))
39
+ setEnd(Math.ceil((viewport.scrollTop + rect.height) / chunkSize))
40
+ };
41
+ setViewport();
42
+
43
+ viewport.addEventListener('scroll', setViewport)
44
+ return () => {
45
+ viewport.removeEventListener('scroll', setViewport)
46
+ }
47
+ }, [propViewportRef])
48
+
49
+ const renderedIndices = Array
50
+ .from({ length: end - start })
51
+ .map((_, i) => start + i)
52
+ .filter(x => x >= 0 && x < chunkCount);
53
+
54
+
55
+ return [
56
+ //h('pre', {}, renderedIndices.join(', ')),
57
+ h('div', { ref: viewportRef, style: { overflow: 'auto', height: '100%' } },
58
+ h('div', { ref: listRef, style: { height: (chunkSize * chunkCount) + 'px', position: 'relative' } },
59
+ renderedIndices.map(index =>
60
+ h('div', { style: { position: 'absolute', top: (index * chunkSize) + 'px', height: chunkSize, padding: '-1', border: '1px dotted black', width: '100%' }},
61
+ renderChunk(index)))
62
+ ))
63
+ ];
64
+ };
package/index.ts CHANGED
@@ -1 +1,4 @@
1
- export * from './InsightApp';
1
+ // export * from './InsightApp';
2
+ export * from './TreeViewer';
3
+
4
+ export * from './utils';
package/lookup.ts ADDED
@@ -0,0 +1,185 @@
1
+ import { CommitReport, DeltaReport, ElementReport, ThreadReport, TreeReport, WorkTaskReport } from "@lukekaalim/act-debug";
2
+ import { CommitID, CommitVersion, WorkThread2 } from "@lukekaalim/act-recon";
3
+
4
+ export class MutableCommitRef {
5
+ element: ElementReport;
6
+ id: CommitID;
7
+ version: CommitVersion;
8
+ distance: number;
9
+
10
+ report: CommitReport;
11
+
12
+ children: MutableCommitRef[] = [];
13
+ parent: null | MutableCommitRef = null;
14
+
15
+ constructor(commit: CommitReport) {
16
+ this.element = commit.element;
17
+ this.id = commit.id;
18
+ this.version = commit.version;
19
+ this.distance = commit.distance;
20
+
21
+ this.report = commit;
22
+ }
23
+
24
+ update(commit: CommitReport) {
25
+ this.report = commit;
26
+ this.version = commit.version;
27
+ this.element = commit.element;
28
+ }
29
+
30
+ resolve(lookupMap: Map<CommitID, MutableCommitRef>) {
31
+ if (this.report.parent !== null)
32
+ this.linkParent(this.report.parent, lookupMap);
33
+ this.linkChildren(this.report.children, lookupMap);
34
+ }
35
+
36
+ linkParent(parent: CommitID, lookupMap: Map<CommitID, MutableCommitRef>) {
37
+ this.parent = lookupMap.get(parent) || null;
38
+ }
39
+ linkChildren(children: CommitID[], lookupMap: Map<CommitID, MutableCommitRef>) {
40
+ this.children = children.map(c => lookupMap.get(c)).filter(x => !!x);
41
+ }
42
+ }
43
+
44
+ export class CommitLookupCache {
45
+ map: Map<CommitID, CommitReport> = new Map();
46
+ roots: Set<CommitID> = new Set();
47
+
48
+ setTree(tree: TreeReport) {
49
+ this.map.clear();
50
+ this.roots.clear();
51
+
52
+ for (const commit of tree.commits) {
53
+ this.map.set(commit.id, commit)
54
+ if (!commit.parent)
55
+ this.roots.add(commit.id);
56
+ }
57
+ }
58
+
59
+ ingest(delta: DeltaReport) {
60
+ for (const create of delta.created) {
61
+ this.map.set(create.id, create);
62
+ if (!create.parent)
63
+ this.roots.add(create.id);
64
+ }
65
+ for (const update of delta.updated)
66
+ this.map.set(update.id, update);
67
+ for (const remove of delta.removed) {
68
+ this.map.delete(remove.id);
69
+ this.roots.delete(remove.id);
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * A bunch of relevant data for a Tree in the progress of changing
76
+ */
77
+ export class ThreadLookupCache {
78
+ canon: CommitLookupCache;
79
+
80
+ report: DeltaReport | null = null;
81
+ thread: ThreadReport | null = null;
82
+
83
+ constructor(canon: CommitLookupCache) {
84
+ this.canon = canon;
85
+ }
86
+
87
+ roots: Set<CommitID> = new Set();
88
+
89
+ created: Set<CommitID> = new Set();
90
+ updated: Set<CommitID> = new Set();
91
+ removed: Set<CommitID> = new Set();
92
+
93
+ /**
94
+ * An up to date map of the tree, plus deleted notes in this delta
95
+ */
96
+ all: Map<CommitID, CommitReport> = new Map();
97
+
98
+ nextTask: WorkTaskReport | null = null;
99
+ prevTask: WorkTaskReport | null = null;
100
+
101
+ allTasks: Map<CommitID, WorkTaskReport> = new Map();
102
+
103
+ targets: Set<CommitID> = new Set();
104
+ visited: Set<CommitID> = new Set();
105
+
106
+ /**
107
+ * Clear the delta cache
108
+ */
109
+ reset() {
110
+ this.roots = new Set(this.canon.roots)
111
+ this.all = new Map(this.canon.map);
112
+ this.allTasks = new Map();
113
+
114
+ this.nextTask = null;
115
+ this.prevTask = null;
116
+ this.report = null;
117
+
118
+ this.created.clear();
119
+ this.updated.clear();
120
+ this.removed.clear();
121
+
122
+ this.targets.clear();
123
+ this.visited.clear();
124
+ }
125
+
126
+ ingestThread(thread: ThreadReport) {
127
+ this.thread = thread;
128
+
129
+ this.nextTask = thread.pendingTasks[thread.pendingTasks.length - 1];
130
+ this.targets = new Set(thread.reasons.map(reason => reason.target));
131
+ this.visited = new Set(thread.visited)
132
+ this.allTasks = new Map(thread.pendingTasks.map(task => [task.id, task]))
133
+ }
134
+
135
+ ingestDelta(delta: DeltaReport) {
136
+ this.report = delta;
137
+ const createdIds = new Set(delta.created.map(c => c.id));
138
+
139
+ for (const commit of delta.created) {
140
+ this.created.add(commit.id)
141
+
142
+ const children = [...new Set(commit.children.filter(c => this.all.has(c) || createdIds.has(c)))]
143
+ this.all.set(commit.id, { ...commit, children });
144
+
145
+ if (!commit.parent)
146
+ this.roots.add(commit.id);
147
+ }
148
+ for (const commit of delta.updated) {
149
+ const existingCommit = this.canon.map.get(commit.id) as CommitReport;
150
+
151
+ const children = [...new Set([
152
+ ...commit.children.filter(c => this.all.has(c)),
153
+ ...existingCommit.children,
154
+ ])]
155
+ const mergedCommitReport = { ...commit, children };
156
+
157
+ this.updated.add(commit.id)
158
+ this.all.set(commit.id, mergedCommitReport)
159
+ }
160
+ for (const commit of delta.removed) {
161
+ this.removed.add(commit.id);
162
+ this.all.set(commit.id, commit);
163
+ }
164
+ }
165
+
166
+ getFlat() {
167
+ const pending: CommitReport[] = [...this.roots.values()]
168
+ .map(root => this.all.get(root))
169
+ .filter(x => !!x);
170
+
171
+ const flat: CommitReport[] = [];
172
+
173
+ while (pending.length > 0) {
174
+ const commit = pending.pop() as CommitReport;
175
+ flat.push(commit);
176
+ for (const childId of [...commit.children].reverse()) {
177
+ const child = this.all.get(childId);
178
+ if (child)
179
+ pending.push(child);
180
+ }
181
+ }
182
+
183
+ return flat;
184
+ }
185
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@lukekaalim/act-insight",
3
3
  "main": "index.ts",
4
- "version": "0.0.2-alpha.6",
4
+ "version": "1.1.0",
5
5
  "scripts": {},
6
6
  "dependencies": {
7
7
  "@lukekaalim/act": "*",
8
- "@lukekaalim/act-recon": "3.0.0-alpha.4",
9
- "@lukekaalim/act-web": "3.4.0-alpha.3",
10
- "@lukekaalim/act-debug": "1.0.0-alpha.0",
8
+ "@lukekaalim/act-recon": "3.1.0",
9
+ "@lukekaalim/act-web": "5.0.0",
10
+ "@lukekaalim/act-debug": "1.0.1",
11
11
  "@sindresorhus/string-hash": "^2.0.0",
12
12
  "@types/firefox-webext-browser": "^120.0.4",
13
13
  "lodash-es": "^4.17.21"
package/utils.ts CHANGED
@@ -1,52 +1,64 @@
1
- import { Element, errorBoundaryType, primitiveNodeTypes, providerNodeType, renderNodeType } from "@lukekaalim/act";
2
- import { CommitPath, CommitRef } from "@lukekaalim/act-recon";
3
-
4
- export const getElementName = (element: Element) => {
5
- if (typeof element.type === 'function')
6
- return `<component(${element.type.name})>`;
7
- if (typeof element.type === 'symbol')
8
- switch (element.type) {
9
- case primitiveNodeTypes.number:
10
- return `<number value={${element.props.value}}>`
11
- case primitiveNodeTypes.string:
12
- return `<string value="${element.props.value}">`
13
- case primitiveNodeTypes.boolean:
14
- return `<boolean value="${element.props.value}">`
15
- case primitiveNodeTypes.array:
16
- return `<array>`
17
- case primitiveNodeTypes.null:
18
- return `<null>`
19
- case renderNodeType:
20
- return `<render type="${element.props.type}">`;
21
- case providerNodeType:
22
- return `<context id="${element.props.id}">`;
23
- case errorBoundaryType:
24
- return `<boundary>`;
25
- default:
26
- return `<symbol>`
27
- }
28
- if (element.type)
29
- return `<${element.type}>`;
30
- return '<none>';
1
+
2
+ import { h, Node } from "@lukekaalim/act";
3
+ import { NodeBuilder, RenderSpace2 } from "@lukekaalim/act-backstage";
4
+ import { createDebugScheduler, DebugReconciler } from "@lukekaalim/act-debug";
5
+ import { createWebNodeBuilder, HTML, render } from "@lukekaalim/act-web";
6
+ import { InsightApp } from "./InsightApp";
7
+ import { Reconciler2 } from "@lukekaalim/act-recon";
8
+
9
+ export type DevOptions = {
10
+ mode?: 'extension' | 'popup' | 'none'
11
+ };
12
+
13
+ export const renderDEV = (node: Node, builders: NodeBuilder<any, any>[], { mode = 'none' }: DevOptions = {}) => {
14
+ const reconciler = new DebugReconciler();
15
+ const spaces = builders.map(builder => new RenderSpace2(reconciler.tree, builder));
16
+
17
+ reconciler.bus = {
18
+ render(delta) {
19
+ for (const space of spaces)
20
+ space.create(delta);
21
+ for (const space of spaces)
22
+ space.update(delta);
23
+ },
24
+ }
25
+ switch (mode) {
26
+ case 'popup':
27
+ createDebugPopup(reconciler);
28
+ break;
29
+ default:
30
+ }
31
+
32
+ const ref = reconciler.mount(node);
33
+ return {ref, reconciler}
31
34
  }
32
35
 
33
36
 
34
- export const findCommonAncestor = (commitRefs: CommitRef[]) => {
35
- let commonAncestorPath: CommitPath | null = null;
36
- for (const ref of commitRefs) {
37
- if (!commonAncestorPath)
38
- commonAncestorPath = ref.path
39
- else {
40
- for (const id of [...ref.path].reverse()) {
41
- const index = commonAncestorPath.indexOf(id);
42
- if (index !== -1) {
43
- commonAncestorPath = commonAncestorPath.slice(0, index + 1)
44
- break;
45
- }
46
- }
37
+ export const createDebugPopup = async (reconciler: DebugReconciler) => {
38
+ const newWindow = window.open('', "DevTools", "popup");
39
+ if (!newWindow)
40
+ throw new Error(`Unable to make/find new window!`);
41
+
42
+ const body = newWindow.document.body;
43
+ for (const child of [...body.childNodes, ...newWindow.document.head.childNodes])
44
+ child.remove();
45
+
46
+ for (const headElement of [...window.document.head.childNodes])
47
+ if (headElement instanceof HTMLStyleElement)
48
+ newWindow.document.head.appendChild(headElement.cloneNode(true))
49
+ else if (headElement instanceof HTMLLinkElement) {
50
+ const element = headElement.cloneNode(true) as HTMLLinkElement;
51
+ const src = new URL(element.href, document.location.href);
52
+ element.href = src.href;
53
+ newWindow.document.head.appendChild(element)
47
54
  }
48
- };
49
- if (commonAncestorPath)
50
- return CommitRef.from(commonAncestorPath);
51
- return null;
55
+
56
+ return new Promise<void>(onReady => {
57
+ render(
58
+ h(InsightApp, { controller: reconciler.controller, bus: reconciler.debugBus, document: newWindow.document, onReady }),
59
+ body,
60
+ { window: newWindow }
61
+ );
62
+ })
63
+
52
64
  }
package/vite.config.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { defineConfig } from 'vite';
2
- import webExtension from "vite-plugin-web-extension";
2
+ //import webExtension from "vite-plugin-web-extension";
3
3
 
4
4
  export default defineConfig({
5
5
  build: {
6
6
  modulePreload: false
7
7
  },
8
- plugins: [webExtension({
9
- browser: 'firefox'
10
- })],
8
+ //plugins: [webExtension({
9
+ // browser: 'firefox'
10
+ //})],
11
11
  });
@@ -1,5 +0,0 @@
1
- .commitViewer {
2
- position: fixed;
3
- right: 0px;
4
- width: 40%;
5
- }