Skip to content

Commit

Permalink
Put together inspector view using observablehq
Browse files Browse the repository at this point in the history
- Inspector document at the moment just contains `targetURL` field of the
document being inspected, but in the future it will be extended to store
state like (un)folded substructures and possibly more.
- At the moment view initially renders title editor to enter `targetURL`
but ideally it would possibly have dropdown like omnibar to just
choose document from the active board. I also would like to have
long press on Option/Alt key to activate overlay over widgets with a
button to launch an inspector for any of the widgets.
  • Loading branch information
Gozala committed Nov 5, 2019
1 parent 45b77ea commit 32bc084
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"main": "./dist/main.js",
"config": {},
"dependencies": {
"@observablehq/inspector": "3.1.0",
"automerge": "github:automerge/automerge#opaque-strings",
"base64-js": "^1.3.1",
"bs58": "^4.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import './content-types/files'
import './content-types/storage-peer'

// other single-context components
import './content-types/Inspector'
import './content-types/TextContent'
import './content-types/ThreadContent'
import './content-types/UrlContent'
Expand Down
107 changes: 107 additions & 0 deletions src/renderer/components/content-types/Inspector.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
:root {
--syntax_normal: #1b1e23;
--syntax_comment: #a9b0bc;
--syntax_number: #20a5ba;
--syntax_keyword: #c30771;
--syntax_atom: #10a778;
--syntax_string: #008ec4;
--syntax_error: #ffbedc;
--syntax_unknown_variable: #838383;
--syntax_known_variable: #005f87;
--syntax_matchbracket: #20bbfc;
--syntax_key: #6636b4;
--mono_fonts: 82%/1.5 Menlo, Consolas, monospace;
}

.Inspector {
display: flex;
background-color: white;
width: 100%;
overflow: auto;
height: 100%;
padding: 1px 1px 0px 1px;
}

.observablehq--expanded,
.observablehq--collapsed,
.observablehq--function,
.observablehq--import,
.observablehq--string:before,
.observablehq--string:after,
.observablehq--gray {
color: var(--syntax_normal);
}

.observablehq--collapsed,
.observablehq--inspect a {
cursor: pointer;
}

.observablehq--field {
text-indent: -1em;
margin-left: 1em;
}

.observablehq--empty {
color: var(--syntax_comment);
}

.observablehq--keyword,
.observablehq--blue {
color: #3182bd;
}

.observablehq--forbidden,
.observablehq--pink {
color: #e377c2;
}

.observablehq--orange {
color: #e6550d;
}

.observablehq--null,
.observablehq--undefined,
.observablehq--boolean {
color: var(--syntax_atom);
}

.observablehq--number,
.observablehq--bigint,
.observablehq--date,
.observablehq--regexp,
.observablehq--symbol,
.observablehq--green {
color: var(--syntax_number);
}

.observablehq--index,
.observablehq--key {
color: var(--syntax_key);
}

.observablehq--empty {
font-style: oblique;
}

.observablehq--string,
.observablehq--purple {
color: var(--syntax_string);
}

.observablehq--error,
.observablehq--red {
color: #e7040f;
}

.observablehq--inspect {
font: var(--mono_fonts);
overflow-x: auto;
display: block;
white-space: pre;
}

.observablehq--error .observablehq--inspect {
word-break: break-all;
white-space: pre-wrap;
}
161 changes: 161 additions & 0 deletions src/renderer/components/content-types/Inspector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import React, { useEffect, useRef } from 'react'
import { Handle } from 'hypermerge'

import * as Observable from '@observablehq/inspector'
import * as ContentTypes from '../../ContentTypes'
import { ContentProps } from '../Content'
import { HypermergeUrl, parts } from '../../ShareLink'
import { useDocument } from '../../Hooks'
import Badge from '../Badge'
import TitleEditor from '../TitleEditor'
import * as ContentData from '../../ContentData'
// import '@observablehq/inspector/src/style.css'
import './Inspector.css'

interface InspectorDoc {
targetURL: HypermergeUrl
}

interface Props extends ContentProps {
uniquelySelected?: boolean
}

interface InspectorOpts {
targetURL: HypermergeUrl | null
change: (cb: (doc: InspectorDoc) => void) => void
}

Inspector.minWidth = 6
Inspector.minHeight = 2
Inspector.defaultWidth = 12
Inspector.defaultHeight = 18

const decodeTargetURL = (url: string) => {
try {
const { scheme, docId } = parts(url)
if (scheme !== 'hypermerge') {
throw new Error(`Invalid url scheme: ${scheme} (expected hypermerge)`)
}

if (!docId) {
throw new Error(`Missing docId in ${url}`)
}

return `hypermerge:/${docId}` as HypermergeUrl
} catch (_) {
return null
}
}

export default function Inspector<d>(props: Props) {
const [doc, changeDoc] = useDocument<InspectorDoc>(props.hypermergeUrl)
const targetURL = doc && decodeTargetURL(doc.targetURL)

const [ref] = useInspector({
targetURL,
change(fn: (doc: InspectorDoc) => void) {
changeDoc((doc) => fn(doc))
},
})
const placeholder = `URL of the document to inspect e.g ${props.hypermergeUrl}`

return (
<div className="Inspector" onPaste={stopPropagation}>
<TitleEditor field="targetURL" url={props.hypermergeUrl} placeholder={placeholder} />
<div ref={ref} />
</div>
)
}

function useInspector<d>({
targetURL,
change,
}: InspectorOpts): [React.Ref<HTMLDivElement>, Observable.Inspector | null] {
const ref = useRef<HTMLDivElement>(null)
const inspector = useRef<Observable.Inspector | null>(null)
const [targetDoc] = useDocument(targetURL)
// const makeChange = useStaticCallback(change)

useEffect(() => {
if (!ref.current) return () => {}

const container = ref.current
inspector.current = new Observable.Inspector(container)
inspector.current.pending()

return () => {
// inspector.current = null
container.textContent = ''

inspector.current = new Observable.Inspector(container)
inspector.current.pending()
}
}, [ref.current]) // eslint-disable-line

useEffect(() => {
if (!inspector.current) return () => {}

if (targetDoc) {
inspector.current.fulfilled(targetDoc)
}

return () => {
if (ref.current) {
ref.current.textContent = ''
}
}
}, [targetDoc, targetURL])

return [ref, inspector.current]
}

function stopPropagation(e: React.SyntheticEvent) {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
}

async function createFrom(contentData: ContentData.ContentData, handle: Handle<InspectorDoc>) {
handle.change((doc) => {})
}

function create({ text }, handle: Handle<InspectorDoc>) {
handle.change((doc) => {})
}

function InspectorInList(props: ContentProps) {
const [doc] = useDocument<InspectorDoc>(props.hypermergeUrl)
function onDragStart(e: React.DragEvent) {
e.dataTransfer.setData('application/pushpin-url', props.url)
}

if (!doc) return null

return (
<div className="DocLink">
<span draggable onDragStart={onDragStart}>
<Badge icon="sticky-note" />
</span>
<TitleEditor
field="targetURL"
url={props.hypermergeUrl}
placeholder="URL of the document to inpsect"
/>
</div>
)
}

const supportsMimeType = (mimeType) => true

ContentTypes.register({
type: 'inspect',
name: 'Inspector',
icon: 'cogs',
contexts: {
board: Inspector,
workspace: Inspector,
list: InspectorInList,
},
create,
createFrom,
supportsMimeType,
})
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,13 @@
dependencies:
core-js "^2.5.7"

"@observablehq/inspector@3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@observablehq/inspector/-/inspector-3.1.0.tgz#4726f9aabc58aa410a4ba27adf89b180fa9a302b"
integrity sha512-DOx40q05QZdQnYpUsKch+8raWGmKSBh5DIAinQRoK+ij7Eq/PKpHJvNrrhThF4+2b/qViUhZGOwMLPhlC2qMzg==
dependencies:
esm "^3.0.84"

"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
Expand Down Expand Up @@ -3265,6 +3272,11 @@ eslint@^5.16.0:
table "^5.2.3"
text-table "^0.2.0"

esm@^3.0.84:
version "3.2.25"
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==

espree@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a"
Expand Down

0 comments on commit 32bc084

Please sign in to comment.