From 8d3a88582f0373149bac3ffb1376bdf1329c2f87 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 19:44:37 +0700 Subject: [PATCH 01/47] fix(b-components-panel): improve rendering of tree --- .../b-components-panel/b-components-panel.ss | 4 +- .../b-components-panel/b-components-panel.ts | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss index 47868f9..2e22429 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss @@ -16,9 +16,11 @@ < .&__body < b-tree & + ref = tree | :items = items | :item = 'b-components-panel-item' | :theme = 'demo' | :cancelable = true | - :lazyRender = true + :lazyRender = true | + :renderFilter = createTreeRenderFilter() . diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts index 190529a..578752b 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts @@ -6,16 +6,24 @@ * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ +import symbolGenerator from 'core/symbol'; import iBlock, { component, prop, field, computed } from 'components/super/i-block/i-block'; +import type bTree from 'components/base/b-tree/b-tree'; +import type { RenderFilter } from 'components/base/b-tree/b-tree'; import type { Item, ComponentData } from 'features/components/b-components-panel/interface'; import { createItems } from 'features/components/b-components-panel/modules/helpers'; export * from 'features/components/b-components-panel/interface'; +const $$ = symbolGenerator(); + @component() export default class bComponentsPanel extends iBlock { + override readonly $refs!: iBlock['$refs'] & { + tree?: bTree; + }; /** * Component's data @@ -61,6 +69,48 @@ export default class bComponentsPanel extends iBlock { } } + /** + * Returns render filter for `bTree`, which delays rendering of children + * for folded items + */ + createTreeRenderFilter(): RenderFilter { + const unfolded = new Set(); + const resolvers = new Map void>(); + + this.waitRef('tree') + .then((tree) => this.async.on( + tree.unsafe.top.selfEmitter, + 'fold', + (_ctx: unknown, _el: Element, item: Item, folded: boolean) => { + if (folded) { + unfolded.delete(item.value); + + } else { + unfolded.add(item.value); + + resolvers.get(item.value)?.(true); + resolvers.delete(item.value); + } + }, + {label: $$.foldChange} + )) + .catch(stderr); + + return (ctx, el, i) => { + if (ctx.level === 0 && i < ctx.renderChunks) { + return true; + } + + if (!ctx.folded || unfolded.has(el.parentValue)) { + return true; + } + + return new Promise((resolve) => { + resolvers.set(el.parentValue, resolve); + }); + }; + } + /** * Update show empty * From 2e7ea9b9f19e62a3374a5130ea666d59b3f64164 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 19:44:47 +0700 Subject: [PATCH 02/47] chore: update v4fire/client --- packages/devtools-extension/components-lock.json | 14 +++++++++----- yarn.lock | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/devtools-extension/components-lock.json b/packages/devtools-extension/components-lock.json index dd2ce0f..004130a 100644 --- a/packages/devtools-extension/components-lock.json +++ b/packages/devtools-extension/components-lock.json @@ -1,5 +1,5 @@ { - "hash": "9a9ab7af9b3e01398691a5504d10670718f30c05120c98a22533eaf56fe2daeb", + "hash": "0b67a39d6be3b70d606ea68479aa6289d5cd8891cc63e67d8f8d90fad4881b14", "data": { "%data": "%data:Map", "%data:Map": [ @@ -2652,7 +2652,8 @@ "components/traits/i-control-list/i-control-list", "core/browser", "core/cookies", - "core/html" + "core/html", + "models/modules/session" ] }, "name": "p-v4-components-demo", @@ -2693,7 +2694,8 @@ "components/traits/i-control-list/i-control-list", "core/browser", "core/cookies", - "core/html" + "core/html", + "models/modules/session" ], "resolvedLibs": { "%data": "%data:Set", @@ -2707,7 +2709,8 @@ "core/cookies", "core/html", "core/router/engines/browser-history", - "core/router/engines/in-memory" + "core/router/engines/in-memory", + "models/modules/session" ] }, "resolvedOwnLibs": { @@ -2722,7 +2725,8 @@ "core/cookies", "core/html", "core/router/engines/browser-history", - "core/router/engines/in-memory" + "core/router/engines/in-memory", + "models/modules/session" ] }, "type": "page", diff --git a/yarn.lock b/yarn.lock index 40c9d96..0fd0011 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2710,8 +2710,8 @@ __metadata: linkType: hard "@v4fire/client@git+https://github.com/V4Fire/Client.git#v4": - version: 4.0.0-beta.37 - resolution: "@v4fire/client@https://github.com/V4Fire/Client.git#commit=1b46a272dfe5592fcdc5696622330fdb3912caf5" + version: 4.0.0-beta.38 + resolution: "@v4fire/client@https://github.com/V4Fire/Client.git#commit=a3053782d1219fc3525f44aad2073882dc4d7b37" dependencies: "@babel/core": 7.17.5 "@babel/helpers": 7.17.7 @@ -2949,7 +2949,7 @@ __metadata: optional: true webpack-cli: optional: true - checksum: ba699367d38b9f7bcd6661eb982a9452fa14f565e323f3b884d191f72d490844b2a1ad755974fdc3cacf4b0974b56a5a22ddfd394ef336d3e0c64d7b943aaf5a + checksum: 9b63121ea771ec519b59cbe3f53729deea3c5e7525729501df35fd02af95d0f9139e900957cbe818498b85522529d6d17f71fa201832ada8842444231cf912b7 languageName: node linkType: hard From bcf2f57b4663315a428cce3c8a416248ff4549b0 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 19:53:56 +0700 Subject: [PATCH 03/47] chore(p-components): use throttle to load selected component data --- packages/devtools-core/src/pages/p-components/p-components.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devtools-core/src/pages/p-components/p-components.ts b/packages/devtools-core/src/pages/p-components/p-components.ts index 94d3f66..dfa6b97 100644 --- a/packages/devtools-core/src/pages/p-components/p-components.ts +++ b/packages/devtools-core/src/pages/p-components/p-components.ts @@ -54,14 +54,14 @@ export default class pComponents extends iDynamicPage { this.selectedComponentId = componentId; this.selectedComponentData = null; - const load = this.async.debounce(async () => { + const load = this.async.throttle(async () => { try { await this.loadSelectedComponentData(); } catch (error) { // TODO: show alert stderr(error); } - }, 300, {label: $$.loadSelectedComponentMeta}); + }, 1000, {label: $$.loadSelectedComponentMeta}); load(); } From bd0f7500ac2d5831539cc7cf232c29078ddefa9a Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 20:49:02 +0700 Subject: [PATCH 04/47] fix(eval-component-meta): check componentId of the node --- .../src/pages/p-components/p-components.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/devtools-extension/src/pages/p-components/p-components.ts b/packages/devtools-extension/src/pages/p-components/p-components.ts index b94029f..061028a 100644 --- a/packages/devtools-extension/src/pages/p-components/p-components.ts +++ b/packages/devtools-extension/src/pages/p-components/p-components.ts @@ -138,7 +138,10 @@ function evalComponentMeta(value: string, name?: string): Nullable { 'LANG_PACKS' ]); - let node = document.querySelector(`.i-block-helper.${value}`); + let node = Array.prototype.find.call( + document.querySelectorAll(`.i-block-helper.${value}`), + (node) => node.component?.componentId === value + ); if (node == null && name != null) { // Maybe it's a functional component From e28ec62a4993413de9264d085f8933af0037c1d1 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 20:54:16 +0700 Subject: [PATCH 05/47] feat(p-root): set --header-height css variable --- packages/devtools-core/src/pages/p-root/p-root.ss | 6 ++---- packages/devtools-core/src/pages/p-root/p-root.ts | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/devtools-core/src/pages/p-root/p-root.ss b/packages/devtools-core/src/pages/p-root/p-root.ss index 012b9a0..9f3d3cb 100644 --- a/packages/devtools-core/src/pages/p-root/p-root.ss +++ b/packages/devtools-core/src/pages/p-root/p-root.ss @@ -17,8 +17,6 @@ {{ placeholder }} < template v-else - block header - < b-header + < b-header ref = header - block page - < b-dynamic-page.&__page & - ref = page - . + < b-dynamic-page.&__page ref = page diff --git a/packages/devtools-core/src/pages/p-root/p-root.ts b/packages/devtools-core/src/pages/p-root/p-root.ts index 8f6a3b8..644956c 100644 --- a/packages/devtools-core/src/pages/p-root/p-root.ts +++ b/packages/devtools-core/src/pages/p-root/p-root.ts @@ -5,10 +5,11 @@ * Released under the MIT license * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ -import iStaticPage, { component, system, field } from 'components/super/i-static-page/i-static-page'; +import iStaticPage, { component, system, field, hook } from 'components/super/i-static-page/i-static-page'; import createRouter from 'core/router/engines/in-memory'; +import type bHeader from 'components/widgets/b-header/b-header'; import type bDynamicPage from 'components/base/b-dynamic-page/b-dynamic-page'; export * from 'components/super/i-static-page/i-static-page'; @@ -16,6 +17,7 @@ export * from 'components/super/i-static-page/i-static-page'; @component({root: true}) export default class pRoot extends iStaticPage { override readonly $refs!: iStaticPage['$refs'] & { + header?: bHeader; page?: bDynamicPage; }; @@ -30,4 +32,15 @@ export default class pRoot extends iStaticPage { */ @system() routerEngine: typeof createRouter = createRouter; + + /** + * Sets `--header-height` css variable + */ + @hook('mounted') + async init(): Promise { + const header = await this.waitRef('header'); + const page = await this.waitRef('page'); + + (page.$el).style.setProperty('--header-height', `${header.$el?.clientHeight ?? 0}px`); + } } From 2114260b017780ebae1dd44b9ca18b0bb568c96f Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 20:55:03 +0700 Subject: [PATCH 06/47] chore(p-components): improve height of children with `overflow: auto` --- .../components/b-components-panel/b-components-panel.styl | 1 - .../components/b-components-tree/b-components-tree.styl | 2 -- .../devtools-core/src/pages/p-components/p-components.styl | 5 +++++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl index 661ac7b..10bfbab 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl @@ -8,7 +8,6 @@ b-components-panel extends i-block border-bottom solid 1px black &__body - height 88vh overflow auto padding 8px word-break break-word diff --git a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.styl b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.styl index 0a142eb..836725f 100644 --- a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.styl +++ b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.styl @@ -3,8 +3,6 @@ b-components-tree extends i-block &__wrapper position relative - // FIXME fill remaining height - height 88vh overflow auto &__input diff --git a/packages/devtools-core/src/pages/p-components/p-components.styl b/packages/devtools-core/src/pages/p-components/p-components.styl index 5f58f8b..f23ac0b 100644 --- a/packages/devtools-core/src/pages/p-components/p-components.styl +++ b/packages/devtools-core/src/pages/p-components/p-components.styl @@ -5,6 +5,11 @@ p-components extends i-dynamic-page &__content display flex + > * + display flex + flex-direction column + height calc(100vh - var(--header-height, 0)) + > :first-child flex 0 0 60% border-right solid 1px black From b2dd2e578ae03af94f4029bd5d6df4cd104b0daa Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 21:18:18 +0700 Subject: [PATCH 07/47] feat(b-tree): create pretty theme --- .../base/b-tree/b-tree_theme_pretty.styl | 26 +++++++++++++++++++ .../b-components-panel/b-components-panel.ss | 2 +- .../b-components-tree/b-components-tree.ss | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 packages/devtools-core/src/components/base/b-tree/b-tree_theme_pretty.styl diff --git a/packages/devtools-core/src/components/base/b-tree/b-tree_theme_pretty.styl b/packages/devtools-core/src/components/base/b-tree/b-tree_theme_pretty.styl new file mode 100644 index 0000000..4ee0194 --- /dev/null +++ b/packages/devtools-core/src/components/base/b-tree/b-tree_theme_pretty.styl @@ -0,0 +1,26 @@ +b-tree_theme_pretty extends b-tree + /theme &__item-wrapper + display flex + + /theme &__marker + width 16px + + /theme &__fold + text-align center + + /theme &__fold:before + content "" + display inline-block + + border-top 4px solid transparent + border-bottom 4px solid transparent + border-left 6px solid rgba(100, 100, 100, 0.5) + + transition transform 0.1s ease + transform rotate(90deg) + + /theme &__node_folded_true &__fold:before + transform rotate(0) + + /theme &__node_folded_true &__children + display none diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss index 2e22429..2aed809 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss @@ -19,7 +19,7 @@ ref = tree | :items = items | :item = 'b-components-panel-item' | - :theme = 'demo' | + :theme = 'pretty' | :cancelable = true | :lazyRender = true | :renderFilter = createTreeRenderFilter() diff --git a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ss b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ss index 320e09c..132f14a 100644 --- a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ss +++ b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ss @@ -22,7 +22,7 @@ :item = 'b-components-tree-item' | :itemProps = itemProps | :folded = false | - :theme = 'demo' | + :theme = 'pretty' | :cancelable = false . From b54682d7afb432e28cffdc243939118d9e145421 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 21:36:41 +0700 Subject: [PATCH 08/47] chore(devtools-backend/serialize): pass value to isRestrictedKey --- packages/devtools-backend/src/serialize/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devtools-backend/src/serialize/index.ts b/packages/devtools-backend/src/serialize/index.ts index d89b2dc..76f7653 100644 --- a/packages/devtools-backend/src/serialize/index.ts +++ b/packages/devtools-backend/src/serialize/index.ts @@ -37,7 +37,7 @@ export function getType(value: T): string { */ export function serialize( value: T, - isRestrictedKey?: (key: string) => boolean + isRestrictedKey?: (key: string, value?: unknown) => boolean ): string { let refCounter = 1; @@ -49,7 +49,7 @@ export function serialize( return JSON.stringify(value, expandedStringify); function expandedStringify(this: any, key: string, value: unknown): unknown { - if (isRestrictedKey?.(key)) { + if (isRestrictedKey?.(key, value)) { return '[Restricted]'; } From cf07e729ea716ee6f978f1cc0265905e0badfeef Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 21:36:46 +0700 Subject: [PATCH 09/47] chore(eval-component-meta): improve restricted key --- .../src/pages/p-components/p-components.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/devtools-extension/src/pages/p-components/p-components.ts b/packages/devtools-extension/src/pages/p-components/p-components.ts index 061028a..5800994 100644 --- a/packages/devtools-extension/src/pages/p-components/p-components.ts +++ b/packages/devtools-extension/src/pages/p-components/p-components.ts @@ -131,9 +131,6 @@ function evalComponentMeta(value: string, name?: string): Nullable { 'r', 'self', 'unsafe', - 'window', - 'document', - 'console', 'router', 'LANG_PACKS' ]); @@ -181,5 +178,8 @@ function evalComponentMeta(value: string, name?: string): Nullable { const result = {componentName, props, fields, computedFields, systemFields, hierarchy, values}; - return globalThis.__V4FIRE_DEVTOOLS_BACKEND__.serialize(result, (key) => key.startsWith('$') || restricted.has(key)); + return globalThis.__V4FIRE_DEVTOOLS_BACKEND__.serialize( + result, + (key, value) => key.startsWith('$') || restricted.has(key) || value === globalThis || value === document || value === console + ); } From 335b46fc06f70353ae17e8d3d8b3362017ca419f Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 22:08:00 +0700 Subject: [PATCH 10/47] chore(b-components-panel): add padding for level 0 nodes --- .../components/b-components-panel/b-components-panel.styl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl index 10bfbab..0c2a50f 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl @@ -9,5 +9,8 @@ b-components-panel extends i-block &__body overflow auto - padding 8px + padding 4px 0 word-break break-word + + .b-tree__node_level_0 + padding 4px 8px 8px 4px From 233bb795f96f94cd00be4de8554b9dc68d192da6 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 16 Nov 2023 22:08:42 +0700 Subject: [PATCH 11/47] chore(b-components-tree): highlight active item --- .../components/b-components-tree/b-components-tree.styl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.styl b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.styl index 836725f..4682e8b 100644 --- a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.styl +++ b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.styl @@ -5,6 +5,10 @@ b-components-tree extends i-block position relative overflow auto + .b-tree__node_active_true + > .b-tree__item-wrapper + background-color rgba(100, 100, 100, 0.1) + &__input width 100% padding 8px 16px From 35d0716e9016773d9d59e5fc999cbf813357c07d Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 17 Nov 2023 14:39:30 +0700 Subject: [PATCH 12/47] chore: set eslint ignores correctly --- .eslintignore | 5 ----- eslint.config.js | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index ab8975c..0000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -# Third party -**/node_modules - -# Build products -coverage/ diff --git a/eslint.config.js b/eslint.config.js index 42a91d2..7d75ef0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -24,7 +24,24 @@ const copyrightTemplate = [ ' ' ]; +const ignore = [ + '**/src/**/@(i-|b-|p-|g-|v-)*/index.js', + '**/src/**/test/**/*.js', + + '**/assets/**', + '**/src/assets/**', + + '**/tmp/**', + '**/src/entries/tmp/**', + + '**/docs/**', + '**/dist/**', + '**/node_modules/**' +]; + base.forEach((item) => { + item.ignores = ignore; + if (item.plugins) { item.plugins['header'] = headerPlugin; } From d88c7c6f7528db996ee905c73376fefb1a7ff74c Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 16:07:09 +0300 Subject: [PATCH 13/47] fix(p-root): set header height variable for root node --- packages/devtools-core/src/pages/p-root/p-root.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/devtools-core/src/pages/p-root/p-root.ts b/packages/devtools-core/src/pages/p-root/p-root.ts index 644956c..f65f0d4 100644 --- a/packages/devtools-core/src/pages/p-root/p-root.ts +++ b/packages/devtools-core/src/pages/p-root/p-root.ts @@ -39,8 +39,7 @@ export default class pRoot extends iStaticPage { @hook('mounted') async init(): Promise { const header = await this.waitRef('header'); - const page = await this.waitRef('page'); - (page.$el).style.setProperty('--header-height', `${header.$el?.clientHeight ?? 0}px`); + (this.$el).style.setProperty('--header-height', `${header.$el?.clientHeight ?? 0}px`); } } From 62ce12621bfd3854a2c1362a71765a8a35018a93 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 16:58:18 +0300 Subject: [PATCH 14/47] feat(devtools-backend): create search module --- .../devtools-backend/src/search/README.md | 5 ++++ .../src/search/find-component-node.ts | 28 +++++++++++++++++++ packages/devtools-backend/src/search/index.ts | 8 ++++++ 3 files changed, 41 insertions(+) create mode 100644 packages/devtools-backend/src/search/README.md create mode 100644 packages/devtools-backend/src/search/find-component-node.ts create mode 100644 packages/devtools-backend/src/search/index.ts diff --git a/packages/devtools-backend/src/search/README.md b/packages/devtools-backend/src/search/README.md new file mode 100644 index 0000000..e8a13d6 --- /dev/null +++ b/packages/devtools-backend/src/search/README.md @@ -0,0 +1,5 @@ +# Search + +These modules provide search API: + +- find component node diff --git a/packages/devtools-backend/src/search/find-component-node.ts b/packages/devtools-backend/src/search/find-component-node.ts new file mode 100644 index 0000000..a9b2fc4 --- /dev/null +++ b/packages/devtools-backend/src/search/find-component-node.ts @@ -0,0 +1,28 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +/** + * Find component DOM node + * + * @param id - component id + * @param name - component name + */ +export default function findComponentNode(id: string, name?: string): T | null { + let node = Array.prototype.find.call( + document.querySelectorAll(`.i-block-helper.${id}`), + (node) => node.component?.componentId === id + ); + + if (node == null && name != null) { + // Maybe it's a functional component + const nodes = document.querySelectorAll(`.i-block-helper.${name}`); + node = Array.prototype.find.call(nodes, (node) => node.component?.componentId === id); + } + + return node; +} diff --git a/packages/devtools-backend/src/search/index.ts b/packages/devtools-backend/src/search/index.ts new file mode 100644 index 0000000..fa1d3e5 --- /dev/null +++ b/packages/devtools-backend/src/search/index.ts @@ -0,0 +1,8 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ +export { default as findComponentNode } from './find-component-node'; From 4785d8498cc33c8d13b5ff1f1c48dee0d05784f1 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 16:58:32 +0300 Subject: [PATCH 15/47] feat(devtools-backend): create ui module --- packages/devtools-backend/src/ui/README.md | 5 + .../src/ui/component-highlight.ts | 108 ++++++++++++++++++ packages/devtools-backend/src/ui/index.ts | 8 ++ 3 files changed, 121 insertions(+) create mode 100644 packages/devtools-backend/src/ui/README.md create mode 100644 packages/devtools-backend/src/ui/component-highlight.ts create mode 100644 packages/devtools-backend/src/ui/index.ts diff --git a/packages/devtools-backend/src/ui/README.md b/packages/devtools-backend/src/ui/README.md new file mode 100644 index 0000000..fc127cc --- /dev/null +++ b/packages/devtools-backend/src/ui/README.md @@ -0,0 +1,5 @@ +# UI + +These modules provide API to modify the UI of inspected window: + +- componentHighlight - displays an overlay over the component diff --git a/packages/devtools-backend/src/ui/component-highlight.ts b/packages/devtools-backend/src/ui/component-highlight.ts new file mode 100644 index 0000000..77e2e52 --- /dev/null +++ b/packages/devtools-backend/src/ui/component-highlight.ts @@ -0,0 +1,108 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import findComponentNode from '../search/find-component-node'; + +interface HideOptions { + animate?: boolean; + delay?: number; +} + +const + highlightNodeId = 'v4fire-devtools-highlight', + highlightAnimationDuration = 300; + +let animationTimeout: any; + +/** + * Show highlight for the component + * + * @param componentId + * @param componentName + */ +export function show(componentId: string, componentName: string): void { + const node = findComponentNode(componentId, componentName); + + if (node == null) { + return; + } + + clearTimeout(animationTimeout); + + const highlightNode = getOrCreateHighlightNode(); + + const {width, height, top, left} = node.getBoundingClientRect(); + highlightNode.style.width = `${width}px`; + highlightNode.style.height = `${height}px`; + highlightNode.style.top = `${(globalThis.scrollY + top)}px`; + highlightNode.style.left = `${left}px`; + highlightNode.style.display = 'block'; +} + +/** + * Hide component's highlight + * @param animateOrOptions + */ +export function hide(animateOrOptions?: boolean | HideOptions): void { + let animate = false; + let delay: number | null = null; + + if (typeof animateOrOptions === 'boolean') { + animate = animateOrOptions; + + } else if (typeof animateOrOptions === 'object') { + ({animate = false, delay = null} = animateOrOptions); + } + + const node = document.getElementById(highlightNodeId); + + if (node == null) { + return; + } + + if (animate) { + const end = () => { + animationTimeout = setTimeout(() => { + node.style.display = 'none'; + node.style.opacity = '1'; + }, highlightAnimationDuration); + }; + + const start = () => { + node.style.opacity = '0'; + + end(); + }; + + if (delay != null) { + animationTimeout = setTimeout(start, delay); + } else { + start(); + } + + } else { + node.style.display = 'none'; + } +} + +function getOrCreateHighlightNode(): HTMLElement { + let highlightNode = document.getElementById(highlightNodeId); + + if (highlightNode == null) { + highlightNode = document.createElement('div'); + highlightNode.id = highlightNodeId; + highlightNode.style.position = 'absolute'; + highlightNode.style.display = 'none'; + highlightNode.style.backgroundColor = 'rgba(250, 0, 250, 0.3)'; + highlightNode.style.zIndex = '9999'; + highlightNode.style.transition = `opacity ${highlightAnimationDuration}ms ease`; + document.body.appendChild(highlightNode); + } + + return highlightNode; +} diff --git a/packages/devtools-backend/src/ui/index.ts b/packages/devtools-backend/src/ui/index.ts new file mode 100644 index 0000000..1c10a61 --- /dev/null +++ b/packages/devtools-backend/src/ui/index.ts @@ -0,0 +1,8 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ +export * as componentHighlight from './component-highlight'; From c9f4a212b0bda3648aa18f4d58da8ce60dcbd5b6 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 16:59:08 +0300 Subject: [PATCH 16/47] feat(devtools-backend): export ui and search modules --- packages/devtools-backend/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/devtools-backend/src/index.ts b/packages/devtools-backend/src/index.ts index e758937..18d0247 100644 --- a/packages/devtools-backend/src/index.ts +++ b/packages/devtools-backend/src/index.ts @@ -7,3 +7,5 @@ */ export * from './serialize'; +export * from './search'; +export * from './ui'; From 9af9f5b6bab00f1545eda2e5a156ea8d852b8d77 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 17:00:03 +0300 Subject: [PATCH 17/47] feat(b-components-tree): add mouseenter and mouseleave props to item --- .../b-components-tree/b-components-tree.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts index a0c2c81..0035e8c 100644 --- a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts +++ b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts @@ -98,6 +98,9 @@ class bComponentsTree extends iBlock implements iSearch { ['children', 'folded', 'componentName', 'parentValue'] ); + props['@mouseenter'] = this.onItemMouseEnter.bind(this, item); + props['@mouseleave'] = this.onItemMouseLeave.bind(this, item); + return props; } @@ -151,6 +154,26 @@ class bComponentsTree extends iBlock implements iSearch { protected onTreeChange(_: unknown, componentId: string): void { this.emit('change', componentId); } + + /** + * Handle item mouseenter event + * + * @param item + * @param event + */ + protected onItemMouseEnter(item: Item, event: MouseEvent): void { + // TODO: use engine to highlight the component node + } + + /** + * Handle item mouseleave event + * + * @param item + * @param event + */ + protected onItemMouseLeave(item: Item, event: MouseEvent): void { + // TODO: use engine to remove highlight from the component node + } } export default bComponentsTree; From 599d2f6991c778b9b44c6df06c9c5bf91e86afbc Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 17:01:30 +0300 Subject: [PATCH 18/47] fix(eval-components-tree): log missing parent in map --- .../src/pages/p-components/p-components.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/devtools-extension/src/pages/p-components/p-components.ts b/packages/devtools-extension/src/pages/p-components/p-components.ts index 5800994..b845ffe 100644 --- a/packages/devtools-extension/src/pages/p-components/p-components.ts +++ b/packages/devtools-extension/src/pages/p-components/p-components.ts @@ -109,7 +109,14 @@ function evalComponentsTree(): Item[] { if (parentId != null) { if (!map.has(parentId)) { buffer.push(() => { - map.get(parentId).children.push(descriptor); + const item = map.get(parentId); + + if (item != null) { + item.children.push(descriptor); + + } else { + stderr(`Missing parent, component: ${component.componentName}, parent id: ${parentId}`); + } }); } else { From 33d7a050aa197c2e502e6361c36d7317bf85501e Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 17:01:48 +0300 Subject: [PATCH 19/47] chore(eval-component-meta): use findComponentNode --- .../src/pages/p-components/p-components.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/devtools-extension/src/pages/p-components/p-components.ts b/packages/devtools-extension/src/pages/p-components/p-components.ts index b845ffe..14dbcc4 100644 --- a/packages/devtools-extension/src/pages/p-components/p-components.ts +++ b/packages/devtools-extension/src/pages/p-components/p-components.ts @@ -142,16 +142,7 @@ function evalComponentMeta(value: string, name?: string): Nullable { 'LANG_PACKS' ]); - let node = Array.prototype.find.call( - document.querySelectorAll(`.i-block-helper.${value}`), - (node) => node.component?.componentId === value - ); - - if (node == null && name != null) { - // Maybe it's a functional component - const nodes = document.querySelectorAll(`.i-block-helper.${name}`); - node = Array.prototype.find.call(nodes, (node) => node.component?.componentId === value); - } + const node = globalThis.__V4FIRE_DEVTOOLS_BACKEND__.findComponentNode(value, name); if (node == null) { return null; From ebad7ec91b1c36795a97180ac0a053a5b2936d11 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 17:04:44 +0300 Subject: [PATCH 20/47] chore(p-devtools): render p-root on idle --- .../devtools-extension/src/pages/p-devtools/init.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/devtools-extension/src/pages/p-devtools/init.ts b/packages/devtools-extension/src/pages/p-devtools/init.ts index b1253ee..6bbdd83 100644 --- a/packages/devtools-extension/src/pages/p-devtools/init.ts +++ b/packages/devtools-extension/src/pages/p-devtools/init.ts @@ -7,7 +7,7 @@ */ import Async from '@v4fire/core/core/async'; -import { browserAPI } from 'core/browser-api'; +import { browserAPI, devtoolsEval } from 'core/browser-api'; import type pRoot from 'pages/p-root/p-root'; import { CouldNotFindV4FireOnThePageError, detectV4Fire } from 'pages/p-devtools/modules/detect-v4fire'; @@ -72,8 +72,12 @@ function mountDevToolsWhenV4FireHasLoaded() { injectBackend(browserAPI.devtools.inspectedWindow.tabId); if (shouldUpdateRoot) { - setRootPlaceholder(null); - shouldUpdateRoot = false; + devtoolsEval(() => new Promise((resolve) => globalThis.requestIdleCallback(resolve))) + .then(() => { + setRootPlaceholder(null); + shouldUpdateRoot = false; + }) + .catch(stderr); } }; From d2340e82eff26ac1b8e0c63d81e0199f9a93d214 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 27 Nov 2023 17:11:31 +0300 Subject: [PATCH 21/47] chore(devtools-extension): highlight component's node --- .../devtools-extension/components-lock.json | 2 +- .../b-components-tree/b-components-tree.ts | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts diff --git a/packages/devtools-extension/components-lock.json b/packages/devtools-extension/components-lock.json index 004130a..a440dcd 100644 --- a/packages/devtools-extension/components-lock.json +++ b/packages/devtools-extension/components-lock.json @@ -213,7 +213,7 @@ }, "type": "block", "mixin": false, - "logic": "node_modules/@v4fire/devtools-core/src/features/components/b-components-tree/b-components-tree.ts", + "logic": "src/features/components/b-components-tree/b-components-tree.ts", "styles": [ "node_modules/@v4fire/devtools-core/src/features/components/b-components-tree/b-components-tree.styl" ], diff --git a/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts b/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts new file mode 100644 index 0000000..ba7f0f0 --- /dev/null +++ b/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts @@ -0,0 +1,76 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import { devtoolsEval } from 'core/browser-api'; + +import { component, hook, system } from 'components/super/i-block/i-block'; + +import Super, { Item } from '@super/features/components/b-components-tree/b-components-tree'; + +@component() +export default class bComponentsTree extends Super { + + /** + * Id of highlighted component + */ + @system() + highlightedComponentId: string | null = null; + + @hook('mounted') + init(): void { + this.async.on(this.selfEmitter, 'change', (_: unknown, componentId: string) => { + const item = >this.$refs.tree?.getItemByValue(componentId); + + if (item != null) { + devtoolsEval( + evalHighlightActive, + [this.highlightedComponentId !== componentId, componentId, item.componentName] + ) + .catch(stderr); + } + }); + } + + /** + * Show highlight for the component on mouseenter + * + * @param item + */ + protected override onItemMouseEnter(item: Item): void { + this.highlightedComponentId = item.value; + devtoolsEval(evalShowComponentHighlight, [item.value, item.componentName]).catch(stderr); + } + + /** + * Hide highlight for the component + */ + protected override onItemMouseLeave(): void { + devtoolsEval(evalHideComponentHighlight).catch(stderr); + } +} + +function evalHighlightActive(autoHide: boolean, ...args: [string, string]): void { + const backend = globalThis.__V4FIRE_DEVTOOLS_BACKEND__; + const node = backend.findComponentNode(...args); + // @ts-expect-error Non-standard API + node?.scrollIntoViewIfNeeded(false); + + backend.componentHighlight.show(...args); + + if (autoHide) { + backend.componentHighlight.hide({delay: 1500, animate: true}); + } +} + +function evalShowComponentHighlight(...args: [string, string]): void { + globalThis.__V4FIRE_DEVTOOLS_BACKEND__.componentHighlight.show(...args); +} + +function evalHideComponentHighlight(): void { + globalThis.__V4FIRE_DEVTOOLS_BACKEND__.componentHighlight.hide(); +} From 02c6dcdd3bb505cf9354a485f36fddbbb1419b8f Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Sat, 9 Dec 2023 00:57:04 +0700 Subject: [PATCH 22/47] feat(b-header): add reload button --- packages/devtools-core/src/assets/svg/reload.svg | 5 +++++ .../src/components/widgets/b-header/b-header.ss | 3 +++ .../src/components/widgets/b-header/b-header.styl | 5 +++++ .../src/components/widgets/b-header/b-header.ts | 6 ++++++ .../devtools-core/src/components/widgets/b-header/index.js | 3 ++- 5 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 packages/devtools-core/src/assets/svg/reload.svg diff --git a/packages/devtools-core/src/assets/svg/reload.svg b/packages/devtools-core/src/assets/svg/reload.svg new file mode 100644 index 0000000..04c3a9e --- /dev/null +++ b/packages/devtools-core/src/assets/svg/reload.svg @@ -0,0 +1,5 @@ + + + diff --git a/packages/devtools-core/src/components/widgets/b-header/b-header.ss b/packages/devtools-core/src/components/widgets/b-header/b-header.ss index 1931396..29cd2a0 100644 --- a/packages/devtools-core/src/components/widgets/b-header/b-header.ss +++ b/packages/devtools-core/src/components/widgets/b-header/b-header.ss @@ -4,6 +4,9 @@ - template index() extends ['i-block'].index - block body + < .&__actions + < b-icon-button :icon = 'reload' | @click = onReload + < .&__tabs < b-button.&__tab @click = r.router.push('components') Components diff --git a/packages/devtools-core/src/components/widgets/b-header/b-header.styl b/packages/devtools-core/src/components/widgets/b-header/b-header.styl index cf7dfd6..f59a63b 100644 --- a/packages/devtools-core/src/components/widgets/b-header/b-header.styl +++ b/packages/devtools-core/src/components/widgets/b-header/b-header.styl @@ -1,8 +1,13 @@ @import "components/super/i-block/i-block.styl" b-header extends i-block + flex-row flex-start center + line-height 1 border-bottom 1px solid black + &__actions + padding 8px + &__tabs flex-row flex-start diff --git a/packages/devtools-core/src/components/widgets/b-header/b-header.ts b/packages/devtools-core/src/components/widgets/b-header/b-header.ts index ba6d648..ae6c9d9 100644 --- a/packages/devtools-core/src/components/widgets/b-header/b-header.ts +++ b/packages/devtools-core/src/components/widgets/b-header/b-header.ts @@ -16,4 +16,10 @@ export * from 'components/super/i-block/i-block'; @component() export default class bHeader extends iBlock { + /** + * Reloads the window + */ + onReload(): void { + globalThis.location.reload(); + } } diff --git a/packages/devtools-core/src/components/widgets/b-header/index.js b/packages/devtools-core/src/components/widgets/b-header/index.js index 011fe59..e4fc0e0 100644 --- a/packages/devtools-core/src/components/widgets/b-header/index.js +++ b/packages/devtools-core/src/components/widgets/b-header/index.js @@ -11,5 +11,6 @@ package('b-header') .extends('i-block') .dependencies( - 'b-button' + 'b-button', + 'b-icon-button' ) From 384c77ae65a699aa480f3c1ce991cf69ed27855f Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Sat, 9 Dec 2023 00:57:33 +0700 Subject: [PATCH 23/47] fix(b-components-tree): re-export from super --- .../features/components/b-components-tree/b-components-tree.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts b/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts index ba7f0f0..e2cf257 100644 --- a/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts +++ b/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts @@ -12,6 +12,8 @@ import { component, hook, system } from 'components/super/i-block/i-block'; import Super, { Item } from '@super/features/components/b-components-tree/b-components-tree'; +export * from '@super/features/components/b-components-tree/b-components-tree'; + @component() export default class bComponentsTree extends Super { From 72558c7980eb48d9fe9813122d1c3a405df41149 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Sat, 9 Dec 2023 00:58:12 +0700 Subject: [PATCH 24/47] chore(b-components-panel): add componentId to ComponentData type --- .../src/features/components/b-components-panel/interface.ts | 5 +++++ .../src/pages/p-components/p-components.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/devtools-core/src/features/components/b-components-panel/interface.ts b/packages/devtools-core/src/features/components/b-components-panel/interface.ts index 57f5069..b85939f 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/interface.ts +++ b/packages/devtools-core/src/features/components/b-components-panel/interface.ts @@ -17,6 +17,11 @@ export interface Item extends Super { } export type ComponentData = { + /** + * Component's id + */ + componentId: string; + /** * Component's values for props, fields, etc. */ diff --git a/packages/devtools-extension/src/pages/p-components/p-components.ts b/packages/devtools-extension/src/pages/p-components/p-components.ts index 14dbcc4..ec50f7a 100644 --- a/packages/devtools-extension/src/pages/p-components/p-components.ts +++ b/packages/devtools-extension/src/pages/p-components/p-components.ts @@ -174,7 +174,7 @@ function evalComponentMeta(value: string, name?: string): Nullable { parent = parent.parentMeta; } - const result = {componentName, props, fields, computedFields, systemFields, hierarchy, values}; + const result = {componentId: value, componentName, props, fields, computedFields, systemFields, hierarchy, values}; return globalThis.__V4FIRE_DEVTOOLS_BACKEND__.serialize( result, From f42ebf9116f69cc2ef6f264dc0d3c36629c34577 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Sat, 9 Dec 2023 00:59:05 +0700 Subject: [PATCH 25/47] feat(b-components-panel): use icons for actions --- .../devtools-core/src/assets/svg/circle-dashed.svg | 5 +++++ packages/devtools-core/src/assets/svg/circle.svg | 5 +++++ packages/devtools-core/src/assets/svg/inspect.svg | 5 +++++ .../b-components-panel/b-components-panel.ss | 9 ++++----- .../b-components-panel/b-components-panel.styl | 6 ++++++ .../b-components-panel/b-components-panel.ts | 14 +++++++++----- 6 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 packages/devtools-core/src/assets/svg/circle-dashed.svg create mode 100644 packages/devtools-core/src/assets/svg/circle.svg create mode 100644 packages/devtools-core/src/assets/svg/inspect.svg diff --git a/packages/devtools-core/src/assets/svg/circle-dashed.svg b/packages/devtools-core/src/assets/svg/circle-dashed.svg new file mode 100644 index 0000000..0a07b66 --- /dev/null +++ b/packages/devtools-core/src/assets/svg/circle-dashed.svg @@ -0,0 +1,5 @@ + + + diff --git a/packages/devtools-core/src/assets/svg/circle.svg b/packages/devtools-core/src/assets/svg/circle.svg new file mode 100644 index 0000000..64a57b4 --- /dev/null +++ b/packages/devtools-core/src/assets/svg/circle.svg @@ -0,0 +1,5 @@ + + + diff --git a/packages/devtools-core/src/assets/svg/inspect.svg b/packages/devtools-core/src/assets/svg/inspect.svg new file mode 100644 index 0000000..dded026 --- /dev/null +++ b/packages/devtools-core/src/assets/svg/inspect.svg @@ -0,0 +1,5 @@ + + + diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss index 2aed809..513b8b8 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss @@ -8,11 +8,10 @@ < b {{ componentData.componentName.camelize(false) }} - < b-checkbox & - :label = (showEmpty ? "hide" : "show") + " empty" | - :checked = showEmpty | - @change = showEmptyChange - . + < .&__header-actions + < b-icon-button :hint = "Inspect DOM" | :icon = 'inspect' | @click = onInspect + + < b-icon-button :hint = (showEmpty ? "Hide" : "Show") + " empty" | :icon = (showEmpty ? 'circle' : 'circle-dashed') | @click = onShowEmptyChange < .&__body < b-tree & diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl index 0c2a50f..5ccd219 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.styl @@ -4,9 +4,15 @@ b-components-panel extends i-block &__header display flex justify-content space-between + align-items center padding 8px border-bottom solid 1px black + &-actions + display flex + gap 4px + line-height 1 + &__body overflow auto padding 4px 0 diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts index 578752b..671483e 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts @@ -113,11 +113,15 @@ export default class bComponentsPanel extends iBlock { /** * Update show empty - * - * @param _ - * @param checked */ - protected showEmptyChange(_: unknown, checked?: boolean): void { - this.showEmpty = Boolean(checked); + protected onShowEmptyChange(): void { + this.showEmpty = !this.showEmpty; + } + + /** + * Inspect component node + */ + protected onInspect(): void { + // TODO: use inspected app } } From 7f09c6b1cc226dd1b5d4c325ee7f27ad62811863 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Sat, 9 Dec 2023 01:03:59 +0700 Subject: [PATCH 26/47] feat(b-components-panel): implement onInspect method --- .../b-components-panel/b-components-panel.ts | 2 +- .../b-components-panel/b-components-panel.ts | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 packages/devtools-extension/src/features/components/b-components-panel/b-components-panel.ts diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts index 671483e..b7194b7 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts @@ -119,7 +119,7 @@ export default class bComponentsPanel extends iBlock { } /** - * Inspect component node + * Inspect component's node */ protected onInspect(): void { // TODO: use inspected app diff --git a/packages/devtools-extension/src/features/components/b-components-panel/b-components-panel.ts b/packages/devtools-extension/src/features/components/b-components-panel/b-components-panel.ts new file mode 100644 index 0000000..f3b0421 --- /dev/null +++ b/packages/devtools-extension/src/features/components/b-components-panel/b-components-panel.ts @@ -0,0 +1,43 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ +import { devtoolsEval } from 'core/browser-api'; + +import { component } from 'components/super/i-block/i-block'; + +import Super from '@super/features/components/b-components-panel/b-components-panel'; + +@component() +export default class bComponentsPanel extends Super { + protected override onInspect(): void { + const {componentId, componentName} = this.componentData; + + devtoolsEval(evalInspect, [componentId, componentName]) + .catch(stderr); + } +} + +function evalInspect(componentId: string, componentName: string): void { + // eslint-disable-next-line @typescript-eslint/method-signature-style + const {inspect} = <{inspect?: (el: Element) => void} & Global>globalThis; + + if (typeof inspect !== 'function') { + // eslint-disable-next-line no-alert + alert('Browser doesn\'t provide inspect util'); + return; + } + + const node = globalThis.__V4FIRE_DEVTOOLS_BACKEND__.findComponentNode(componentId, componentName); + + if (node != null) { + inspect(node); + + } else { + // eslint-disable-next-line no-alert + alert('Component\'s node not found'); + } +} From 598a7b75f0c4ba621ec9208c0e3256b985e226d8 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Sat, 9 Dec 2023 01:04:10 +0700 Subject: [PATCH 27/47] chore: update components lock --- packages/devtools-extension/components-lock.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/devtools-extension/components-lock.json b/packages/devtools-extension/components-lock.json index a440dcd..12a1c10 100644 --- a/packages/devtools-extension/components-lock.json +++ b/packages/devtools-extension/components-lock.json @@ -1,5 +1,5 @@ { - "hash": "0b67a39d6be3b70d606ea68479aa6289d5cd8891cc63e67d8f8d90fad4881b14", + "hash": "6ac41a6ba1ed10dacc79208cd6792a9febe8c4f86e48d75037699688c52293fe", "data": { "%data": "%data:Map", "%data:Map": [ @@ -143,7 +143,7 @@ }, "type": "block", "mixin": false, - "logic": "node_modules/@v4fire/devtools-core/src/features/components/b-components-panel/b-components-panel.ts", + "logic": "src/features/components/b-components-panel/b-components-panel.ts", "styles": [ "node_modules/@v4fire/devtools-core/src/features/components/b-components-panel/b-components-panel.styl" ], @@ -749,14 +749,16 @@ "name": "b-header", "parent": "i-block", "dependencies": [ - "b-button" + "b-button", + "b-icon-button" ], "libs": [] }, "name": "b-header", "parent": "i-block", "dependencies": [ - "b-button" + "b-button", + "b-icon-button" ], "libs": [], "resolvedLibs": { From 03476bb5bacca2d791e88739cff700b4c68aae48 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Sat, 9 Dec 2023 01:04:59 +0700 Subject: [PATCH 28/47] chore(p-devtools): do not inject backend for window without tabId --- packages/devtools-extension/src/pages/p-devtools/init.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/devtools-extension/src/pages/p-devtools/init.ts b/packages/devtools-extension/src/pages/p-devtools/init.ts index 6bbdd83..2c8fd5d 100644 --- a/packages/devtools-extension/src/pages/p-devtools/init.ts +++ b/packages/devtools-extension/src/pages/p-devtools/init.ts @@ -69,7 +69,10 @@ function mountDevToolsWhenV4FireHasLoaded() { ); // Inject backend only when the v4fire is mounted - injectBackend(browserAPI.devtools.inspectedWindow.tabId); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (browserAPI.devtools.inspectedWindow.tabId != null) { + injectBackend(browserAPI.devtools.inspectedWindow.tabId); + } if (shouldUpdateRoot) { devtoolsEval(() => new Promise((resolve) => globalThis.requestIdleCallback(resolve))) From de9b30e646221aafa17a87dd0417d2ce531153b1 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Sat, 9 Dec 2023 01:05:19 +0700 Subject: [PATCH 29/47] chore: update @v4fire/client --- yarn.lock | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0fd0011..1a3d15b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2710,8 +2710,8 @@ __metadata: linkType: hard "@v4fire/client@git+https://github.com/V4Fire/Client.git#v4": - version: 4.0.0-beta.38 - resolution: "@v4fire/client@https://github.com/V4Fire/Client.git#commit=a3053782d1219fc3525f44aad2073882dc4d7b37" + version: 4.0.0-beta.45 + resolution: "@v4fire/client@https://github.com/V4Fire/Client.git#commit=3e73bf79b4e8b9a3c3e183fc44fc38679f295582" dependencies: "@babel/core": 7.17.5 "@babel/helpers": 7.17.7 @@ -2778,6 +2778,7 @@ __metadata: path-to-regexp: 3.2.0 portfinder: 1.0.28 postcss: 8.4.6 + postcss-discard-comments: 6.0.0 postcss-loader: 6.2.1 raw-loader: 4.0.2 requestidlecallback: 0.3.0 @@ -2795,6 +2796,7 @@ __metadata: svgo-loader: 3.0.0 svgo-sync: 0.5.1 terser-webpack-plugin: 5.3.1 + to-string-loader: ^1.2.0 ts-loader: 9.2.6 tslib: 2.4.1 typescript: 4.6.2 @@ -2907,6 +2909,8 @@ __metadata: optional: true postcss: optional: true + postcss-discard-comments: + optional: true postcss-loader: optional: true raw-loader: @@ -2935,6 +2939,8 @@ __metadata: optional: true terser-webpack-plugin: optional: true + to-string-loader: + optional: true ts-loader: optional: true typescript: @@ -2949,7 +2955,7 @@ __metadata: optional: true webpack-cli: optional: true - checksum: 9b63121ea771ec519b59cbe3f53729deea3c5e7525729501df35fd02af95d0f9139e900957cbe818498b85522529d6d17f71fa201832ada8842444231cf912b7 + checksum: 5801744827a0c3d60c11e75d4bbc18312feff82a07d44d5087e3141b04b7903efa553c464c4e5f957b1b200873b2d9284f57296b4f5b5ea37486dcd8547e3a96 languageName: node linkType: hard @@ -12123,7 +12129,7 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^1.0.3, loader-utils@npm:^1.1.0": +"loader-utils@npm:^1.0.0, loader-utils@npm:^1.0.3, loader-utils@npm:^1.1.0": version: 1.4.2 resolution: "loader-utils@npm:1.4.2" dependencies: @@ -14549,6 +14555,15 @@ __metadata: languageName: node linkType: hard +"postcss-discard-comments@npm:6.0.0": + version: 6.0.0 + resolution: "postcss-discard-comments@npm:6.0.0" + peerDependencies: + postcss: ^8.2.15 + checksum: 9be073707b5ef781c616ddd32ffd98faf14bf8b40027f341d5a4fb7989fa7b017087ad54146a370fe38295b1f2568b9f5522f4e4c1a1d09fe0e01abd9f5ae00d + languageName: node + linkType: hard + "postcss-discard-comments@npm:^5.1.2": version: 5.1.2 resolution: "postcss-discard-comments@npm:5.1.2" @@ -17776,6 +17791,15 @@ __metadata: languageName: node linkType: hard +"to-string-loader@npm:^1.2.0": + version: 1.2.0 + resolution: "to-string-loader@npm:1.2.0" + dependencies: + loader-utils: ^1.0.0 + checksum: 738d51379aab962c843b0764335b0a1f89f42402b18c1a75d1e2653ef938702a7a6f132cfe7fb888cd14ca2e9a76ed779f9be34ea0a257c500d3f8edde8a1140 + languageName: node + linkType: hard + "to-through@npm:^2.0.0": version: 2.0.0 resolution: "to-through@npm:2.0.0" From 9c23e91710d560b9c26c0a7e86eee608c763ad55 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 16:45:39 +0700 Subject: [PATCH 30/47] chore(devtools-backend): make component-highlight a singleton --- .../src/ui/component-highlight.ts | 117 ++++++++++-------- packages/devtools-backend/src/ui/index.ts | 2 +- 2 files changed, 64 insertions(+), 55 deletions(-) diff --git a/packages/devtools-backend/src/ui/component-highlight.ts b/packages/devtools-backend/src/ui/component-highlight.ts index 77e2e52..1641afc 100644 --- a/packages/devtools-backend/src/ui/component-highlight.ts +++ b/packages/devtools-backend/src/ui/component-highlight.ts @@ -17,79 +17,88 @@ const highlightNodeId = 'v4fire-devtools-highlight', highlightAnimationDuration = 300; -let animationTimeout: any; +class ComponentHighlight { + /** + * Highlight animation timeout + */ + #animationTimeout: any; + + /** + * Show highlight for the component + * + * @param componentId + * @param componentName + */ + show(componentId: string, componentName: string): void { + const node = findComponentNode(componentId, componentName); + + if (node == null) { + return; + } -/** - * Show highlight for the component - * - * @param componentId - * @param componentName - */ -export function show(componentId: string, componentName: string): void { - const node = findComponentNode(componentId, componentName); + clearTimeout(this.#animationTimeout); - if (node == null) { - return; - } + const highlightNode = getOrCreateHighlightNode(); - clearTimeout(animationTimeout); + const {width, height, top, left} = node.getBoundingClientRect(); + highlightNode.style.width = `${width}px`; + highlightNode.style.height = `${height}px`; + highlightNode.style.top = `${(globalThis.scrollY + top)}px`; + highlightNode.style.left = `${left}px`; + highlightNode.style.opacity = '1'; + highlightNode.style.display = 'block'; + } - const highlightNode = getOrCreateHighlightNode(); + /** + * Hide component's highlight + * @param animateOrOptions + */ + hide(animateOrOptions?: boolean | HideOptions): void { + let animate = false; + let delay: number | null = null; - const {width, height, top, left} = node.getBoundingClientRect(); - highlightNode.style.width = `${width}px`; - highlightNode.style.height = `${height}px`; - highlightNode.style.top = `${(globalThis.scrollY + top)}px`; - highlightNode.style.left = `${left}px`; - highlightNode.style.display = 'block'; -} + if (typeof animateOrOptions === 'boolean') { + animate = animateOrOptions; -/** - * Hide component's highlight - * @param animateOrOptions - */ -export function hide(animateOrOptions?: boolean | HideOptions): void { - let animate = false; - let delay: number | null = null; + } else if (typeof animateOrOptions === 'object') { + ({animate = false, delay = null} = animateOrOptions); + } - if (typeof animateOrOptions === 'boolean') { - animate = animateOrOptions; + const node = document.getElementById(highlightNodeId); - } else if (typeof animateOrOptions === 'object') { - ({animate = false, delay = null} = animateOrOptions); - } + if (node == null) { + return; + } - const node = document.getElementById(highlightNodeId); + if (animate) { + clearTimeout(this.#animationTimeout); - if (node == null) { - return; - } + const end = () => { + this.#animationTimeout = setTimeout(() => { + node.style.display = 'none'; + }, highlightAnimationDuration); + }; - if (animate) { - const end = () => { - animationTimeout = setTimeout(() => { - node.style.display = 'none'; - node.style.opacity = '1'; - }, highlightAnimationDuration); - }; + const start = () => { + node.style.opacity = '0'; - const start = () => { - node.style.opacity = '0'; + end(); + }; - end(); - }; + if (delay != null) { + this.#animationTimeout = setTimeout(start, delay); + } else { + start(); + } - if (delay != null) { - animationTimeout = setTimeout(start, delay); } else { - start(); + node.style.display = 'none'; } - - } else { - node.style.display = 'none'; } } +export const componentHighlight = new ComponentHighlight(); + function getOrCreateHighlightNode(): HTMLElement { let highlightNode = document.getElementById(highlightNodeId); diff --git a/packages/devtools-backend/src/ui/index.ts b/packages/devtools-backend/src/ui/index.ts index 1c10a61..fb28900 100644 --- a/packages/devtools-backend/src/ui/index.ts +++ b/packages/devtools-backend/src/ui/index.ts @@ -5,4 +5,4 @@ * Released under the MIT license * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ -export * as componentHighlight from './component-highlight'; +export { componentHighlight } from './component-highlight'; From bc57902b35cd881c145ceb0d3ac35122a4297361 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 20:05:08 +0700 Subject: [PATCH 31/47] chore(devtools-backend): disable pointer events for component highlight --- packages/devtools-backend/src/ui/component-highlight.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/devtools-backend/src/ui/component-highlight.ts b/packages/devtools-backend/src/ui/component-highlight.ts index 1641afc..039ac7e 100644 --- a/packages/devtools-backend/src/ui/component-highlight.ts +++ b/packages/devtools-backend/src/ui/component-highlight.ts @@ -110,6 +110,7 @@ function getOrCreateHighlightNode(): HTMLElement { highlightNode.style.backgroundColor = 'rgba(250, 0, 250, 0.3)'; highlightNode.style.zIndex = '9999'; highlightNode.style.transition = `opacity ${highlightAnimationDuration}ms ease`; + highlightNode.style.pointerEvents = 'none'; document.body.appendChild(highlightNode); } From 505d9f3165b2a7d0552f850c2814cad67e4032e5 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 20:06:56 +0700 Subject: [PATCH 32/47] feat(devtools-backend): create ComponentLocate class It allows to locate the component using the mouse --- .../src/ui/component-locate.ts | 89 +++++++++++++++++++ packages/devtools-backend/src/ui/index.ts | 1 + 2 files changed, 90 insertions(+) create mode 100644 packages/devtools-backend/src/ui/component-locate.ts diff --git a/packages/devtools-backend/src/ui/component-locate.ts b/packages/devtools-backend/src/ui/component-locate.ts new file mode 100644 index 0000000..f4fbc57 --- /dev/null +++ b/packages/devtools-backend/src/ui/component-locate.ts @@ -0,0 +1,89 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import { componentHighlight } from './component-highlight'; + +interface ComponentInterface { + componentId: string; + componentName: string; +} + +class ComponentLocate { + #mouseoverListener: ((e: MouseEvent) => void) | null = null; + + #clickListener: ((e: MouseEvent) => void) | null = null; + + enable() { + this.disable(); + + let component: ComponentInterface | null = null; + + this.#mouseoverListener = (e: MouseEvent) => { + component = e.target instanceof Element ? findComponent(e.target) : null; + + if (component != null) { + const {componentId, componentName} = component; + componentHighlight.show(componentId, componentName); + + } else { + componentHighlight.hide(); + } + }; + + this.#clickListener = (e) => { + e.preventDefault(); + e.stopPropagation(); + + this.disable(); + + if (component != null) { + componentHighlight.hide({animate: true}); + // TODO: notify devtools application about selected component + } + }; + + document.addEventListener('mouseover', this.#mouseoverListener); + document.addEventListener('click', this.#clickListener, {capture: true}); + } + + disable() { + if (this.#mouseoverListener != null) { + document.removeEventListener('mouseover', this.#mouseoverListener); + this.#mouseoverListener = null; + } + + if (this.#clickListener != null) { + document.removeEventListener('click', this.#clickListener, {capture: true}); + this.#clickListener = null; + } + } +} + +export const componentLocate = new ComponentLocate(); + +function findComponent(target: Element | null): ComponentInterface | null { + let component: ComponentInterface | null = null; + + while (component == null && target != null) { + const node: Element & {component?: ComponentInterface} | null = target.closest('.i-block-helper'); + + if (node == null) { + break; + } + + if (node.component != null) { + ({component} = node); + + } else { + // If `component` property is missing - search higher in hierarchy + target = node.parentNode; + } + } + + return component; +} diff --git a/packages/devtools-backend/src/ui/index.ts b/packages/devtools-backend/src/ui/index.ts index fb28900..f0f697e 100644 --- a/packages/devtools-backend/src/ui/index.ts +++ b/packages/devtools-backend/src/ui/index.ts @@ -6,3 +6,4 @@ * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ export { componentHighlight } from './component-highlight'; +export { componentLocate } from './component-locate'; From 91e7018dfc00baf886115c17ac8b71d581b4d5ea Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 20:10:57 +0700 Subject: [PATCH 33/47] chore(devtools-core): add crosshair icon --- packages/devtools-core/src/assets/svg/crosshair.svg | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/devtools-core/src/assets/svg/crosshair.svg diff --git a/packages/devtools-core/src/assets/svg/crosshair.svg b/packages/devtools-core/src/assets/svg/crosshair.svg new file mode 100644 index 0000000..011c425 --- /dev/null +++ b/packages/devtools-core/src/assets/svg/crosshair.svg @@ -0,0 +1,5 @@ + + + From 665e713fda836dcdabdd945c14065741d7cf5112 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 20:12:12 +0700 Subject: [PATCH 34/47] feat(devtools-core): create b-components-actions --- .../b-components-actions.ss | 18 +++++++++++ .../b-components-actions.styl | 9 ++++++ .../b-components-actions.ts | 31 +++++++++++++++++++ .../components/b-components-actions/index.js | 17 ++++++++++ 4 files changed, 75 insertions(+) create mode 100644 packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ss create mode 100644 packages/devtools-core/src/features/components/b-components-actions/b-components-actions.styl create mode 100644 packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ts create mode 100644 packages/devtools-core/src/features/components/b-components-actions/index.js diff --git a/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ss b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ss new file mode 100644 index 0000000..86d6c3a --- /dev/null +++ b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ss @@ -0,0 +1,18 @@ +- namespace [%fileName%] + +- include 'components/super/i-block'|b as placeholder + +- template index() extends ['i-block'].index + - block body + < b-icon-button :icon = 'crosshair' | @click = enableLocateComponent + + < b-window & + ref = modal | + @close = disableLocateComponent + . + < template #body + < b.&__modal-body + Click on a component on the page to select it + < template #controls + < b-button @click = disableLocateComponent + Cancel diff --git a/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.styl b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.styl new file mode 100644 index 0000000..3d6fc20 --- /dev/null +++ b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.styl @@ -0,0 +1,9 @@ +@import "components/super/i-block/i-block.styl" + +b-components-actions extends i-block + display flex + line-height 1 + + &__modal-body + display block + padding 16px diff --git a/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ts b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ts new file mode 100644 index 0000000..ee7972f --- /dev/null +++ b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ts @@ -0,0 +1,31 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import iBlock, { component } from 'components/super/i-block/i-block'; +import type bWindow from 'components/base/b-window/b-window'; + +@component() +export default class bComponentsActions extends iBlock { + override readonly $refs!: iBlock['$refs'] & { + modal?: bWindow; + }; + + /** + * Enables component search via DOM + */ + enableLocateComponent(): void { + this.$refs.modal?.open().catch(stderr); + } + + /** + * Disables component search via DOM + */ + disableLocateComponent(): void { + this.$refs.modal?.close().catch(stderr); + } +} diff --git a/packages/devtools-core/src/features/components/b-components-actions/index.js b/packages/devtools-core/src/features/components/b-components-actions/index.js new file mode 100644 index 0000000..c450c52 --- /dev/null +++ b/packages/devtools-core/src/features/components/b-components-actions/index.js @@ -0,0 +1,17 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +'use strict'; + +package('b-components-actions') + .extends('i-block') + .dependencies( + 'b-icon-button', + 'b-button', + 'b-window' + ); From 8d56c3de199f52682bafe03b87fb598566946add Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 20:12:45 +0700 Subject: [PATCH 35/47] chore(b-header): integrate b-components-actions --- .../devtools-core/src/components/widgets/b-header/b-header.ss | 2 ++ .../src/components/widgets/b-header/b-header.styl | 2 ++ .../devtools-core/src/components/widgets/b-header/index.js | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/devtools-core/src/components/widgets/b-header/b-header.ss b/packages/devtools-core/src/components/widgets/b-header/b-header.ss index 29cd2a0..73ef34e 100644 --- a/packages/devtools-core/src/components/widgets/b-header/b-header.ss +++ b/packages/devtools-core/src/components/widgets/b-header/b-header.ss @@ -7,6 +7,8 @@ < .&__actions < b-icon-button :icon = 'reload' | @click = onReload + < b-components-actions v-if = r.activePage === 'components' + < .&__tabs < b-button.&__tab @click = r.router.push('components') Components diff --git a/packages/devtools-core/src/components/widgets/b-header/b-header.styl b/packages/devtools-core/src/components/widgets/b-header/b-header.styl index f59a63b..c15d2f1 100644 --- a/packages/devtools-core/src/components/widgets/b-header/b-header.styl +++ b/packages/devtools-core/src/components/widgets/b-header/b-header.styl @@ -6,6 +6,8 @@ b-header extends i-block border-bottom 1px solid black &__actions + flex-row flex-start center + gap 8px padding 8px &__tabs diff --git a/packages/devtools-core/src/components/widgets/b-header/index.js b/packages/devtools-core/src/components/widgets/b-header/index.js index e4fc0e0..a7ed14e 100644 --- a/packages/devtools-core/src/components/widgets/b-header/index.js +++ b/packages/devtools-core/src/components/widgets/b-header/index.js @@ -12,5 +12,6 @@ package('b-header') .extends('i-block') .dependencies( 'b-button', - 'b-icon-button' + 'b-icon-button', + 'b-components-actions' ) From f7b29b211046a36206219b60c8df20d50698f080 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 20:13:31 +0700 Subject: [PATCH 36/47] chore(b-components-actions): add override for devtools-extension --- .../b-components-actions.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 packages/devtools-extension/src/features/components/b-components-actions/b-components-actions.ts diff --git a/packages/devtools-extension/src/features/components/b-components-actions/b-components-actions.ts b/packages/devtools-extension/src/features/components/b-components-actions/b-components-actions.ts new file mode 100644 index 0000000..0f5da54 --- /dev/null +++ b/packages/devtools-extension/src/features/components/b-components-actions/b-components-actions.ts @@ -0,0 +1,34 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import { component } from 'components/super/i-block/i-block'; +import Super from '@super/features/components/b-components-actions/b-components-actions'; + +import { devtoolsEval } from 'core/browser-api'; + +@component() +export default class bComponentsActions extends Super { + override enableLocateComponent(): void { + super.enableLocateComponent(); + + // TODO: use InspectedApp interface + devtoolsEval(() => globalThis.__V4FIRE_DEVTOOLS_BACKEND__.componentLocate.enable()) + .catch(stderr); + } + + override disableLocateComponent(): void { + super.disableLocateComponent(); + + // TODO: use InspectedApp interface + devtoolsEval(() => { + globalThis.__V4FIRE_DEVTOOLS_BACKEND__.componentHighlight.hide(); + globalThis.__V4FIRE_DEVTOOLS_BACKEND__.componentLocate.disable(); + }) + .catch(stderr); + } +} From 61acbe288085c06aeca5ae12c108d436d0ff4b18 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 20:13:36 +0700 Subject: [PATCH 37/47] chore: update components lock --- .../devtools-extension/components-lock.json | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/devtools-extension/components-lock.json b/packages/devtools-extension/components-lock.json index 12a1c10..749b10e 100644 --- a/packages/devtools-extension/components-lock.json +++ b/packages/devtools-extension/components-lock.json @@ -1,5 +1,5 @@ { - "hash": "6ac41a6ba1ed10dacc79208cd6792a9febe8c4f86e48d75037699688c52293fe", + "hash": "7c0fc3586bfa5c89c9ec9aa033da0ec91fc13c9afba1c020b37f858c5d51936f", "data": { "%data": "%data:Map", "%data:Map": [ @@ -111,6 +111,46 @@ "etpl": null } ], + [ + "b-components-actions", + { + "index": "node_modules/@v4fire/devtools-core/src/features/components/b-components-actions/index.js", + "declaration": { + "name": "b-components-actions", + "parent": "i-block", + "dependencies": [ + "b-icon-button", + "b-button", + "b-window" + ], + "libs": [] + }, + "name": "b-components-actions", + "parent": "i-block", + "dependencies": [ + "b-icon-button", + "b-button", + "b-window" + ], + "libs": [], + "resolvedLibs": { + "%data": "%data:Set", + "%data:Set": [] + }, + "resolvedOwnLibs": { + "%data": "%data:Set", + "%data:Set": [] + }, + "type": "block", + "mixin": false, + "logic": "src/features/components/b-components-actions/b-components-actions.ts", + "styles": [ + "node_modules/@v4fire/devtools-core/src/features/components/b-components-actions/b-components-actions.styl" + ], + "tpl": "node_modules/@v4fire/devtools-core/src/features/components/b-components-actions/b-components-actions.ss", + "etpl": null + } + ], [ "b-components-panel", { @@ -750,7 +790,8 @@ "parent": "i-block", "dependencies": [ "b-button", - "b-icon-button" + "b-icon-button", + "b-components-actions" ], "libs": [] }, @@ -758,7 +799,8 @@ "parent": "i-block", "dependencies": [ "b-button", - "b-icon-button" + "b-icon-button", + "b-components-actions" ], "libs": [], "resolvedLibs": { From 9bfc09ddbfb5e0dd165bd864a0ddf4e989487dda Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 11 Dec 2023 20:59:35 +0700 Subject: [PATCH 38/47] fix(b-components-panel): hint position --- .../b-components-panel/b-components-panel.ss | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss index 513b8b8..07e017e 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ss @@ -9,9 +9,19 @@ {{ componentData.componentName.camelize(false) }} < .&__header-actions - < b-icon-button :hint = "Inspect DOM" | :icon = 'inspect' | @click = onInspect + < b-icon-button & + :hint = "Inspect DOM" | + :hintPos = 'bottom-left' | + :icon = 'inspect' | + @click = onInspect + . - < b-icon-button :hint = (showEmpty ? "Hide" : "Show") + " empty" | :icon = (showEmpty ? 'circle' : 'circle-dashed') | @click = onShowEmptyChange + < b-icon-button & + :hint = (showEmpty ? "Hide" : "Show") + " empty" | + :hintPos = 'bottom-left' | + :icon = (showEmpty ? 'circle' : 'circle-dashed') | + @click = onShowEmptyChange + . < .&__body < b-tree & From 8438268f04b401c55a13fc195598aaffc49428b5 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:07:22 +0700 Subject: [PATCH 39/47] chore: add hints for components actions --- .../src/components/widgets/b-header/b-header.ss | 7 ++++++- .../b-components-actions/b-components-actions.ss | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/devtools-core/src/components/widgets/b-header/b-header.ss b/packages/devtools-core/src/components/widgets/b-header/b-header.ss index 73ef34e..a252793 100644 --- a/packages/devtools-core/src/components/widgets/b-header/b-header.ss +++ b/packages/devtools-core/src/components/widgets/b-header/b-header.ss @@ -5,7 +5,12 @@ - template index() extends ['i-block'].index - block body < .&__actions - < b-icon-button :icon = 'reload' | @click = onReload + < b-icon-button & + :icon = 'reload' | + @click = onReload | + :hint = 'Reload tree' | + :hintPos = 'bottom-right' + . < b-components-actions v-if = r.activePage === 'components' diff --git a/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ss b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ss index 86d6c3a..c60d5ac 100644 --- a/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ss +++ b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ss @@ -4,7 +4,12 @@ - template index() extends ['i-block'].index - block body - < b-icon-button :icon = 'crosshair' | @click = enableLocateComponent + < b-icon-button & + :icon = 'crosshair' | + @click = enableLocateComponent | + :hint = 'Select component in the page' | + :hintPos = 'bottom-right' + . < b-window & ref = modal | From cb3c59a8422938059eaa88ebf811ee7c810bfee7 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:09:21 +0700 Subject: [PATCH 40/47] refactor(BrowserTabManager): create helpers and handle proxy connections --- .../src/sw/browser/browser-tab-manager.ts | 83 ------------ .../tab-manager/helpers/connect-tab-ports.ts | 63 ++++++++++ .../helpers/handle-port-disconnect.ts | 31 +++++ .../sw/browser/tab-manager/helpers/index.ts | 11 ++ .../src/sw/browser/tab-manager/index.ts | 119 ++++++++++++++++++ .../src/sw/browser/tab-manager/interface.ts | 31 +++++ packages/devtools-extension/src/sw/index.ts | 2 +- 7 files changed, 256 insertions(+), 84 deletions(-) delete mode 100644 packages/devtools-extension/src/sw/browser/browser-tab-manager.ts create mode 100644 packages/devtools-extension/src/sw/browser/tab-manager/helpers/connect-tab-ports.ts create mode 100644 packages/devtools-extension/src/sw/browser/tab-manager/helpers/handle-port-disconnect.ts create mode 100644 packages/devtools-extension/src/sw/browser/tab-manager/helpers/index.ts create mode 100644 packages/devtools-extension/src/sw/browser/tab-manager/index.ts create mode 100644 packages/devtools-extension/src/sw/browser/tab-manager/interface.ts diff --git a/packages/devtools-extension/src/sw/browser/browser-tab-manager.ts b/packages/devtools-extension/src/sw/browser/browser-tab-manager.ts deleted file mode 100644 index 2d1910b..0000000 --- a/packages/devtools-extension/src/sw/browser/browser-tab-manager.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * V4Fire DevTools - * https://github.com/V4Fire/DevTools - * - * Released under the MIT license - * https://github.com/V4Fire/DevTools/blob/main/LICENSE - */ - -import { browserAPI } from 'core/browser-api'; - -interface Tab { - /** - * Port of the devtools page (V4Fire tab in the browser devtools) - */ - devtoolsPort: chrome.runtime.Port | null; -} - -/** - * This class manages communication between the browser tabs and the devtools page - * by forwarding messages from and to their ports - */ -export default class BrowserTabManager { - /** - * All managed tabs by the extension service worker - */ - tabs: Map = new Map(); - - /** - * Register a new tab - * @param tabId - */ - register(tabId: number): Tab { - if (!this.tabs.has(tabId)) { - this.tabs.set(tabId, {devtoolsPort: null}); - } - - return this.tabs.get(tabId)!; - } - - /** - * Unregister a tab - * @param tabId - */ - unregister(tabId: number): void { - const tab = this.tabs.get(tabId); - - if (tab != null) { - this.cleanup(tab); - } - } - - /** - * Cleanup tab connections - * @param tab - */ - cleanup(tab: Tab): void { - // Perform cleanups: drop all connections, etc. - tab.devtoolsPort?.disconnect(); - } - - /** - * Listen for incoming connections from devtools or the active tab - */ - listen(): void { - browserAPI.runtime.onConnect.addListener((port) => { - // Connection from devtools page - if (/^\d+$/.test(port.name)) { - // DevTools page port doesn't have tab id specified because its sender is the extension - // so the tab id is encoded as the name of the port - const - tabId = Number(port.name), - tab = this.register(tabId); - - // eslint-disable-next-line no-console - console.log('New connection from devtools, tabId:', tabId); - - tab.devtoolsPort = port; - } - - // TODO: Handle connection from content scripts - }); - } -} diff --git a/packages/devtools-extension/src/sw/browser/tab-manager/helpers/connect-tab-ports.ts b/packages/devtools-extension/src/sw/browser/tab-manager/helpers/connect-tab-ports.ts new file mode 100644 index 0000000..58778b0 --- /dev/null +++ b/packages/devtools-extension/src/sw/browser/tab-manager/helpers/connect-tab-ports.ts @@ -0,0 +1,63 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import type { Tab } from 'sw/browser/tab-manager/interface'; + +/** + * Enables message proxy between devtools and proxy port + * + * @param tab + * @throws {Error} - in case ports are already connected + */ +export default function connectTabPorts(tab: Tab): void { + const devtoolsPort = tab.ports.devtools; + const proxyPort = tab.ports.proxy; + + if (devtoolsPort == null || proxyPort == null) { + return; + } + + if (tab.disconnect != null) { + throw new Error( + `Attempted to connect already connected ports for tab with id ${tab.id}` + ); + } + + const + devtoolsPortMessageListener = createPortMessageListener(proxyPort), + proxyPortMessageListener = createPortMessageListener(devtoolsPort); + + const disconnectListener = () => { + devtoolsPort.onMessage.removeListener(devtoolsPortMessageListener); + proxyPort.onMessage.removeListener(proxyPortMessageListener); + + // We handle disconnect() calls manually, based on each specific case + // No need to disconnect other port here + tab.disconnect = null; + }; + + function createPortMessageListener(portOut: chrome.runtime.Port) { + return (message: unknown) => { + try { + portOut.postMessage(message); + } catch (e) { + stderr(new Error(`Broken pipe ${tab.id}`, {cause: e})); + + disconnectListener(); + } + }; + } + + tab.disconnect = disconnectListener; + + devtoolsPort.onMessage.addListener(devtoolsPortMessageListener); + proxyPort.onMessage.addListener(proxyPortMessageListener); + + devtoolsPort.onDisconnect.addListener(disconnectListener); + proxyPort.onDisconnect.addListener(disconnectListener); +} diff --git a/packages/devtools-extension/src/sw/browser/tab-manager/helpers/handle-port-disconnect.ts b/packages/devtools-extension/src/sw/browser/tab-manager/helpers/handle-port-disconnect.ts new file mode 100644 index 0000000..15cc5e9 --- /dev/null +++ b/packages/devtools-extension/src/sw/browser/tab-manager/helpers/handle-port-disconnect.ts @@ -0,0 +1,31 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import type { Tab } from 'sw/browser/tab-manager/interface'; + +/** + * Adds listener for port's disconnect event + * + * @param tab + * @param portKey + */ +export default function handlePortDisconnect( + tab: Tab, + portKey: keyof Tab['ports'] +): void { + const port = tab.ports[portKey]; + + // In case proxy port was disconnected from the other end, from content script + // This can happen if content script was detached, when user does in-tab navigation + // This listener should never be called when we call port.disconnect() from this service worker + port?.onDisconnect.addListener(() => { + tab.disconnect?.(); + + tab.ports[portKey] = null; + }); +} diff --git a/packages/devtools-extension/src/sw/browser/tab-manager/helpers/index.ts b/packages/devtools-extension/src/sw/browser/tab-manager/helpers/index.ts new file mode 100644 index 0000000..0440a35 --- /dev/null +++ b/packages/devtools-extension/src/sw/browser/tab-manager/helpers/index.ts @@ -0,0 +1,11 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +export { default as handlePortDisconnect } from 'sw/browser/tab-manager/helpers/handle-port-disconnect'; +export { default as connectTabPorts } from 'sw/browser/tab-manager/helpers/connect-tab-ports'; + diff --git a/packages/devtools-extension/src/sw/browser/tab-manager/index.ts b/packages/devtools-extension/src/sw/browser/tab-manager/index.ts new file mode 100644 index 0000000..eaeb43a --- /dev/null +++ b/packages/devtools-extension/src/sw/browser/tab-manager/index.ts @@ -0,0 +1,119 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import { browserAPI } from 'core/browser-api'; + +import type { Tab } from 'sw/browser/tab-manager/interface'; + +import { + + handlePortDisconnect, + connectTabPorts + +} from 'sw/browser/tab-manager/helpers'; + +/** + * This class manages communication between the browser tabs and the devtools page + * by forwarding messages from and to their ports + */ +export default class BrowserTabManager { + /** + * All managed tabs by the extension service worker + */ + tabs: Map = new Map(); + + /** + * Register a new tab + * @param tabId + */ + register(tabId: number): Tab { + if (!this.tabs.has(tabId)) { + this.tabs.set(tabId, { + id: tabId, + ports: { + devtools: null, + proxy: null + }, + disconnect: null + }); + } + + return this.tabs.get(tabId)!; + } + + /** + * Unregister a tab + * @param tabId + */ + unregister(tabId: number): void { + const tab = this.tabs.get(tabId); + + if (tab != null) { + this.cleanup(tab); + } + } + + /** + * Cleanup tab connections + * @param tab + */ + cleanup(tab: Tab): void { + // Perform cleanups: drop all connections, etc. + tab.ports.devtools?.disconnect(); + tab.ports.proxy?.disconnect(); + } + + /** + * Listen for incoming connections from devtools or the active tab + */ + listen(): void { + browserAPI.runtime.onConnect.addListener((port) => { + let tab: Tab | null = null; + + // Connection from devtools page + if (/^\d+$/.test(port.name)) { + // DevTools page port doesn't have tab id specified + // because it's sender is the extension so the tab id is encoded as the name of the port + const tabId = Number(port.name); + + tab = this.register(tabId); + + // eslint-disable-next-line no-console + console.log('New connection from devtools, tabId:', tabId); + + tab.ports.devtools = port; + handlePortDisconnect(tab, 'devtools'); + + // Connection from inspected window. + // Tab might not be present for restricted pages in Firefox. + } else if (port.name === 'proxy' && port.sender?.tab?.id != null) { + const tabId = port.sender.tab.id; + + tab = this.register(tabId); + + // eslint-disable-next-line no-console + console.log('New connection from inspected window, tabId:', tabId); + + if (tab.ports.proxy != null) { + + // eslint-disable-next-line no-console + console.log('Reset previous proxy connection'); + tab.disconnect?.(); + tab.ports.proxy.disconnect(); + } + + tab.ports.proxy = port; + handlePortDisconnect(tab, 'proxy'); + } + + if (tab != null) { + connectTabPorts(tab); + } + }); + } +} diff --git a/packages/devtools-extension/src/sw/browser/tab-manager/interface.ts b/packages/devtools-extension/src/sw/browser/tab-manager/interface.ts new file mode 100644 index 0000000..192c5a4 --- /dev/null +++ b/packages/devtools-extension/src/sw/browser/tab-manager/interface.ts @@ -0,0 +1,31 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +export interface Tab { + /** + * Id of the tab + */ + id: number; + + ports: { + /** + * Port of the devtools page (V4Fire tab in the browser devtools) + */ + devtools: chrome.runtime.Port | null; + + /** + * Port of the proxy which is injected into the inspected window + */ + proxy: chrome.runtime.Port | null; + }; + + /** + * Disconnect callback + */ + disconnect: (() => void) | null; +} diff --git a/packages/devtools-extension/src/sw/index.ts b/packages/devtools-extension/src/sw/index.ts index 333de0c..cb692fb 100644 --- a/packages/devtools-extension/src/sw/index.ts +++ b/packages/devtools-extension/src/sw/index.ts @@ -8,7 +8,7 @@ import 'sw/init/inject-content-scripts'; -import BrowserTabManager from 'sw/browser/browser-tab-manager'; +import BrowserTabManager from 'sw/browser/tab-manager'; import RuntimeMessageHandler from 'sw/browser/runtime-message-handler'; const From fd07195cfc05718bd9632f16c6056a43eae1ebfd Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:09:48 +0700 Subject: [PATCH 41/47] fix(CouldNotFindV4FireOnThePageError): fix name --- .../src/pages/p-devtools/modules/detect-v4fire/error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools-extension/src/pages/p-devtools/modules/detect-v4fire/error.ts b/packages/devtools-extension/src/pages/p-devtools/modules/detect-v4fire/error.ts index 28b58b7..2a0a441 100644 --- a/packages/devtools-extension/src/pages/p-devtools/modules/detect-v4fire/error.ts +++ b/packages/devtools-extension/src/pages/p-devtools/modules/detect-v4fire/error.ts @@ -14,6 +14,6 @@ export default class CouldNotFindV4FireOnThePageError extends Error { Error.captureStackTrace(this, CouldNotFindV4FireOnThePageError); } - this.name = 'CouldNotFindReactOnThePageError'; + this.name = 'CouldNotFindV4FireOnThePageError'; } } From 5e94b508444ebce676b1df4b05225d5ad144cdc3 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:12:44 +0700 Subject: [PATCH 42/47] feat(p-devtools): listen for messages from proxy --- .../src/pages/p-devtools/init.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/devtools-extension/src/pages/p-devtools/init.ts b/packages/devtools-extension/src/pages/p-devtools/init.ts index 2c8fd5d..439fdcf 100644 --- a/packages/devtools-extension/src/pages/p-devtools/init.ts +++ b/packages/devtools-extension/src/pages/p-devtools/init.ts @@ -12,6 +12,8 @@ import { browserAPI, devtoolsEval } from 'core/browser-api'; import type pRoot from 'pages/p-root/p-root'; import { CouldNotFindV4FireOnThePageError, detectV4Fire } from 'pages/p-devtools/modules/detect-v4fire'; +// TODO: refactor + const $a = new Async(); /** @@ -135,6 +137,9 @@ function connectDevToolsPort() { name: String(tabId) }); + // TODO: create bridge + port.onMessage.addListener(listenDevtoolsMessage); + // This port may be disconnected by Chrome at some point, this callback // will be executed only if this port was disconnected from the other end // so, when we call `port.disconnect()` from this script, @@ -154,6 +159,7 @@ function performFullCleanup() { root = null; try { + port?.onMessage.removeListener(listenDevtoolsMessage); port?.disconnect(); } catch (error) { // eslint-disable-next-line no-console @@ -174,3 +180,19 @@ function injectBackend(tabId: number): void { .catch(stderr); } +function listenDevtoolsMessage(message: {event: string; payload: any}): void { + if (root == null) { + return; + } + + const {component} = (<{component: pRoot} & Element>root); + + switch (message.event) { + case 'select-component': + component.selfEmitter.emit(`bridge.${message.event}`, message.payload); + break; + + default: + // Do nothing + } +} From cef55e1f7289c9c16840f04b40109ead430c6669 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:14:44 +0700 Subject: [PATCH 43/47] chore(component-locate): send message to devtools on component select --- packages/devtools-backend/src/ui/component-locate.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/devtools-backend/src/ui/component-locate.ts b/packages/devtools-backend/src/ui/component-locate.ts index f4fbc57..21d4ed2 100644 --- a/packages/devtools-backend/src/ui/component-locate.ts +++ b/packages/devtools-backend/src/ui/component-locate.ts @@ -43,7 +43,17 @@ class ComponentLocate { if (component != null) { componentHighlight.hide({animate: true}); - // TODO: notify devtools application about selected component + + const {componentId, componentName} = component; + + // TODO: create bridge + globalThis.postMessage({ + source: 'v4fire-devtools-bridge', + payload: { + event: 'select-component', + payload: {componentId, componentName} + } + }, '*'); } }; From 58352622db6d393db7d52fe9fd7a8c4733a311ec Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:16:11 +0700 Subject: [PATCH 44/47] refactor(b-components-tree): add scrollToItem method --- .../b-components-tree/b-components-tree.ts | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts index 0035e8c..f9b30b2 100644 --- a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts +++ b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts @@ -78,14 +78,7 @@ class bComponentsTree extends iBlock implements iSearch { tree.setActive(value); } - // It's ugly but we need to scroll to this element - const el = tree.unsafe.findItemElement(value); - - if (el != null) { - const {clientHeight = 0} = el.querySelector(`.${tree.unsafe.block!.getFullElementName('item-wrapper')}`) ?? {}; - - this.search.scrollContainerToElement(wrapper, el, clientHeight); - } + this.scrollToItem(value); } /** @@ -145,6 +138,23 @@ class bComponentsTree extends iBlock implements iSearch { } } + /** + * Find item by it's value and scroll to it + * @param value + */ + protected scrollToItem(value: string): void { + const {tree, wrapper} = this.$refs; + + // It's ugly but we need to scroll to this element + const el = tree!.unsafe.findItemElement(value); + + if (el != null) { + const {clientHeight = 0} = el.querySelector(`.${tree!.unsafe.block!.getFullElementName('item-wrapper')}`) ?? {}; + + this.search.scrollContainerToElement(wrapper!, el, clientHeight); + } + } + /** * * @param _ From 5b9448b66dbef38e650e868c41295b48a509315d Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:17:09 +0700 Subject: [PATCH 45/47] feat(scripts/proxy): connect to extension's service worker --- .../devtools-extension/src/scripts/proxy.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/devtools-extension/src/scripts/proxy.ts b/packages/devtools-extension/src/scripts/proxy.ts index f7df6e6..eed22b9 100644 --- a/packages/devtools-extension/src/scripts/proxy.ts +++ b/packages/devtools-extension/src/scripts/proxy.ts @@ -10,8 +10,14 @@ // @see: https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md#world // It can forward messages from content scripts running in MAIN world to the extension's service worker. +let port: chrome.runtime.Port | null = null; + globalThis.addEventListener('pageshow', () => { globalThis.addEventListener('message', handleMessageFromPage); + + if (port == null) { + connectPort(); + } }); globalThis.addEventListener('pagehide', () => { @@ -25,6 +31,12 @@ function handleMessageFromPage(event: MessageEvent) { } switch (event.data.source) { + // This is a message from a bridge (initialized by a devtools backend) + case 'v4fire-devtools-bridge': + port?.postMessage(event.data.payload); + break; + + // This is a message from a detect script case 'v4fire-devtools-detect': { const {source, payload} = event.data; void chrome.runtime.sendMessage({source, payload}); @@ -36,3 +48,31 @@ function handleMessageFromPage(event: MessageEvent) { // Do nothing } } + +function handleMessageFromDevtools(message: any) { + globalThis.postMessage( + { + source: 'v4fire-devtools-content-script', + payload: message + }, + '*' + ); +} + +function handleDisconnect() { + port = null; + + // Try to reconnect + connectPort(); +} + +// Creates port from application page to the V4Fire DevTools' service worker +// Which then connects it with devtools port +function connectPort() { + port = chrome.runtime.connect({ + name: 'proxy' + }); + + port.onMessage.addListener(handleMessageFromDevtools); + port.onDisconnect.addListener(handleDisconnect); +} From 2fa6d6f541102abfb99f0892b15867a02446651b Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:17:36 +0700 Subject: [PATCH 46/47] feat: handle `bridge.select-component` event --- .../b-components-actions/b-components-actions.ts | 7 ++++++- .../b-components-tree/b-components-tree.ts | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ts b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ts index ee7972f..f14299f 100644 --- a/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ts +++ b/packages/devtools-core/src/features/components/b-components-actions/b-components-actions.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ -import iBlock, { component } from 'components/super/i-block/i-block'; +import iBlock, { component, watch } from 'components/super/i-block/i-block'; import type bWindow from 'components/base/b-window/b-window'; @component() @@ -28,4 +28,9 @@ export default class bComponentsActions extends iBlock { disableLocateComponent(): void { this.$refs.modal?.close().catch(stderr); } + + @watch('rootEmitter:bridge.select-component') + protected closeModal(): void { + this.$refs.modal?.close().catch(stderr); + } } diff --git a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts index f9b30b2..7529764 100644 --- a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts +++ b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts @@ -165,6 +165,21 @@ class bComponentsTree extends iBlock implements iSearch { this.emit('change', componentId); } + /** + * Watch for `select-component` event from root + * + * @param _ + * @param payload + */ + // FIXME: watch r.bridge:select-component + @watch('rootEmitter:bridge.select-component') + protected onSelectComponent(_: unknown, payload: any): void { + const {componentId} = payload; + if (this.$refs.tree?.setActive(componentId)) { + this.scrollToItem(componentId); + } + } + /** * Handle item mouseenter event * From 91bf06b6f246229412678a162b9c937bc56f82d5 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Tue, 12 Dec 2023 20:18:17 +0700 Subject: [PATCH 47/47] docs: add test-cases for components page --- docs/ru/tests/01-components.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docs/ru/tests/01-components.md diff --git a/docs/ru/tests/01-components.md b/docs/ru/tests/01-components.md new file mode 100644 index 0000000..48dbcc6 --- /dev/null +++ b/docs/ru/tests/01-components.md @@ -0,0 +1,19 @@ +# Тесты страницы "Components" + +## 1. Выбор компонента на странице + +### Кейс 1.1 + +1. Открыть приложение V4Fire +2. Открыть devtools +3. Открыть вкладку `v4fire` в devtools +4. Кликнуть на иконку прицела и выбрать компонент на странице -> Данные компонента отобразились в панели + +### Кейс 1.2 + +1. Открыть приложение V4Fire +2. Открыть devtools +3. Открыть вкладку `v4fire` в devtools +4. Сменить вкладку в браузере +5. Вернуться на вкладку с приложением V4Fire +6. Кликнуть на иконку прицела и выбрать компонент на странице -> Данные компонента отобразились в панели \ No newline at end of file