diff --git a/packages/annotation-toolkit/package.json b/packages/annotation-toolkit/package.json index 45d9ff887..1a322d303 100644 --- a/packages/annotation-toolkit/package.json +++ b/packages/annotation-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "annotation-toolkit", - "version": "1.1.0", + "version": "1.1.1", "description": "", "main": "build/bundle.js", "scripts": { diff --git a/packages/annotation-toolkit/release-notes.md b/packages/annotation-toolkit/release-notes.md index 7a07ed3e7..c0a2d5dae 100644 --- a/packages/annotation-toolkit/release-notes.md +++ b/packages/annotation-toolkit/release-notes.md @@ -1,7 +1,26 @@ -# vNext -- The `layerButtons` object now accepts an `intent` property which can be used to color-code buttons. -- The `` accepts a `videoPlayerProps` object that can be used to forward props to the underlying `react-video-player` object. -- The Layers panel is now scrollable. +# v1.1.1 +- **NEW** Support for generating `` screenshots via the requests queue. Usage: + ```js + push(requestsPathFor("Video"), { + type: "screenshot", + payload: { + size: [x, y, cropWidth, cropHeight], // size is of original dimensions before videoScale is applied + callback: (info) => { + // info is the base64 encoded image data + }, + } + }) + ``` +- **FIX** The `` no longer requires an `id` property. This requirement was introduced as an unintentional constraint in v1.1.0 and wasn't present in v1.0.x. It will automatically detect its `id` via context if none is provided. If one is provided, it will use that instead (however this `id` MUST correspond to an already defined `id` for another layer). In shell-mode, we now print a nice error message when an `id` if no layer is found matching this `id`. In standalone mode, this `id` can be any arbitrarily created `id`. +- **ENHANCEMENT** The `layerButtons` object now accepts an `intent` property which can be used to color-code buttons. +- **ENHANCEMENT** The `` accepts a `videoPlayerProps` object that can be used to forward props to the underlying `react-video-player` object. +- **ENHANCEMENT** The Layers Panel is now scrollable. +- **NEW** `` accepts an `instructionsPane` renderProp to draw out an information panel on the top right hand corner of the screen above the actions pane. +- **FIX** Big performance bump by avoiding unnecessary renders within the LayerPanel. +- **FIX** Fix character encoding of active layer actions indicator. +- **NEW** Add support for `secondaryLabel` which works similar to `secondaryIcon` for `` components. +- **NEW** A portal ref component is available via global state at `_unsafe.portalRef` for other BlueprintJS components such as dialogs and modals to use. +- **NEW** `contextHeight` prop for the `` to control the height of the Context Panel. Default: `200px`. # v1.1.0 - **ENHANCEMENT** A helpful message is shown when `` is missing a `layers={...}` prop. diff --git a/packages/annotation-toolkit/src/AppShell.js b/packages/annotation-toolkit/src/AppShell.js index 7b4b21da6..760db1caa 100644 --- a/packages/annotation-toolkit/src/AppShell.js +++ b/packages/annotation-toolkit/src/AppShell.js @@ -5,6 +5,7 @@ import LayersPanel from "./panels/LayersPanel"; import Layer from "./layers/Layer"; import cx from "classnames"; import { Button } from "@blueprintjs/core"; +import { useStore } from "global-context-store"; function Window({ title, children, buttons, bodyClassNames = [] }) { return ( @@ -21,10 +22,9 @@ function Window({ title, children, buttons, bodyClassNames = [] }) { key={idx + "-" + button.title} title={button.title} onClick={() => button.action && button.action()} - className={ - "mosaic-default-control bp3-button bp3-minimal bp3-icon-" + - button.icon - } + icon={button.icon} + minimal={true} + className={"mosaic-default-control"} > ))} @@ -101,10 +101,22 @@ function AppShell({ layerButtons = [], showDebugPane = false, contextPanel: ContextPanel = () => null, + contextHeight = "200px", instructionPane = null, }) { + const { set } = useStore(); + + const portalRef = React.useRef(); + React.useEffect(() => { + set("_unsafe.portalRef", portalRef.current); + }, [portalRef.current]); + return (
+
{showNavbar ? ( @@ -131,7 +143,10 @@ function AppShell({
-
+
diff --git a/packages/annotation-toolkit/src/index.js b/packages/annotation-toolkit/src/index.js index 8a9d8be50..f5bb209f8 100644 --- a/packages/annotation-toolkit/src/index.js +++ b/packages/annotation-toolkit/src/index.js @@ -1,11 +1,11 @@ import AppShell from "./AppShell"; -import Layer from "./layers/Layer"; +import Layer, { LayerContext } from "./layers/Layer"; import BBoxFrame from "./layers/BBoxFrame"; import VideoPlayer from "./layers/VideoPlayer"; import "./react-mosaic-component.css"; import "./index.css"; -export { AppShell, Layer, BBoxFrame, VideoPlayer }; +export { AppShell, Layer, BBoxFrame, VideoPlayer, LayerContext }; export * from "./helpers"; diff --git a/packages/annotation-toolkit/src/layers/Layer.js b/packages/annotation-toolkit/src/layers/Layer.js index 905a46e8d..828217acc 100644 --- a/packages/annotation-toolkit/src/layers/Layer.js +++ b/packages/annotation-toolkit/src/layers/Layer.js @@ -1,6 +1,7 @@ import React, { useContext } from "react"; import cx from "classnames"; import { useStore } from "global-context-store"; +import { Icon } from "@blueprintjs/core"; const LayerContext = React.createContext({ stack: [] }); @@ -8,6 +9,7 @@ function Layer({ displayName, icon, secondaryIcon = "", + secondaryLabel = "", children, component, actions = null, @@ -39,6 +41,7 @@ function Layer({ id: layerId, noPointerEvents, hideActionsIfUnselected, + layerStack, getData, onSelect, }); @@ -81,25 +84,25 @@ function Layer({ onClick={() => setExpanded(!expanded)} className={cx( expanded ? "bp3-tree-node-caret-open" : "", - children ? "bp3-tree-node-caret" : "bp3-tree-node-caret-none", - "bp3-icon-standard" + children ? "bp3-tree-node-caret" : "bp3-tree-node-caret-none" )} - > - {icon ? ( + > + + + {icon ? : null} + {displayName} + {secondaryLabel ? ( + style={{ marginRight: 5, opacity: 0.8, fontSize: 12 }} + className={cx("bp3-tree-node-secondary-label")} + > + {secondaryLabel} + ) : null} - {displayName} - + + {/* */}
{children && expanded ? (
    {children}
@@ -110,3 +113,4 @@ function Layer({ } export default Layer; +export { LayerContext }; diff --git a/packages/annotation-toolkit/src/layers/VideoPlayer.js b/packages/annotation-toolkit/src/layers/VideoPlayer.js index 8b5848c48..e61af9ffd 100644 --- a/packages/annotation-toolkit/src/layers/VideoPlayer.js +++ b/packages/annotation-toolkit/src/layers/VideoPlayer.js @@ -3,6 +3,7 @@ import ReactPlayer from "react-player"; import { useStore } from "global-context-store"; import { Spinner } from "@blueprintjs/core"; import { dataPathBuilderFor, requestsPathFor } from "../helpers"; +import { LayerContext } from "./Layer"; export default function VideoPlayer({ id, @@ -18,6 +19,9 @@ export default function VideoPlayer({ const vidRef = useRef(); const canvasRef = useRef(); + const layerInfo = useContext(LayerContext); + id = id || layerInfo?.id || undefined; + const [detectedSize, setDetectedSize] = React.useState([10, 10]); const [videoLoaded, setVideoLoaded] = React.useState(false); @@ -42,11 +46,33 @@ export default function VideoPlayer({ "seconds" ); } else if (req.type === "screenshot") { + const [x, y, cropWidth, cropHeight] = req?.payload?.size || [ + 0, + 0, + width, + height, + ]; + canvasRef.current.height = cropHeight; + canvasRef.current.width = cropWidth; canvasRef.current .getContext("2d") - .drawImage(vidRef.current.getInternalPlayer(), 0, 0, width, height); + .drawImage( + vidRef.current.getInternalPlayer(), + x / scale, + y / scale, + cropWidth / scale, + cropHeight / scale, + 0, + 0, + cropWidth, + cropHeight + ); const screenshotData = canvasRef.current.toDataURL("image/png"); - req.payload.callback({ store, data: screenshotData }); + req.payload.callback({ + store, + size: [x, y, cropWidth, cropHeight], + data: screenshotData, + }); } }, [vidRef.current] diff --git a/packages/annotation-toolkit/src/panels/ContentPanel.js b/packages/annotation-toolkit/src/panels/ContentPanel.js index c452a6dd2..2c5019d43 100644 --- a/packages/annotation-toolkit/src/panels/ContentPanel.js +++ b/packages/annotation-toolkit/src/panels/ContentPanel.js @@ -4,6 +4,7 @@ import { isFunction } from "../utils"; import mapValues from "lodash.mapvalues"; import { Menu, MenuDivider, Classes, Card } from "@blueprintjs/core"; +import { LayerContext } from "../layers/Layer"; function ContentPanel({ instructionPane: InstructionPane }) { const store = useStore(); @@ -21,12 +22,19 @@ function ContentPanel({ instructionPane: InstructionPane }) { let layers = get(["layers"]); if (!layers) return null; layers = mapValues(layers, (layer) => layer.config); - const alwaysOnLayers = Object.values(layers).filter((layer) => { - return ( - layer.alwaysOn === true || - (isFunction(layer.alwaysOn) && layer.alwaysOn()) - ); - }); + const alwaysOnLayers = Object.entries(layers) + .filter(([layerName, layer]) => { + if (!layer) { + throw new Error( + `Could not find any Layer registered with id: "${layerName}"` + ); + } + return ( + layer.alwaysOn === true || + (isFunction(layer.alwaysOn) && layer.alwaysOn()) + ); + }) + .map(([_, layer]) => layer); const groupedLayers = Object.values(layers).filter((layer) => { return ( layer.onWithGroup === true && @@ -83,10 +91,12 @@ function ContentPanel({ instructionPane: InstructionPane }) { pointerEvents: selectedLayer.noPointerEvents ? "none" : "auto", }} > - + + +
) : null} {[...alwaysOnLayers, ...groupedLayers].map((layer) => @@ -99,7 +109,9 @@ function ContentPanel({ instructionPane: InstructionPane }) { pointerEvents: layer.noPointerEvents ? "none" : "auto", }} > - + + +
) )} @@ -147,12 +159,22 @@ function ContentPanel({ instructionPane: InstructionPane }) { {gatheredActions.actions.length === 0 && state.selectedLayer ? ( ) : null} {gatheredActions.actions.map((action, idx) => ( - + {action} ))} diff --git a/packages/annotation-toolkit/src/panels/LayersPanel.js b/packages/annotation-toolkit/src/panels/LayersPanel.js index 84b6d94b7..2b53063fe 100644 --- a/packages/annotation-toolkit/src/panels/LayersPanel.js +++ b/packages/annotation-toolkit/src/panels/LayersPanel.js @@ -6,9 +6,7 @@ export default function LayersPanel(props) {
-
    - -
+
    {props.layers()}
{props.showDebugPane ? : null}