Skip to content

Commit

Permalink
Display image, video, and audio files in table cells
Browse files Browse the repository at this point in the history
  • Loading branch information
yy0931 committed May 4, 2023
1 parent 4542eaf commit 539c9dd
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 8 deletions.
128 changes: 128 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions ui/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import produce from "immer"
import type { JSXInternal } from "preact/src/jsx"
import * as remote from "./remote"
import { Button, Checkbox, Highlight, Select } from "./components"
import { blob2hex, escapeSQLIdentifier, renderValue, type2color, unsafeEscapeValue, useTableStore } from "./table"
import { blob2hex, escapeSQLIdentifier, CellValue, type2color, unsafeEscapeValue, useTableStore } from "./table"
import { BigintMath, createStore } from "./util"

type State =
Expand Down Expand Up @@ -144,7 +144,7 @@ const mountInput = () => {

useTableStore.setState({
input: {
draftValue: renderValue(parseTextareaValue(state.textareaValue, state.blobValue, state.type)),
draftValue: <CellValue value={parseTextareaValue(state.textareaValue, state.blobValue, state.type)} />,
textarea,
}
})
Expand All @@ -155,7 +155,7 @@ const mountInput = () => {
}
useTableStore.setState({
input: {
draftValue: renderValue(parseTextareaValue(state.textareaValue, state.blobValue, state.type)),
draftValue: <CellValue value={parseTextareaValue(state.textareaValue, state.blobValue, state.type)} />,
textarea: null,
}
})
Expand Down Expand Up @@ -499,7 +499,7 @@ export const Editor = () => {
if (input.textarea !== null) {
input.textarea.value = state.textareaValue
}
useTableStore.setState({ input: { ...input, draftValue: renderValue(parseTextareaValue(state.textareaValue, state.blobValue, state.type)) } })
useTableStore.setState({ input: { ...input, draftValue: <CellValue value={parseTextareaValue(state.textareaValue, state.blobValue, state.type)} /> } })
}, state.statement === "UPDATE" ? [state.textareaValue, state.blobValue, state.type] : [undefined, undefined, undefined])

let header: JSXInternal.Element
Expand Down
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@tippyjs/react": "^4.2.6",
"fast-deep-equal": "^3.1.3",
"file-type": "^18.3.0",
"msgpackr": "github:yy0931/msgpackr",
"prismjs": "^1.29.0",
"rollup-plugin-license": "^3.0.1",
Expand Down
41 changes: 37 additions & 4 deletions ui/table.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useMemo, useCallback, useState, useEffect } from "preact/hooks"
import { useRef, useMemo, useCallback, useState, useEffect, useLayoutEffect } from "preact/hooks"
import * as remote from "./remote"
import { useEditorStore } from "./editor"
import produce from "immer"
Expand All @@ -7,6 +7,7 @@ import { flash, persistentRef, renderContextmenu, Tooltip } from "./components"
import { BigintMath, createStore, querySelectorWithRetry } from "./util"
import deepEqual from "fast-deep-equal"
import type { JSXInternal } from "preact/src/jsx"
import { fileTypeFromBuffer } from "file-type/core"

/** Build the WHERE clause from the state of the find widget */
const buildFindWidgetQuery = (tableInfo: remote.TableInfo) => {
Expand Down Expand Up @@ -590,7 +591,7 @@ const TableRow = (props: { selected: boolean, readonly selectedColumn: string |
}}
data-testid={`cell ${props.rowNumber - 1n}, ${i}`}>
<pre class={"overflow-hidden text-ellipsis whitespace-nowrap max-w-[50em] [font-size:inherit] " + (input?.textarea && cursorVisibility ? "cursor-line" : "")}>
<span class="select-none">{input?.draftValue ?? renderValue(value)}</span>
<span class="select-none">{input?.draftValue ?? <CellValue value={value} />}</span>
{input?.textarea && <MountInput element={input.textarea} onFocusOrMount={onFocusOrMount} onBlurOrUnmount={onBlurOrUnmount} />}
</pre>
</td>
Expand Down Expand Up @@ -701,8 +702,40 @@ export const unsafeEscapeValue = (value: remote.SQLite3Value) => {
}
}

export const renderValue = (value: remote.SQLite3Value): JSXInternal.Element => {
if (value instanceof Uint8Array) { // BLOB
/** Renders a DOM Node. */
const DOMNode = (props: { node: Node }) => {
const ref = useRef<HTMLSpanElement>(null)
useLayoutEffect(() => {
if (!ref.current) { return }
ref.current.append(props.node)
return () => { ref.current?.removeChild(props.node) }
}, [props.node])
return <span ref={ref}></span>
}

export const CellValue = ({ value }: { value: remote.SQLite3Value }) => {
const [content, setContent] = useState<{ value: remote.SQLite3Value, node?: preact.ComponentChildren }>({ value })
useEffect(() => {
if (!(value instanceof Uint8Array)) { return }
let unmounted = false
fileTypeFromBuffer(value).then((result) => {
if (!result || unmounted) { return }
const type = result.mime
if (type.startsWith("image/")) {
setContent({ value, node: <img class="h-max-[200px]" src={URL.createObjectURL(new Blob([value], { type }))}></img> })
} else if (type.startsWith("audio/")) {
setContent({ value, node: <audio class="h-[19px] w-[80%]" controls src={URL.createObjectURL(new Blob([value], { type }))}></audio> })
} else if (type.startsWith("video/")) {
setContent({ value, node: <video class="h-max-[200px]" controls src={URL.createObjectURL(new Blob([value], { type }))}></video> })
} else {
setContent({ value, node: <span class="text-[var(--data-null)]">{type}: {`x'${blob2hex(value, 8)}'`}</span> })
}
}).catch(console.error)
return () => { unmounted = true }
}, [value])
if (content.value === value && content.node) {
return <>{content.node}</>
} else if (value instanceof Uint8Array) { // BLOB
return <span class="text-[var(--data-null)]">{`x'${blob2hex(value, 8)}'`}</span>
} else if (value === null) { // NULL
return <span class="text-[var(--data-null)]">NULL</span>
Expand Down

0 comments on commit 539c9dd

Please sign in to comment.