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 ? (
@@ -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.showDebugPane ?
: null}