@tanstack/devtools 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/main-panel.js +8 -2
- package/dist/esm/components/main-panel.js.map +1 -1
- package/dist/esm/components/tabs.js +10 -0
- package/dist/esm/components/tabs.js.map +1 -1
- package/dist/esm/context/draw-context.d.ts +13 -0
- package/dist/esm/context/draw-context.js +55 -0
- package/dist/esm/context/draw-context.js.map +1 -0
- package/dist/esm/context/use-devtools-context.js +10 -1
- package/dist/esm/context/use-devtools-context.js.map +1 -1
- package/dist/esm/hooks/use-head-changes.d.ts +39 -0
- package/dist/esm/hooks/use-head-changes.js +65 -0
- package/dist/esm/hooks/use-head-changes.js.map +1 -0
- package/dist/esm/styles/tokens.js +4 -2
- package/dist/esm/styles/tokens.js.map +1 -1
- package/dist/esm/styles/use-styles.d.ts +19 -5
- package/dist/esm/styles/use-styles.js +142 -39
- package/dist/esm/styles/use-styles.js.map +1 -1
- package/dist/esm/tabs/index.d.ts +5 -0
- package/dist/esm/tabs/index.js +8 -2
- package/dist/esm/tabs/index.js.map +1 -1
- package/dist/esm/tabs/plugins-tab.js +31 -13
- package/dist/esm/tabs/plugins-tab.js.map +1 -1
- package/dist/esm/tabs/seo-tab.d.ts +1 -0
- package/dist/esm/tabs/seo-tab.js +295 -0
- package/dist/esm/tabs/seo-tab.js.map +1 -0
- package/dist/esm/tabs/settings-tab.js +261 -222
- package/dist/esm/tabs/settings-tab.js.map +1 -1
- package/package.json +2 -2
- package/src/components/main-panel.tsx +5 -1
- package/src/components/tabs.tsx +9 -0
- package/src/context/draw-context.tsx +67 -0
- package/src/context/use-devtools-context.ts +12 -2
- package/src/hooks/use-head-changes.ts +110 -0
- package/src/styles/use-styles.ts +147 -39
- package/src/tabs/index.tsx +25 -0
- package/src/tabs/plugins-tab.tsx +51 -23
- package/src/tabs/seo-tab.tsx +245 -0
- package/src/tabs/settings-tab.tsx +109 -95
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/devtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "TanStack Devtools is a set of tools for building advanced devtools for your application.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"goober": "^2.1.16",
|
|
44
44
|
"solid-js": "^1.9.7",
|
|
45
45
|
"@tanstack/devtools-event-bus": "0.3.0",
|
|
46
|
-
"@tanstack/devtools-ui": "0.3.
|
|
46
|
+
"@tanstack/devtools-ui": "0.3.2"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"solid-js": ">=1.9.7"
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
|
+
import { DrawClientProvider } from '../context/draw-context'
|
|
2
3
|
import { useDevtoolsSettings, useHeight } from '../context/use-devtools-context'
|
|
3
4
|
import { useStyles } from '../styles/use-styles'
|
|
4
5
|
import { TANSTACK_DEVTOOLS } from '../utils/storage'
|
|
5
6
|
import { usePiPWindow } from '../context/pip-context'
|
|
7
|
+
|
|
6
8
|
import type { Accessor, JSX } from 'solid-js'
|
|
7
9
|
|
|
8
10
|
export const MainPanel = (props: {
|
|
@@ -30,7 +32,9 @@ export const MainPanel = (props: {
|
|
|
30
32
|
styles().devtoolsPanelContainerResizing(props.isResizing),
|
|
31
33
|
)}
|
|
32
34
|
>
|
|
33
|
-
{
|
|
35
|
+
<DrawClientProvider animationMs={400}>
|
|
36
|
+
{props.children}
|
|
37
|
+
</DrawClientProvider>
|
|
34
38
|
</div>
|
|
35
39
|
)
|
|
36
40
|
}
|
package/src/components/tabs.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import clsx from 'clsx'
|
|
|
2
2
|
import { For } from 'solid-js'
|
|
3
3
|
import { useStyles } from '../styles/use-styles'
|
|
4
4
|
import { useDevtoolsState } from '../context/use-devtools-context'
|
|
5
|
+
import { useDrawContext } from '../context/draw-context'
|
|
5
6
|
import { tabs } from '../tabs'
|
|
6
7
|
import { usePiPWindow } from '../context/pip-context'
|
|
7
8
|
|
|
@@ -18,6 +19,8 @@ export const Tabs = (props: TabsProps) => {
|
|
|
18
19
|
`width=${window.innerWidth},height=${state().height},top=${window.screen.height},left=${window.screenLeft}}`,
|
|
19
20
|
)
|
|
20
21
|
}
|
|
22
|
+
const { hoverUtils } = useDrawContext()
|
|
23
|
+
|
|
21
24
|
return (
|
|
22
25
|
<div class={styles().tabContainer}>
|
|
23
26
|
<For each={tabs}>
|
|
@@ -26,6 +29,12 @@ export const Tabs = (props: TabsProps) => {
|
|
|
26
29
|
type="button"
|
|
27
30
|
onClick={() => setState({ activeTab: tab.id })}
|
|
28
31
|
class={clsx(styles().tab, { active: state().activeTab === tab.id })}
|
|
32
|
+
onMouseEnter={() => {
|
|
33
|
+
if (tab.id === 'plugins') hoverUtils.enter()
|
|
34
|
+
}}
|
|
35
|
+
onMouseLeave={() => {
|
|
36
|
+
if (tab.id === 'plugins') hoverUtils.leave()
|
|
37
|
+
}}
|
|
29
38
|
>
|
|
30
39
|
{tab.icon}
|
|
31
40
|
</button>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
createMemo,
|
|
4
|
+
createSignal,
|
|
5
|
+
onCleanup,
|
|
6
|
+
useContext,
|
|
7
|
+
} from 'solid-js'
|
|
8
|
+
import type { ParentComponent } from 'solid-js'
|
|
9
|
+
|
|
10
|
+
const useDraw = (props: { animationMs: number }) => {
|
|
11
|
+
const [activeHover, setActiveHover] = createSignal<boolean>(false)
|
|
12
|
+
const [forceExpand, setForceExpand] = createSignal<boolean>(false)
|
|
13
|
+
|
|
14
|
+
const expanded = createMemo(() => activeHover() || forceExpand())
|
|
15
|
+
|
|
16
|
+
let hoverTimeout: ReturnType<typeof setTimeout> | null = null
|
|
17
|
+
onCleanup(() => {
|
|
18
|
+
if (hoverTimeout) clearTimeout(hoverTimeout)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const hoverUtils = {
|
|
22
|
+
enter: () => {
|
|
23
|
+
if (hoverTimeout) {
|
|
24
|
+
clearTimeout(hoverTimeout)
|
|
25
|
+
hoverTimeout = null
|
|
26
|
+
}
|
|
27
|
+
setActiveHover(true)
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
leave: () => {
|
|
31
|
+
hoverTimeout = setTimeout(() => {
|
|
32
|
+
setActiveHover(false)
|
|
33
|
+
}, props.animationMs)
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
expanded,
|
|
39
|
+
setForceExpand,
|
|
40
|
+
hoverUtils,
|
|
41
|
+
animationMs: props.animationMs,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type ContextType = ReturnType<typeof useDraw>
|
|
46
|
+
|
|
47
|
+
const DrawContext = createContext<ContextType | undefined>(undefined)
|
|
48
|
+
|
|
49
|
+
export const DrawClientProvider: ParentComponent<{
|
|
50
|
+
animationMs: number
|
|
51
|
+
}> = (props) => {
|
|
52
|
+
const value = useDraw({ animationMs: props.animationMs })
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<DrawContext.Provider value={value}>{props.children}</DrawContext.Provider>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function useDrawContext() {
|
|
60
|
+
const context = useContext(DrawContext)
|
|
61
|
+
|
|
62
|
+
if (context === undefined) {
|
|
63
|
+
throw new Error(`useDrawContext must be used within a DrawClientProvider`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return context
|
|
67
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createMemo, useContext } from 'solid-js'
|
|
1
|
+
import { createEffect, createMemo, useContext } from 'solid-js'
|
|
2
2
|
import { DevtoolsContext } from './devtools-context.jsx'
|
|
3
|
-
|
|
3
|
+
import { useDrawContext } from './draw-context.jsx'
|
|
4
|
+
|
|
4
5
|
import type { DevtoolsStore } from './devtools-store.js'
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -19,10 +20,19 @@ const useDevtoolsContext = () => {
|
|
|
19
20
|
|
|
20
21
|
export const usePlugins = () => {
|
|
21
22
|
const { store, setStore } = useDevtoolsContext()
|
|
23
|
+
const { setForceExpand } = useDrawContext()
|
|
22
24
|
|
|
23
25
|
const plugins = createMemo(() => store.plugins)
|
|
24
26
|
const activePlugin = createMemo(() => store.state.activePlugin)
|
|
25
27
|
|
|
28
|
+
createEffect(() => {
|
|
29
|
+
if (activePlugin() == null) {
|
|
30
|
+
setForceExpand(false)
|
|
31
|
+
} else {
|
|
32
|
+
setForceExpand(true)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
26
36
|
const setActivePlugin = (pluginId: string) => {
|
|
27
37
|
setStore((prev) => ({
|
|
28
38
|
...prev,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { onCleanup, onMount } from 'solid-js'
|
|
2
|
+
|
|
3
|
+
type HeadChange =
|
|
4
|
+
| { kind: 'added'; node: Node }
|
|
5
|
+
| { kind: 'removed'; node: Node }
|
|
6
|
+
| {
|
|
7
|
+
kind: 'attr'
|
|
8
|
+
target: Element
|
|
9
|
+
name: string | null
|
|
10
|
+
oldValue: string | null
|
|
11
|
+
}
|
|
12
|
+
| { kind: 'title'; title: string }
|
|
13
|
+
|
|
14
|
+
type UseHeadChangesOptions = {
|
|
15
|
+
/**
|
|
16
|
+
* Observe attribute changes on elements inside <head>
|
|
17
|
+
* Default: true
|
|
18
|
+
*/
|
|
19
|
+
attributes?: boolean
|
|
20
|
+
/**
|
|
21
|
+
* Observe added/removed nodes in <head>
|
|
22
|
+
* Default: true
|
|
23
|
+
*/
|
|
24
|
+
childList?: boolean
|
|
25
|
+
/**
|
|
26
|
+
* Observe descendants of <head>
|
|
27
|
+
* Default: true
|
|
28
|
+
*/
|
|
29
|
+
subtree?: boolean
|
|
30
|
+
/**
|
|
31
|
+
* Also observe <title> changes explicitly
|
|
32
|
+
* Default: true
|
|
33
|
+
*/
|
|
34
|
+
observeTitle?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useHeadChanges(
|
|
38
|
+
onChange: (change: HeadChange, raw?: MutationRecord) => void,
|
|
39
|
+
opts: UseHeadChangesOptions = {},
|
|
40
|
+
) {
|
|
41
|
+
const {
|
|
42
|
+
attributes = true,
|
|
43
|
+
childList = true,
|
|
44
|
+
subtree = true,
|
|
45
|
+
observeTitle = true,
|
|
46
|
+
} = opts
|
|
47
|
+
|
|
48
|
+
onMount(() => {
|
|
49
|
+
const headObserver = new MutationObserver((mutations) => {
|
|
50
|
+
for (const m of mutations) {
|
|
51
|
+
if (m.type === 'childList') {
|
|
52
|
+
m.addedNodes.forEach((node) => onChange({ kind: 'added', node }, m))
|
|
53
|
+
m.removedNodes.forEach((node) =>
|
|
54
|
+
onChange({ kind: 'removed', node }, m),
|
|
55
|
+
)
|
|
56
|
+
} else if (m.type === 'attributes') {
|
|
57
|
+
const el = m.target as Element
|
|
58
|
+
onChange(
|
|
59
|
+
{
|
|
60
|
+
kind: 'attr',
|
|
61
|
+
target: el,
|
|
62
|
+
name: m.attributeName,
|
|
63
|
+
oldValue: m.oldValue ?? null,
|
|
64
|
+
},
|
|
65
|
+
m,
|
|
66
|
+
)
|
|
67
|
+
} else {
|
|
68
|
+
// If someone mutates a Text node inside <title>, surface it as a title change.
|
|
69
|
+
const isInTitle =
|
|
70
|
+
m.target.parentNode &&
|
|
71
|
+
(m.target.parentNode as Element).tagName.toLowerCase() === 'title'
|
|
72
|
+
if (isInTitle) onChange({ kind: 'title', title: document.title }, m)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
headObserver.observe(document.head, {
|
|
78
|
+
childList,
|
|
79
|
+
attributes,
|
|
80
|
+
subtree,
|
|
81
|
+
attributeOldValue: attributes,
|
|
82
|
+
characterData: true, // helps catch <title> text node edits
|
|
83
|
+
characterDataOldValue: false,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Extra explicit observer for <title>, since `document.title = "..."`
|
|
87
|
+
// may not always bubble as a head mutation in all setups.
|
|
88
|
+
let titleObserver: MutationObserver | undefined
|
|
89
|
+
if (observeTitle) {
|
|
90
|
+
const titleEl =
|
|
91
|
+
document.head.querySelector('title') ||
|
|
92
|
+
// create a <title> if missing so future changes are observable
|
|
93
|
+
document.head.appendChild(document.createElement('title'))
|
|
94
|
+
|
|
95
|
+
titleObserver = new MutationObserver(() => {
|
|
96
|
+
onChange({ kind: 'title', title: document.title })
|
|
97
|
+
})
|
|
98
|
+
titleObserver.observe(titleEl, {
|
|
99
|
+
childList: true,
|
|
100
|
+
characterData: true,
|
|
101
|
+
subtree: true,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
onCleanup(() => {
|
|
106
|
+
headObserver.disconnect()
|
|
107
|
+
titleObserver?.disconnect()
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
}
|
package/src/styles/use-styles.ts
CHANGED
|
@@ -4,12 +4,132 @@ import { tokens } from './tokens'
|
|
|
4
4
|
import type { TanStackDevtoolsConfig } from '../context/devtools-context'
|
|
5
5
|
import type { Accessor } from 'solid-js'
|
|
6
6
|
|
|
7
|
+
const mSecondsToCssSeconds = (mSeconds: number) =>
|
|
8
|
+
`${(mSeconds / 1000).toFixed(2)}s`
|
|
9
|
+
|
|
7
10
|
const stylesFactory = () => {
|
|
8
11
|
const { colors, font, size, alpha, border } = tokens
|
|
9
12
|
const { fontFamily, size: fontSize } = font
|
|
10
13
|
const css = goober.css
|
|
11
14
|
|
|
12
15
|
return {
|
|
16
|
+
seoTabContainer: css`
|
|
17
|
+
padding: 0;
|
|
18
|
+
margin: 0 auto;
|
|
19
|
+
background: ${colors.darkGray[700]};
|
|
20
|
+
border-radius: 12px;
|
|
21
|
+
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.08);
|
|
22
|
+
overflow-y: auto;
|
|
23
|
+
height: 100%;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: 0;
|
|
27
|
+
width: 100%;
|
|
28
|
+
overflow-y: auto;
|
|
29
|
+
`,
|
|
30
|
+
seoTabTitle: css`
|
|
31
|
+
font-size: 1.25rem;
|
|
32
|
+
font-weight: 600;
|
|
33
|
+
color: ${colors.purple[400]};
|
|
34
|
+
margin: 0;
|
|
35
|
+
padding: 1rem 1.5rem 0.5rem 1.5rem;
|
|
36
|
+
text-align: left;
|
|
37
|
+
border-bottom: 1px solid ${colors.gray[700]};
|
|
38
|
+
`,
|
|
39
|
+
seoTabSection: css`
|
|
40
|
+
padding: 1.5rem;
|
|
41
|
+
background: ${colors.darkGray[800]};
|
|
42
|
+
border: 1px solid ${colors.gray[700]};
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: 0.5rem;
|
|
46
|
+
margin-bottom: 2rem;
|
|
47
|
+
border-radius: 0.75rem;
|
|
48
|
+
`,
|
|
49
|
+
seoPreviewSection: css`
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: row;
|
|
52
|
+
gap: 16px;
|
|
53
|
+
margin-bottom: 0;
|
|
54
|
+
justify-content: flex-start;
|
|
55
|
+
align-items: flex-start;
|
|
56
|
+
overflow-x: auto;
|
|
57
|
+
flex-wrap: wrap;
|
|
58
|
+
padding-bottom: 0.5rem;
|
|
59
|
+
`,
|
|
60
|
+
seoPreviewCard: css`
|
|
61
|
+
border: 1px solid ${colors.gray[700]};
|
|
62
|
+
border-radius: 8px;
|
|
63
|
+
padding: 12px 10px;
|
|
64
|
+
background: ${colors.darkGray[900]};
|
|
65
|
+
margin-bottom: 0;
|
|
66
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
align-items: flex-start;
|
|
70
|
+
min-width: 200px;
|
|
71
|
+
max-width: 240px;
|
|
72
|
+
font-size: 0.95rem;
|
|
73
|
+
gap: 4px;
|
|
74
|
+
`,
|
|
75
|
+
seoPreviewHeader: css`
|
|
76
|
+
font-size: 1rem;
|
|
77
|
+
font-weight: 500;
|
|
78
|
+
margin-bottom: 6px;
|
|
79
|
+
color: ${colors.purple[400]};
|
|
80
|
+
`,
|
|
81
|
+
seoPreviewImage: css`
|
|
82
|
+
max-width: 100%;
|
|
83
|
+
border-radius: 6px;
|
|
84
|
+
margin-bottom: 6px;
|
|
85
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
86
|
+
height: 160px;
|
|
87
|
+
`,
|
|
88
|
+
seoPreviewTitle: css`
|
|
89
|
+
font-size: 1rem;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
margin-bottom: 2px;
|
|
92
|
+
color: ${colors.gray[100]};
|
|
93
|
+
`,
|
|
94
|
+
seoPreviewDesc: css`
|
|
95
|
+
color: ${colors.gray[300]};
|
|
96
|
+
margin-bottom: 2px;
|
|
97
|
+
font-size: 0.95rem;
|
|
98
|
+
`,
|
|
99
|
+
seoPreviewUrl: css`
|
|
100
|
+
color: ${colors.gray[500]};
|
|
101
|
+
font-size: 0.9rem;
|
|
102
|
+
margin-bottom: 2px;
|
|
103
|
+
word-break: break-all;
|
|
104
|
+
`,
|
|
105
|
+
seoMissingTagsSection: css`
|
|
106
|
+
margin-top: 4px;
|
|
107
|
+
font-size: 0.95rem;
|
|
108
|
+
color: ${colors.red[400]};
|
|
109
|
+
`,
|
|
110
|
+
seoMissingTagsList: css`
|
|
111
|
+
margin: 4px 0 0 0;
|
|
112
|
+
padding: 0;
|
|
113
|
+
list-style: none;
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-wrap: wrap;
|
|
116
|
+
gap: 6px;
|
|
117
|
+
max-width: 240px;
|
|
118
|
+
`,
|
|
119
|
+
seoMissingTag: css`
|
|
120
|
+
background: ${colors.red[500]}22;
|
|
121
|
+
color: ${colors.red[500]};
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
padding: 1px 6px;
|
|
124
|
+
font-size: 0.9rem;
|
|
125
|
+
font-weight: 500;
|
|
126
|
+
`,
|
|
127
|
+
seoAllTagsFound: css`
|
|
128
|
+
color: ${colors.green[500]};
|
|
129
|
+
font-weight: 500;
|
|
130
|
+
margin-left: 6px;
|
|
131
|
+
font-size: 0.95rem;
|
|
132
|
+
`,
|
|
13
133
|
devtoolsPanelContainer: (
|
|
14
134
|
panelLocation: TanStackDevtoolsConfig['panelLocation'],
|
|
15
135
|
isDetached: boolean,
|
|
@@ -231,13 +351,37 @@ const stylesFactory = () => {
|
|
|
231
351
|
height: 100%;
|
|
232
352
|
overflow: hidden;
|
|
233
353
|
`,
|
|
234
|
-
|
|
235
|
-
|
|
354
|
+
|
|
355
|
+
pluginsTabDraw: css`
|
|
356
|
+
width: 0px;
|
|
357
|
+
height: 100%;
|
|
236
358
|
background-color: ${colors.darkGray[800]};
|
|
237
|
-
border-right: 1px solid ${colors.gray[700]};
|
|
238
359
|
box-shadow: 0 1px 0 ${colors.gray[700]};
|
|
360
|
+
`,
|
|
361
|
+
pluginsTabDrawExpanded: css`
|
|
362
|
+
width: ${size[48]};
|
|
363
|
+
border-right: 1px solid ${colors.gray[700]};
|
|
364
|
+
`,
|
|
365
|
+
pluginsTabDrawTransition: (mSeconds: number) => {
|
|
366
|
+
return css`
|
|
367
|
+
transition: width ${mSecondsToCssSeconds(mSeconds)} ease;
|
|
368
|
+
`
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
pluginsTabSidebar: css`
|
|
372
|
+
width: ${size[48]};
|
|
239
373
|
overflow-y: auto;
|
|
374
|
+
transform: translateX(-100%);
|
|
375
|
+
`,
|
|
376
|
+
pluginsTabSidebarExpanded: css`
|
|
377
|
+
transform: translateX(0);
|
|
240
378
|
`,
|
|
379
|
+
pluginsTabSidebarTransition: (mSeconds: number) => {
|
|
380
|
+
return css`
|
|
381
|
+
transition: transform ${mSecondsToCssSeconds(mSeconds)} ease;
|
|
382
|
+
`
|
|
383
|
+
},
|
|
384
|
+
|
|
241
385
|
pluginName: css`
|
|
242
386
|
font-size: ${fontSize.xs};
|
|
243
387
|
font-family: ${fontFamily.sans};
|
|
@@ -262,42 +406,6 @@ const stylesFactory = () => {
|
|
|
262
406
|
overflow-y: auto;
|
|
263
407
|
`,
|
|
264
408
|
|
|
265
|
-
settingsContainer: css`
|
|
266
|
-
padding: 1.5rem;
|
|
267
|
-
height: 100%;
|
|
268
|
-
overflow-y: auto;
|
|
269
|
-
background-color: ${colors.darkGray[700]};
|
|
270
|
-
`,
|
|
271
|
-
settingsSection: css`
|
|
272
|
-
margin-bottom: 2rem;
|
|
273
|
-
padding: 1.5rem;
|
|
274
|
-
background-color: ${colors.darkGray[800]};
|
|
275
|
-
border: 1px solid ${colors.gray[700]};
|
|
276
|
-
border-radius: 0.75rem;
|
|
277
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
278
|
-
`,
|
|
279
|
-
sectionTitle: css`
|
|
280
|
-
font-size: 1.125rem;
|
|
281
|
-
font-weight: 600;
|
|
282
|
-
color: ${colors.gray[100]};
|
|
283
|
-
margin: 0 0 1rem 0;
|
|
284
|
-
padding-bottom: 0.5rem;
|
|
285
|
-
border-bottom: 1px solid ${colors.gray[700]};
|
|
286
|
-
display: flex;
|
|
287
|
-
align-items: center;
|
|
288
|
-
gap: 0.5rem;
|
|
289
|
-
text-align: left;
|
|
290
|
-
`,
|
|
291
|
-
sectionIcon: css`
|
|
292
|
-
color: ${colors.purple[400]};
|
|
293
|
-
`,
|
|
294
|
-
sectionDescription: css`
|
|
295
|
-
color: ${colors.gray[400]};
|
|
296
|
-
font-size: 0.875rem;
|
|
297
|
-
margin: 0 0 1.5rem 0;
|
|
298
|
-
line-height: 1.5;
|
|
299
|
-
text-align: left;
|
|
300
|
-
`,
|
|
301
409
|
settingsGroup: css`
|
|
302
410
|
display: flex;
|
|
303
411
|
flex-direction: column;
|
package/src/tabs/index.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SettingsTab } from './settings-tab'
|
|
2
2
|
import { PluginsTab } from './plugins-tab'
|
|
3
|
+
import { SeoTab } from './seo-tab'
|
|
3
4
|
|
|
4
5
|
export const tabs = [
|
|
5
6
|
{
|
|
@@ -24,6 +25,30 @@ export const tabs = [
|
|
|
24
25
|
</svg>
|
|
25
26
|
),
|
|
26
27
|
},
|
|
28
|
+
{
|
|
29
|
+
name: 'SEO',
|
|
30
|
+
id: 'seo',
|
|
31
|
+
component: () => <SeoTab />,
|
|
32
|
+
icon: (
|
|
33
|
+
<svg
|
|
34
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
35
|
+
width="24"
|
|
36
|
+
height="24"
|
|
37
|
+
viewBox="0 0 24 24"
|
|
38
|
+
fill="none"
|
|
39
|
+
stroke="currentColor"
|
|
40
|
+
stroke-width="2"
|
|
41
|
+
stroke-linecap="round"
|
|
42
|
+
stroke-linejoin="round"
|
|
43
|
+
class="lucide lucide-file-search2-icon lucide-file-search-2"
|
|
44
|
+
>
|
|
45
|
+
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
|
|
46
|
+
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
|
47
|
+
<circle cx="11.5" cy="14.5" r="2.5" />
|
|
48
|
+
<path d="M13.3 16.3 15 18" />
|
|
49
|
+
</svg>
|
|
50
|
+
),
|
|
51
|
+
},
|
|
27
52
|
{
|
|
28
53
|
name: 'Settings',
|
|
29
54
|
id: 'settings',
|
package/src/tabs/plugins-tab.tsx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { For, createEffect } from 'solid-js'
|
|
2
2
|
import clsx from 'clsx'
|
|
3
|
+
import { useDrawContext } from '../context/draw-context'
|
|
3
4
|
import { usePlugins } from '../context/use-devtools-context'
|
|
4
5
|
import { useStyles } from '../styles/use-styles'
|
|
5
6
|
import { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from '../constants'
|
|
6
7
|
|
|
7
8
|
export const PluginsTab = () => {
|
|
8
9
|
const { plugins, activePlugin, setActivePlugin } = usePlugins()
|
|
10
|
+
const { expanded, hoverUtils, animationMs } = useDrawContext()
|
|
9
11
|
let activePluginRef: HTMLDivElement | undefined
|
|
10
12
|
|
|
11
13
|
createEffect(() => {
|
|
@@ -17,32 +19,58 @@ export const PluginsTab = () => {
|
|
|
17
19
|
}
|
|
18
20
|
})
|
|
19
21
|
const styles = useStyles()
|
|
22
|
+
|
|
20
23
|
return (
|
|
21
24
|
<div class={styles().pluginsTabPanel}>
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
25
|
+
<div
|
|
26
|
+
class={clsx(
|
|
27
|
+
styles().pluginsTabDraw,
|
|
28
|
+
{
|
|
29
|
+
[styles().pluginsTabDrawExpanded]: expanded(),
|
|
30
|
+
},
|
|
31
|
+
styles().pluginsTabDrawTransition(animationMs),
|
|
32
|
+
)}
|
|
33
|
+
onMouseEnter={() => {
|
|
34
|
+
hoverUtils.enter()
|
|
35
|
+
}}
|
|
36
|
+
onMouseLeave={() => {
|
|
37
|
+
hoverUtils.leave()
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
class={clsx(
|
|
42
|
+
styles().pluginsTabSidebar,
|
|
43
|
+
{
|
|
44
|
+
[styles().pluginsTabSidebarExpanded]: expanded(),
|
|
45
|
+
},
|
|
46
|
+
styles().pluginsTabSidebarTransition(animationMs),
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<For each={plugins()}>
|
|
50
|
+
{(plugin) => {
|
|
51
|
+
let pluginHeading: HTMLHeadingElement | undefined
|
|
52
|
+
createEffect(() => {
|
|
53
|
+
if (pluginHeading) {
|
|
54
|
+
typeof plugin.name === 'string'
|
|
55
|
+
? (pluginHeading.textContent = plugin.name)
|
|
56
|
+
: plugin.name(pluginHeading)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
onClick={() => setActivePlugin(plugin.id!)}
|
|
62
|
+
class={clsx(styles().pluginName, {
|
|
63
|
+
active: activePlugin() === plugin.id,
|
|
64
|
+
})}
|
|
65
|
+
>
|
|
66
|
+
<h3 id={PLUGIN_TITLE_CONTAINER_ID} ref={pluginHeading} />
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}}
|
|
70
|
+
</For>
|
|
71
|
+
</div>
|
|
45
72
|
</div>
|
|
73
|
+
|
|
46
74
|
<div
|
|
47
75
|
id={PLUGIN_CONTAINER_ID}
|
|
48
76
|
ref={activePluginRef}
|