diff --git a/.env b/.env
index 45a32c4..d11dc00 100644
--- a/.env
+++ b/.env
@@ -2,4 +2,7 @@ REACT_APP_BROKER_URL=http://localhost:9000
REACT_APP_ICE_SERVERS_URL=http://localhost:9000/iceservers
REACT_APP_STRIPE_API_KEY=pk_test_8M3NHrF1eI2b84ubF4F8rSTe0095R3f0My
REACT_APP_STRIPE_URL=http://localhost:9000
-REACT_APP_VERSION=$npm_package_version
\ No newline at end of file
+REACT_APP_VERSION=$npm_package_version
+REACT_APP_PREVIEW=false
+REACT_APP_LOGGING=false
+REACT_APP_FATHOM_SITE_ID=VMSHBPKD
\ No newline at end of file
diff --git a/.env.production b/.env.production
index 9477201..f8a939f 100644
--- a/.env.production
+++ b/.env.production
@@ -1,5 +1,8 @@
-REACT_APP_BROKER_URL=https://connect.owlbear.rodeo
-REACT_APP_ICE_SERVERS_URL=https://connect.owlbear.rodeo/iceservers
+REACT_APP_BROKER_URL=https://test.owlbear.rodeo
+REACT_APP_ICE_SERVERS_URL=https://test.owlbear.rodeo/iceservers
REACT_APP_STRIPE_API_KEY=pk_live_MJjzi5djj524Y7h3fL5PNh4e00a852XD51
REACT_APP_STRIPE_URL=https://payment.owlbear.rodeo
-REACT_APP_VERSION=$npm_package_version
\ No newline at end of file
+REACT_APP_VERSION=$npm_package_version
+REACT_APP_PREVIEW=true
+REACT_APP_LOGGING=true
+REACT_APP_FATHOM_SITE_ID=VMSHBPKD
\ No newline at end of file
diff --git a/package.json b/package.json
index 04638aa..76ae3a2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "owlbear-rodeo",
- "version": "1.6.2",
+ "version": "1.7.0",
"private": true,
"dependencies": {
"@babylonjs/core": "^4.2.0",
@@ -14,6 +14,8 @@
"@testing-library/user-event": "^12.2.2",
"ammo.js": "kripken/ammo.js#aab297a4164779c3a9d8dc8d9da26958de3cb778",
"case": "^1.6.3",
+ "comlink": "^4.3.0",
+ "deep-diff": "^1.0.2",
"dexie": "^3.0.3",
"err-code": "^2.0.3",
"fake-indexeddb": "^3.1.2",
@@ -24,6 +26,7 @@
"lodash.set": "^4.3.2",
"normalize-wheel": "^1.0.1",
"polygon-clipping": "^0.15.1",
+ "pretty-bytes": "^5.4.1",
"raw.macro": "^0.4.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
@@ -43,7 +46,7 @@
"simple-peer": "feross/simple-peer#694/head",
"simplebar-react": "^2.1.0",
"simplify-js": "^1.2.4",
- "socket.io-client": "^2.3.0",
+ "socket.io-client": "^3.0.3",
"source-map-explorer": "^2.4.2",
"theme-ui": "^0.3.1",
"use-image": "^1.0.5",
@@ -52,7 +55,7 @@
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-scripts start",
- "build": "react-scripts build",
+ "build": "react-scripts --max_old_space_size=4096 build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
@@ -70,5 +73,8 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "worker-loader": "^3.0.5"
}
}
diff --git a/public/index.html b/public/index.html
index 3639964..f9e20ed 100644
--- a/public/index.html
+++ b/public/index.html
@@ -28,7 +28,7 @@
-
+
diff --git a/src/components/DragOverlay.js b/src/components/DragOverlay.js
new file mode 100644
index 0000000..a89f02d
--- /dev/null
+++ b/src/components/DragOverlay.js
@@ -0,0 +1,89 @@
+import React, { useEffect, useRef, useState } from "react";
+import { Box, IconButton } from "theme-ui";
+
+import RemoveTokenIcon from "../icons/RemoveTokenIcon";
+
+function DragOverlay({ dragging, node, onRemove }) {
+ const [isRemoveHovered, setIsRemoveHovered] = useState(false);
+ const removeTokenRef = useRef();
+
+ // Detect token hover on remove icon manually to support touch devices
+ useEffect(() => {
+ const map = document.querySelector(".map");
+ const mapRect = map.getBoundingClientRect();
+
+ function detectRemoveHover() {
+ if (!node || !dragging || !removeTokenRef.current) {
+ return;
+ }
+ const stage = node.getStage();
+ if (!stage) {
+ return;
+ }
+ const pointerPosition = stage.getPointerPosition();
+ const screenSpacePointerPosition = {
+ x: pointerPosition.x + mapRect.left,
+ y: pointerPosition.y + mapRect.top,
+ };
+ const removeIconPosition = removeTokenRef.current.getBoundingClientRect();
+
+ if (
+ screenSpacePointerPosition.x > removeIconPosition.left &&
+ screenSpacePointerPosition.y > removeIconPosition.top &&
+ screenSpacePointerPosition.x < removeIconPosition.right &&
+ screenSpacePointerPosition.y < removeIconPosition.bottom
+ ) {
+ if (!isRemoveHovered) {
+ setIsRemoveHovered(true);
+ }
+ } else if (isRemoveHovered) {
+ setIsRemoveHovered(false);
+ }
+ }
+
+ let handler;
+ if (node && dragging) {
+ handler = setInterval(detectRemoveHover, 100);
+ }
+
+ return () => {
+ if (handler) {
+ clearInterval(handler);
+ }
+ };
+ }, [isRemoveHovered, dragging, node]);
+
+ // Detect drag end of token image and remove it if it is over the remove icon
+ useEffect(() => {
+ if (!dragging && node && isRemoveHovered) {
+ onRemove();
+ }
+ });
+
+ return (
+ dragging && (
+
+
+
+
+
+ )
+ );
+}
+
+export default DragOverlay;
diff --git a/src/components/ImageDrop.js b/src/components/ImageDrop.js
index 8bd71d3..4cbc74b 100644
--- a/src/components/ImageDrop.js
+++ b/src/components/ImageDrop.js
@@ -15,11 +15,32 @@ function ImageDrop({ onDrop, dropText, children }) {
setDragging(false);
}
- function handleImageDrop(event) {
+ async function handleImageDrop(event) {
event.preventDefault();
event.stopPropagation();
- const files = event.dataTransfer.files;
let imageFiles = [];
+
+ // Check if the dropped image is from a URL
+ const html = event.dataTransfer.getData("text/html");
+ if (html) {
+ try {
+ const urlMatch = html.match(/src="?([^"\s]+)"?\s*/);
+ const url = urlMatch[1].replace("&", "&"); // Reverse html encoding of url parameters
+ let name = "";
+ const altMatch = html.match(/alt="?([^"]+)"?\s*/);
+ if (altMatch && altMatch.length > 1) {
+ name = altMatch[1];
+ }
+ const response = await fetch(url);
+ if (response.ok) {
+ const file = await response.blob();
+ file.name = name;
+ imageFiles.push(file);
+ }
+ } catch {}
+ }
+
+ const files = event.dataTransfer.files;
for (let file of files) {
if (file.type.startsWith("image")) {
imageFiles.push(file);
diff --git a/src/components/Markdown.js b/src/components/Markdown.js
index b49e564..a9381c5 100644
--- a/src/components/Markdown.js
+++ b/src/components/Markdown.js
@@ -45,7 +45,7 @@ function Image(props) {
}
function ListItem(props) {
- return ;
+ return ;
}
function Code({ children, value }) {
@@ -157,4 +157,8 @@ function Markdown({ source, assets }) {
);
}
+Markdown.defaultProps = {
+ assets: {},
+};
+
export default Markdown;
diff --git a/src/components/Slider.js b/src/components/Slider.js
new file mode 100644
index 0000000..fdc601b
--- /dev/null
+++ b/src/components/Slider.js
@@ -0,0 +1,69 @@
+import React, { useState } from "react";
+import { Box, Slider as ThemeSlider } from "theme-ui";
+
+function Slider({ min, max, value, ml, mr, labelFunc, ...rest }) {
+ const percentValue = ((value - min) * 100) / (max - min);
+
+ const [labelVisible, setLabelVisible] = useState(false);
+
+ return (
+
+ {labelVisible && (
+
+
+
+ {labelFunc(value)}
+
+
+
+ )}
+ setLabelVisible(true)}
+ onMouseUp={() => setLabelVisible(false)}
+ onTouchStart={() => setLabelVisible(true)}
+ onTouchEnd={() => setLabelVisible(false)}
+ {...rest}
+ />
+
+ );
+}
+
+Slider.defaultProps = {
+ min: 0,
+ max: 1,
+ value: 0,
+ ml: 0,
+ mr: 0,
+ labelFunc: (value) => value,
+};
+
+export default Slider;
diff --git a/src/components/Tile.js b/src/components/Tile.js
index 66d5ddc..5e7714d 100644
--- a/src/components/Tile.js
+++ b/src/components/Tile.js
@@ -10,18 +10,37 @@ function Tile({
onSelect,
onEdit,
onDoubleClick,
- large,
+ size,
canEdit,
badges,
editTitle,
}) {
+ let width;
+ let margin;
+ switch (size) {
+ case "small":
+ width = "24%";
+ margin = "0.5%";
+ break;
+ case "medium":
+ width = "32%";
+ margin = `${2 / 3}%`;
+ break;
+ case "large":
+ width = "48%";
+ margin = "1%";
+ break;
+ default:
+ width = "32%";
+ margin = `${2 / 3}%`;
+ }
return (
{
e.stopPropagation();
@@ -126,7 +145,7 @@ Tile.defaultProps = {
onSelect: () => {},
onEdit: () => {},
onDoubleClick: () => {},
- large: false,
+ size: "medium",
canEdit: false,
badges: [],
editTitle: "Edit",
diff --git a/src/components/map/Map.js b/src/components/map/Map.js
index ba2fb2d..cdb7aca 100644
--- a/src/components/map/Map.js
+++ b/src/components/map/Map.js
@@ -8,14 +8,16 @@ import MapDrawing from "./MapDrawing";
import MapFog from "./MapFog";
import MapGrid from "./MapGrid";
import MapMeasure from "./MapMeasure";
-import MapLoadingOverlay from "./MapLoadingOverlay";
import NetworkedMapPointer from "../../network/NetworkedMapPointer";
+import MapNotes from "./MapNotes";
import TokenDataContext from "../../contexts/TokenDataContext";
import SettingsContext from "../../contexts/SettingsContext";
import TokenMenu from "../token/TokenMenu";
import TokenDragOverlay from "../token/TokenDragOverlay";
+import NoteMenu from "../note/NoteMenu";
+import NoteDragOverlay from "../note/NoteDragOverlay";
import { drawActionsToShapes } from "../../helpers/drawing";
@@ -32,9 +34,12 @@ function Map({
onFogDraw,
onFogDrawUndo,
onFogDrawRedo,
+ onMapNoteChange,
+ onMapNoteRemove,
allowMapDrawing,
allowFogDrawing,
allowMapChange,
+ allowNoteEditing,
disabledTokens,
session,
}) {
@@ -100,8 +105,8 @@ function Map({
onFogDraw({ type: "add", shapes: [shape] });
}
- function handleFogShapeSubtract(shape) {
- onFogDraw({ type: "subtract", shapes: [shape] });
+ function handleFogShapeCut(shape) {
+ onFogDraw({ type: "cut", shapes: [shape] });
}
function handleFogShapesRemove(shapeIds) {
@@ -140,6 +145,9 @@ function Map({
if (!allowMapChange) {
disabledControls.push("map");
}
+ if (!allowNoteEditing) {
+ disabledControls.push("note");
+ }
const disabledSettings = { fog: [], drawing: [] };
if (mapShapes.length === 0) {
@@ -182,7 +190,7 @@ function Map({
const [isTokenMenuOpen, setIsTokenMenuOpen] = useState(false);
const [tokenMenuOptions, setTokenMenuOptions] = useState({});
- const [draggingTokenOptions, setDraggingTokenOptions] = useState();
+ const [tokenDraggingOptions, setTokenDraggingOptions] = useState();
function handleTokenMenuOpen(tokenStateId, tokenImage) {
setTokenMenuOptions({ tokenStateId, tokenImage });
setIsTokenMenuOpen(true);
@@ -202,7 +210,7 @@ function Map({
}
// Sort so vehicles render below other tokens
- function sortMapTokenStates(a, b, draggingTokenOptions) {
+ function sortMapTokenStates(a, b, tokenDraggingOptions) {
const tokenA = tokensById[a.tokenId];
const tokenB = tokensById[b.tokenId];
if (tokenA && tokenB) {
@@ -212,16 +220,16 @@ function Map({
const bWeight = getMapTokenCategoryWeight(tokenB.category);
return bWeight - aWeight;
} else if (
- draggingTokenOptions &&
- draggingTokenOptions.dragging &&
- draggingTokenOptions.tokenState.id === a.id
+ tokenDraggingOptions &&
+ tokenDraggingOptions.dragging &&
+ tokenDraggingOptions.tokenState.id === a.id
) {
// If dragging token a move above
return 1;
} else if (
- draggingTokenOptions &&
- draggingTokenOptions.dragging &&
- draggingTokenOptions.tokenState.id === b.id
+ tokenDraggingOptions &&
+ tokenDraggingOptions.dragging &&
+ tokenDraggingOptions.tokenState.id === b.id
) {
// If dragging token b move above
return -1;
@@ -241,7 +249,7 @@ function Map({
const mapTokens = map && mapState && (
{Object.values(mapState.tokens)
- .sort((a, b) => sortMapTokenStates(a, b, draggingTokenOptions))
+ .sort((a, b) => sortMapTokenStates(a, b, tokenDraggingOptions))
.map((tokenState) => (
- setDraggingTokenOptions({
+ setTokenDraggingOptions({
dragging: true,
tokenState,
tokenGroup: e.target,
})
}
onTokenDragEnd={() =>
- setDraggingTokenOptions({
- ...draggingTokenOptions,
+ setTokenDraggingOptions({
+ ...tokenDraggingOptions,
dragging: false,
})
}
@@ -287,17 +295,17 @@ function Map({
/>
);
- const tokenDragOverlay = draggingTokenOptions && (
+ const tokenDragOverlay = tokenDraggingOptions && (
{
onMapTokenStateRemove(state);
- setDraggingTokenOptions(null);
+ setTokenDraggingOptions(null);
}}
onTokenStateChange={onMapTokenStateChange}
- tokenState={draggingTokenOptions && draggingTokenOptions.tokenState}
- tokenGroup={draggingTokenOptions && draggingTokenOptions.tokenGroup}
- dragging={draggingTokenOptions && draggingTokenOptions.dragging}
- token={tokensById[draggingTokenOptions.tokenState.tokenId]}
+ tokenState={tokenDraggingOptions && tokenDraggingOptions.tokenState}
+ tokenGroup={tokenDraggingOptions && tokenDraggingOptions.tokenGroup}
+ dragging={!!(tokenDraggingOptions && tokenDraggingOptions.dragging)}
+ token={tokensById[tokenDraggingOptions.tokenState.tokenId]}
mapState={mapState}
/>
);
@@ -309,7 +317,6 @@ function Map({
onShapeAdd={handleMapShapeAdd}
onShapesRemove={handleMapShapesRemove}
active={selectedToolId === "drawing"}
- toolId="drawing"
toolSettings={settings.drawing}
gridSize={gridSizeNormalized}
/>
@@ -320,14 +327,13 @@ function Map({
map={map}
shapes={fogShapes}
onShapeAdd={handleFogShapeAdd}
- onShapeSubtract={handleFogShapeSubtract}
+ onShapeCut={handleFogShapeCut}
onShapesRemove={handleFogShapesRemove}
onShapesEdit={handleFogShapesEdit}
active={selectedToolId === "fog"}
- toolId="fog"
toolSettings={settings.fog}
gridSize={gridSizeNormalized}
- transparent={allowFogDrawing && !settings.fog.preview}
+ editable={allowFogDrawing && !settings.fog.preview}
/>
);
@@ -350,15 +356,98 @@ function Map({
/>
);
+ const [isNoteMenuOpen, setIsNoteMenuOpen] = useState(false);
+ const [noteMenuOptions, setNoteMenuOptions] = useState({});
+ const [noteDraggingOptions, setNoteDraggingOptions] = useState();
+ function handleNoteMenuOpen(noteId, noteNode) {
+ setNoteMenuOptions({ noteId, noteNode });
+ setIsNoteMenuOpen(true);
+ }
+
+ function sortNotes(a, b, noteDraggingOptions) {
+ if (
+ noteDraggingOptions &&
+ noteDraggingOptions.dragging &&
+ noteDraggingOptions.noteId === a.id
+ ) {
+ // If dragging token `a` move above
+ return 1;
+ } else if (
+ noteDraggingOptions &&
+ noteDraggingOptions.dragging &&
+ noteDraggingOptions.noteId === b.id
+ ) {
+ // If dragging token `b` move above
+ return -1;
+ } else {
+ // Else sort so last modified is on top
+ return a.lastModified - b.lastModified;
+ }
+ }
+
+ const mapNotes = (
+
+ sortNotes(a, b, noteDraggingOptions)
+ )
+ : []
+ }
+ onNoteMenuOpen={handleNoteMenuOpen}
+ draggable={
+ allowNoteEditing &&
+ (selectedToolId === "note" || selectedToolId === "pan")
+ }
+ onNoteDragStart={(e, noteId) =>
+ setNoteDraggingOptions({ dragging: true, noteId, noteGroup: e.target })
+ }
+ onNoteDragEnd={() =>
+ setNoteDraggingOptions({ ...noteDraggingOptions, dragging: false })
+ }
+ />
+ );
+
+ const noteMenu = (
+ setIsNoteMenuOpen(false)}
+ onNoteChange={onMapNoteChange}
+ note={mapState && mapState.notes[noteMenuOptions.noteId]}
+ noteNode={noteMenuOptions.noteNode}
+ map={map}
+ />
+ );
+
+ const noteDragOverlay = (
+ {
+ onMapNoteRemove(noteId);
+ setNoteDraggingOptions(null);
+ }}
+ />
+ );
+
return (
{mapControls}
{tokenMenu}
+ {noteMenu}
{tokenDragOverlay}
-
+ {noteDragOverlay}
>
}
selectedToolId={selectedToolId}
@@ -366,6 +455,7 @@ function Map({
disabledControls={disabledControls}
>
{mapGrid}
+ {mapNotes}
{mapDrawing}
{mapTokens}
{mapFog}
diff --git a/src/components/map/MapControls.js b/src/components/map/MapControls.js
index a912201..fb71b72 100644
--- a/src/components/map/MapControls.js
+++ b/src/components/map/MapControls.js
@@ -18,6 +18,7 @@ import ExpandMoreIcon from "../../icons/ExpandMoreIcon";
import PointerToolIcon from "../../icons/PointerToolIcon";
import FullScreenIcon from "../../icons/FullScreenIcon";
import FullScreenExitIcon from "../../icons/FullScreenExitIcon";
+import NoteToolIcon from "../../icons/NoteToolIcon";
import useSetting from "../../helpers/useSetting";
@@ -66,8 +67,13 @@ function MapContols({
icon: ,
title: "Pointer Tool (Q)",
},
+ note: {
+ id: "note",
+ icon: ,
+ title: "Note Tool (N)",
+ },
};
- const tools = ["pan", "fog", "drawing", "measure", "pointer"];
+ const tools = ["pan", "fog", "drawing", "measure", "pointer", "note"];
const sections = [
{
diff --git a/src/components/map/MapDrawing.js b/src/components/map/MapDrawing.js
index 8bf33c9..66012c3 100644
--- a/src/components/map/MapDrawing.js
+++ b/src/components/map/MapDrawing.js
@@ -23,7 +23,6 @@ function MapDrawing({
onShapeAdd,
onShapesRemove,
active,
- toolId,
toolSettings,
gridSize,
}) {
@@ -35,7 +34,7 @@ function MapDrawing({
const [isBrushDown, setIsBrushDown] = useState(false);
const [erasingShapes, setErasingShapes] = useState([]);
- const shouldHover = toolSettings.type === "erase";
+ const shouldHover = toolSettings.type === "erase" && active;
const isBrush =
toolSettings.type === "brush" || toolSettings.type === "paint";
const isShape =
@@ -55,8 +54,8 @@ function MapDrawing({
return getBrushPositionForTool(
map,
getRelativePointerPositionNormalized(mapImage),
- toolId,
- toolSettings,
+ map.snapToGrid && isShape,
+ false,
gridSize,
shapes
);
diff --git a/src/components/map/MapEditor.js b/src/components/map/MapEditor.js
index d1e046a..d6b22d2 100644
--- a/src/components/map/MapEditor.js
+++ b/src/components/map/MapEditor.js
@@ -8,6 +8,7 @@ import usePreventOverscroll from "../../helpers/usePreventOverscroll";
import useStageInteraction from "../../helpers/useStageInteraction";
import useImageCenter from "../../helpers/useImageCenter";
import { getMapDefaultInset, getMapMaxZoom } from "../../helpers/map";
+import useResponsiveLayout from "../../helpers/useResponsiveLayout";
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
import KeyboardContext from "../../contexts/KeyboardContext";
@@ -104,11 +105,14 @@ function MapEditor({ map, onSettingsChange }) {
map.grid.inset.topLeft.y !== defaultInset.topLeft.y ||
map.grid.inset.bottomRight.x !== defaultInset.bottomRight.x ||
map.grid.inset.bottomRight.y !== defaultInset.bottomRight.y;
+
+ const layout = useResponsiveLayout();
+
return (
{
- if (!active) {
+ if (!active || !editable) {
return;
}
@@ -61,8 +69,10 @@ function MapFog({
return getBrushPositionForTool(
map,
getRelativePointerPositionNormalized(mapImage),
- toolId,
- toolSettings,
+ map.snapToGrid &&
+ (toolSettings.type === "polygon" ||
+ toolSettings.type === "rectangle"),
+ toolSettings.useEdgeSnapping,
gridSize,
shapes
);
@@ -78,8 +88,25 @@ function MapFog({
holes: [],
},
strokeWidth: 0.5,
- color: toolSettings.useFogSubtract ? "red" : "black",
- blend: false,
+ color: toolSettings.useFogCut ? "red" : "black",
+ id: shortid.generate(),
+ visible: true,
+ });
+ }
+ if (toolSettings.type === "rectangle") {
+ setDrawingShape({
+ type: "fog",
+ data: {
+ points: [
+ brushPosition,
+ brushPosition,
+ brushPosition,
+ brushPosition,
+ ],
+ holes: [],
+ },
+ strokeWidth: 0.5,
+ color: toolSettings.useFogCut ? "red" : "black",
id: shortid.generate(),
visible: true,
});
@@ -110,15 +137,35 @@ function MapFog({
};
});
}
+ if (toolSettings.type === "rectangle" && isBrushDown && drawingShape) {
+ const brushPosition = getBrushPosition();
+ setDrawingShape((prevShape) => {
+ const prevPoints = prevShape.data.points;
+ return {
+ ...prevShape,
+ data: {
+ ...prevShape.data,
+ points: [
+ prevPoints[0],
+ { x: brushPosition.x, y: prevPoints[1].y },
+ brushPosition,
+ { x: prevPoints[3].x, y: brushPosition.y },
+ ],
+ },
+ };
+ });
+ }
}
function handleBrushUp() {
- if (toolSettings.type === "brush" && drawingShape) {
- const subtract = toolSettings.useFogSubtract;
-
+ if (
+ toolSettings.type === "brush" ||
+ (toolSettings.type === "rectangle" && drawingShape)
+ ) {
+ const cut = toolSettings.useFogCut;
if (drawingShape.data.points.length > 1) {
let shapeData = {};
- if (subtract) {
+ if (cut) {
shapeData = { id: drawingShape.id, type: drawingShape.type };
} else {
shapeData = { ...drawingShape, color: "black" };
@@ -135,8 +182,8 @@ function MapFog({
),
},
};
- if (subtract) {
- onShapeSubtract(shape);
+ if (cut) {
+ onShapeCut(shape);
} else {
onShapeAdd(shape);
}
@@ -169,8 +216,7 @@ function MapFog({
holes: [],
},
strokeWidth: 0.5,
- color: toolSettings.useFogSubtract ? "red" : "black",
- blend: false,
+ color: toolSettings.useFogCut ? "red" : "black",
id: shortid.generate(),
visible: true,
};
@@ -216,14 +262,14 @@ function MapFog({
});
const finishDrawingPolygon = useCallback(() => {
- const subtract = toolSettings.useFogSubtract;
+ const cut = toolSettings.useFogCut;
const data = {
...drawingShape.data,
// Remove the last point as it hasn't been placed yet
points: drawingShape.data.points.slice(0, -1),
};
- if (subtract) {
- onShapeSubtract({
+ if (cut) {
+ onShapeCut({
id: drawingShape.id,
type: drawingShape.type,
data: data,
@@ -233,7 +279,7 @@ function MapFog({
}
setDrawingShape(null);
- }, [toolSettings, drawingShape, onShapeSubtract, onShapeAdd]);
+ }, [toolSettings, drawingShape, onShapeCut, onShapeAdd]);
// Add keyboard shortcuts
function handleKeyDown({ key }) {
@@ -243,30 +289,22 @@ function MapFog({
if (key === "Escape" && drawingShape) {
setDrawingShape(null);
}
- if (key === "Alt" && drawingShape) {
- updateShapeColor();
- }
}
- function handleKeyUp({ key }) {
- if (key === "Alt" && drawingShape) {
- updateShapeColor();
- }
- }
+ useKeyboard(handleKeyDown);
- function updateShapeColor() {
+ // Update shape color when useFogCut changes
+ useEffect(() => {
setDrawingShape((prevShape) => {
if (!prevShape) {
return;
}
return {
...prevShape,
- color: toolSettings.useFogSubtract ? "black" : "red",
+ color: toolSettings.useFogCut ? "red" : "black",
};
});
- }
-
- useKeyboard(handleKeyDown, handleKeyUp);
+ }, [toolSettings.useFogCut]);
function eraseHoveredShapes() {
// Erase
@@ -323,14 +361,16 @@ function MapFog({
mapWidth,
mapHeight
)}
- visible={(active && !toolSettings.preview) || shape.visible}
- opacity={transparent ? 0.5 : 1}
+ opacity={editable ? 0.5 : 1}
fillPatternImage={patternImage}
fillPriority={active && !shape.visible ? "pattern" : "color"}
holes={holes}
// Disable collision if the fog is transparent and we're not editing it
// This allows tokens to be moved under the fog
- hitFunc={transparent && !active ? () => {} : undefined}
+ hitFunc={editable && !active ? () => {} : undefined}
+ shadowColor={editable ? "rgba(0, 0, 0, 0)" : "rgba(0, 0, 0, 0.33)"}
+ shadowOffset={{ x: 0, y: 5 }}
+ shadowBlur={10}
/>
);
}
@@ -366,9 +406,51 @@ function MapFog({
);
}
+ const [fogShapes, setFogShapes] = useState(shapes);
+ useEffect(() => {
+ function shapeVisible(shape) {
+ return (active && !toolSettings.preview) || shape.visible;
+ }
+
+ if (editable) {
+ setFogShapes(shapes.filter(shapeVisible));
+ } else {
+ setFogShapes(mergeShapes(shapes));
+ }
+ }, [shapes, editable, active, toolSettings]);
+
+ const fogGroupRef = useRef();
+ const debouncedStageScale = useDebounce(stageScale, 50);
+
+ useEffect(() => {
+ const fogGroup = fogGroupRef.current;
+
+ const canvas = fogGroup.getChildren()[0].getCanvas();
+ const pixelRatio = canvas.pixelRatio || 1;
+
+ // Constrain fog buffer to the map resolution
+ const fogRect = fogGroup.getClientRect();
+ const maxMapSize = map ? Math.max(map.width, map.height) : 4096; // Default to 4096
+ const maxFogSize =
+ Math.max(fogRect.width, fogRect.height) / debouncedStageScale;
+ const maxPixelRatio = maxMapSize / maxFogSize;
+
+ fogGroup.cache({
+ pixelRatio: Math.min(
+ Math.max(debouncedStageScale * pixelRatio, 1),
+ maxPixelRatio
+ ),
+ });
+ fogGroup.getLayer().draw();
+ }, [fogShapes, editable, active, debouncedStageScale, mapWidth, map]);
+
return (
- {shapes.map(renderShape)}
+
+ {/* Render a blank shape so cache works with no fog shapes */}
+
+ {fogShapes.map(renderShape)}
+
{drawingShape && renderShape(drawingShape)}
{drawingShape &&
toolSettings &&
diff --git a/src/components/map/MapInteraction.js b/src/components/map/MapInteraction.js
index b750f6d..8f89ecc 100644
--- a/src/components/map/MapInteraction.js
+++ b/src/components/map/MapInteraction.js
@@ -21,6 +21,7 @@ import KeyboardContext from "../../contexts/KeyboardContext";
function MapInteraction({
map,
+ mapState,
children,
controls,
selectedToolId,
@@ -32,12 +33,17 @@ function MapInteraction({
// Map loaded taking in to account different resolutions
const [mapLoaded, setMapLoaded] = useState(false);
useEffect(() => {
- if (map === null) {
+ if (
+ !map ||
+ !mapState ||
+ (map.type === "file" && !map.file && !map.resolutions) ||
+ mapState.mapId !== map.id
+ ) {
setMapLoaded(false);
} else if (mapImageSourceStatus === "loaded") {
setMapLoaded(true);
}
- }, [mapImageSourceStatus, map]);
+ }, [mapImageSourceStatus, map, mapState]);
const [stageWidth, setStageWidth] = useState(1);
const [stageHeight, setStageHeight] = useState(1);
@@ -51,8 +57,10 @@ function MapInteraction({
const mapImageRef = useRef();
function handleResize(width, height) {
- setStageWidth(width);
- setStageHeight(height);
+ if (width > 0 && height > 0) {
+ setStageWidth(width);
+ setStageHeight(height);
+ }
}
const containerRef = useRef();
@@ -135,6 +143,9 @@ function MapInteraction({
if (event.key === "q" && !disabledControls.includes("pointer")) {
onSelectedToolChange("pointer");
}
+ if (event.key === "n" && !disabledControls.includes("note")) {
+ onSelectedToolChange("note");
+ }
}
function handleKeyUp(event) {
@@ -153,8 +164,12 @@ function MapInteraction({
return "move";
case "fog":
case "drawing":
+ return settings.settings[tool].type === "move"
+ ? "pointer"
+ : "crosshair";
case "measure":
case "pointer":
+ case "note":
return "crosshair";
default:
return "default";
diff --git a/src/components/map/MapLoadingOverlay.js b/src/components/map/MapLoadingOverlay.js
index d7d2c9c..6c63620 100644
--- a/src/components/map/MapLoadingOverlay.js
+++ b/src/components/map/MapLoadingOverlay.js
@@ -38,10 +38,10 @@ function MapLoadingOverlay() {
display: "flex",
justifyContent: "center",
alignItems: "center",
- left: "8px",
- bottom: "8px",
+ top: 0,
+ left: 0,
+ right: 0,
flexDirection: "column",
- borderRadius: "28px",
zIndex: 2,
}}
bg="overlay"
@@ -50,8 +50,9 @@ function MapLoadingOverlay() {
ref={progressBarRef}
max={1}
value={0}
- m={2}
- sx={{ width: "32px" }}
+ m={0}
+ sx={{ width: "100%", borderRadius: 0, height: "4px" }}
+ color="primary"
/>
)
diff --git a/src/components/map/MapMeasure.js b/src/components/map/MapMeasure.js
index c3544a7..54ef535 100644
--- a/src/components/map/MapMeasure.js
+++ b/src/components/map/MapMeasure.js
@@ -21,11 +21,26 @@ function MapMeasure({ map, selectedToolSettings, active, gridSize }) {
const [drawingShapeData, setDrawingShapeData] = useState(null);
const [isBrushDown, setIsBrushDown] = useState(false);
- const toolScale =
- active && selectedToolSettings.scale.match(/(\d*)([a-zA-Z]*)/);
- const toolMultiplier =
- active && !isNaN(parseInt(toolScale[1])) ? parseInt(toolScale[1]) : 1;
- const toolUnit = active && toolScale[2];
+ function parseToolScale(scale) {
+ if (typeof scale === "string") {
+ const match = scale.match(/(\d*)(\.\d*)?([a-zA-Z]*)/);
+ const integer = parseFloat(match[1]);
+ const fractional = parseFloat(match[2]);
+ const unit = match[3] || "";
+ if (!isNaN(integer) && !isNaN(fractional)) {
+ return {
+ multiplier: integer + fractional,
+ unit: unit,
+ digits: match[2].length - 1,
+ };
+ } else if (!isNaN(integer) && isNaN(fractional)) {
+ return { multiplier: integer, unit: unit, digits: 0 };
+ }
+ }
+ return { multiplier: 1, unit: "", digits: 0 };
+ }
+
+ const measureScale = parseToolScale(active && selectedToolSettings.scale);
useEffect(() => {
if (!active) {
@@ -38,8 +53,8 @@ function MapMeasure({ map, selectedToolSettings, active, gridSize }) {
return getBrushPositionForTool(
map,
getRelativePointerPositionNormalized(mapImage),
- "drawing",
- { type: "line" },
+ map.snapToGrid,
+ false,
gridSize,
[]
);
@@ -62,9 +77,11 @@ function MapMeasure({ map, selectedToolSettings, active, gridSize }) {
brushPosition,
gridSize
);
+ // Round the grid positions to the nearest 0.1 to aviod floating point issues
+ const precision = { x: 0.1, y: 0.1 };
const length = Vector2.distance(
- Vector2.divide(points[0], gridSize),
- Vector2.divide(points[1], gridSize),
+ Vector2.roundTo(Vector2.divide(points[0], gridSize), precision),
+ Vector2.roundTo(Vector2.divide(points[1], gridSize), precision),
selectedToolSettings.type
);
setDrawingShapeData({
@@ -125,9 +142,9 @@ function MapMeasure({ map, selectedToolSettings, active, gridSize }) {
>
{
// Close modal if interacting with any other element
- function handlePointerDown(event) {
+ function handleInteraction(event) {
const path = event.composedPath();
if (
!path.includes(modalContentNode) &&
- !(excludeNode && path.includes(excludeNode))
+ !(excludeNode && path.includes(excludeNode)) &&
+ !(event.target instanceof HTMLTextAreaElement)
) {
onRequestClose();
- document.body.removeEventListener("pointerdown", handlePointerDown);
+ document.body.removeEventListener("pointerdown", handleInteraction);
+ document.body.removeEventListener("wheel", handleInteraction);
}
}
if (modalContentNode) {
- document.body.addEventListener("pointerdown", handlePointerDown);
+ document.body.addEventListener("pointerdown", handleInteraction);
// Check for wheel event to close modal as well
- document.body.addEventListener(
- "wheel",
- () => {
- onRequestClose();
- },
- { once: true }
- );
+ document.body.addEventListener("wheel", handleInteraction);
}
return () => {
if (modalContentNode) {
- document.body.removeEventListener("pointerdown", handlePointerDown);
+ document.body.removeEventListener("pointerdown", handleInteraction);
}
};
}, [modalContentNode, excludeNode, onRequestClose]);
diff --git a/src/components/map/MapNotes.js b/src/components/map/MapNotes.js
new file mode 100644
index 0000000..1f31893
--- /dev/null
+++ b/src/components/map/MapNotes.js
@@ -0,0 +1,126 @@
+import React, { useContext, useState, useEffect, useRef } from "react";
+import shortid from "shortid";
+import { Group } from "react-konva";
+
+import MapInteractionContext from "../../contexts/MapInteractionContext";
+import MapStageContext from "../../contexts/MapStageContext";
+import AuthContext from "../../contexts/AuthContext";
+
+import { getBrushPositionForTool } from "../../helpers/drawing";
+import { getRelativePointerPositionNormalized } from "../../helpers/konva";
+
+import Note from "../note/Note";
+
+const defaultNoteSize = 2;
+
+function MapNotes({
+ map,
+ active,
+ gridSize,
+ onNoteAdd,
+ onNoteChange,
+ notes,
+ onNoteMenuOpen,
+ draggable,
+ onNoteDragStart,
+ onNoteDragEnd,
+}) {
+ const { interactionEmitter } = useContext(MapInteractionContext);
+ const { userId } = useContext(AuthContext);
+ const mapStageRef = useContext(MapStageContext);
+ const [isBrushDown, setIsBrushDown] = useState(false);
+ const [noteData, setNoteData] = useState(null);
+
+ const creatingNoteRef = useRef();
+
+ useEffect(() => {
+ if (!active) {
+ return;
+ }
+ const mapStage = mapStageRef.current;
+
+ function getBrushPosition() {
+ const mapImage = mapStage.findOne("#mapImage");
+ return getBrushPositionForTool(
+ map,
+ getRelativePointerPositionNormalized(mapImage),
+ map.snapToGrid,
+ false,
+ gridSize,
+ []
+ );
+ }
+
+ function handleBrushDown() {
+ const brushPosition = getBrushPosition();
+ setNoteData({
+ x: brushPosition.x,
+ y: brushPosition.y,
+ size: defaultNoteSize,
+ text: "",
+ id: shortid.generate(),
+ lastModified: Date.now(),
+ lastModifiedBy: userId,
+ visible: true,
+ locked: false,
+ color: "yellow",
+ });
+ setIsBrushDown(true);
+ }
+
+ function handleBrushMove() {
+ if (noteData) {
+ const brushPosition = getBrushPosition();
+ setNoteData((prev) => ({
+ ...prev,
+ x: brushPosition.x,
+ y: brushPosition.y,
+ }));
+ setIsBrushDown(true);
+ }
+ }
+
+ function handleBrushUp() {
+ if (noteData) {
+ onNoteAdd(noteData);
+ onNoteMenuOpen(noteData.id, creatingNoteRef.current);
+ }
+ setNoteData(null);
+ setIsBrushDown(false);
+ }
+
+ interactionEmitter.on("dragStart", handleBrushDown);
+ interactionEmitter.on("drag", handleBrushMove);
+ interactionEmitter.on("dragEnd", handleBrushUp);
+
+ return () => {
+ interactionEmitter.off("dragStart", handleBrushDown);
+ interactionEmitter.off("drag", handleBrushMove);
+ interactionEmitter.off("dragEnd", handleBrushUp);
+ };
+ });
+
+ return (
+
+ {notes.map((note) => (
+
+ ))}
+
+ {isBrushDown && noteData && (
+
+ )}
+
+
+ );
+}
+
+export default MapNotes;
diff --git a/src/components/map/MapSettings.js b/src/components/map/MapSettings.js
index 3c6181b..622bd0d 100644
--- a/src/components/map/MapSettings.js
+++ b/src/components/map/MapSettings.js
@@ -233,6 +233,16 @@ function MapSettings({
/>
Tokens
+
>
diff --git a/src/components/map/MapTile.js b/src/components/map/MapTile.js
index 4f155ce..ae3af7f 100644
--- a/src/components/map/MapTile.js
+++ b/src/components/map/MapTile.js
@@ -11,7 +11,7 @@ function MapTile({
onMapSelect,
onMapEdit,
onDone,
- large,
+ size,
canEdit,
badges,
}) {
@@ -34,7 +34,7 @@ function MapTile({
onSelect={() => onMapSelect(map)}
onEdit={() => onMapEdit(map.id)}
onDoubleClick={onDone}
- large={large}
+ size={size}
canEdit={canEdit}
badges={badges}
editTitle="Edit Map"
diff --git a/src/components/map/MapTiles.js b/src/components/map/MapTiles.js
index aba9e16..296ec1a 100644
--- a/src/components/map/MapTiles.js
+++ b/src/components/map/MapTiles.js
@@ -1,7 +1,6 @@
import React, { useContext } from "react";
import { Flex, Box, Text, IconButton, Close, Label } from "theme-ui";
import SimpleBar from "simplebar-react";
-import { useMedia } from "react-media";
import Case from "case";
import RemoveMapIcon from "../../icons/RemoveMapIcon";
@@ -14,6 +13,8 @@ import FilterBar from "../FilterBar";
import DatabaseContext from "../../contexts/DatabaseContext";
+import useResponsiveLayout from "../../helpers/useResponsiveLayout";
+
function MapTiles({
maps,
groups,
@@ -32,14 +33,15 @@ function MapTiles({
onMapsGroup,
}) {
const { databaseStatus } = useContext(DatabaseContext);
- const isSmallScreen = useMedia({ query: "(max-width: 500px)" });
+ const layout = useResponsiveLayout();
let hasMapState = false;
for (let state of selectedMapStates) {
if (
Object.values(state.tokens).length > 0 ||
state.mapDrawActions.length > 0 ||
- state.fogDrawActions.length > 0
+ state.fogDrawActions.length > 0 ||
+ Object.values(state.notes).length > 0
) {
hasMapState = true;
break;
@@ -60,7 +62,7 @@ function MapTiles({
onMapSelect={onMapSelect}
onMapEdit={onMapEdit}
onDone={onDone}
- large={isSmallScreen}
+ size={layout.tileSize}
canEdit={
isSelected && selectMode === "single" && selectedMaps.length === 1
}
@@ -82,15 +84,18 @@ function MapTiles({
onAdd={onMapAdd}
addTitle="Add Map"
/>
-
+
onMapSelect()}
@@ -113,6 +118,7 @@ function MapTiles({
left: 0,
right: 0,
textAlign: "center",
+ borderRadius: "2px",
}}
bg="highlight"
p={1}
diff --git a/src/components/map/MapToken.js b/src/components/map/MapToken.js
index 585807e..995edf0 100644
--- a/src/components/map/MapToken.js
+++ b/src/components/map/MapToken.js
@@ -7,7 +7,7 @@ import Konva from "konva";
import useDataSource from "../../helpers/useDataSource";
import useDebounce from "../../helpers/useDebounce";
import usePrevious from "../../helpers/usePrevious";
-import * as Vector2 from "../../helpers/vector2";
+import { snapNodeToMap } from "../../helpers/map";
import AuthContext from "../../contexts/AuthContext";
import MapInteractionContext from "../../contexts/MapInteractionContext";
@@ -17,9 +17,6 @@ import TokenLabel from "../token/TokenLabel";
import { tokenSources, unknownSource } from "../../tokens";
-// Enable hit detection on drag to allow for vehicle tokens
-Konva.hitOnDragEnabled = true;
-
const snappingThreshold = 1 / 7;
function MapToken({
@@ -58,6 +55,9 @@ function MapToken({
const tokenImage = imageRef.current;
if (token && token.category === "vehicle") {
+ // Enable hit detection for .intersects() function
+ Konva.hitOnDragEnabled = true;
+
// Find all other tokens on the map
const layer = tokenGroup.getLayer();
const tokens = layer.find(".character");
@@ -86,35 +86,7 @@ function MapToken({
const tokenGroup = event.target;
// Snap to corners of grid
if (map.snapToGrid) {
- const offset = Vector2.multiply(map.grid.inset.topLeft, {
- x: mapWidth,
- y: mapHeight,
- });
- const position = {
- x: tokenGroup.x() + tokenGroup.width() / 2,
- y: tokenGroup.y() + tokenGroup.height() / 2,
- };
- const gridSize = {
- x:
- (mapWidth *
- (map.grid.inset.bottomRight.x - map.grid.inset.topLeft.x)) /
- map.grid.size.x,
- y:
- (mapHeight *
- (map.grid.inset.bottomRight.y - map.grid.inset.topLeft.y)) /
- map.grid.size.y,
- };
- // Transform into offset space, round, then transform back
- const gridSnap = Vector2.add(
- Vector2.roundTo(Vector2.subtract(position, offset), gridSize),
- offset
- );
- const gridDistance = Vector2.length(Vector2.subtract(gridSnap, position));
- const minGrid = Vector2.min(gridSize);
- if (gridDistance < minGrid * snappingThreshold) {
- tokenGroup.x(gridSnap.x - tokenGroup.width() / 2);
- tokenGroup.y(gridSnap.y - tokenGroup.height() / 2);
- }
+ snapNodeToMap(map, mapWidth, mapHeight, tokenGroup, snappingThreshold);
}
}
@@ -123,6 +95,8 @@ function MapToken({
const mountChanges = {};
if (token && token.category === "vehicle") {
+ Konva.hitOnDragEnabled = false;
+
const parent = tokenGroup.getParent();
const mountedTokens = tokenGroup.find(".character");
for (let mountedToken of mountedTokens) {
@@ -209,20 +183,30 @@ function MapToken({
const imageRef = useRef();
useEffect(() => {
const image = imageRef.current;
- if (
- image &&
- tokenSourceStatus === "loaded" &&
- tokenWidth > 0 &&
- tokenHeight > 0
- ) {
+ if (!image) {
+ return;
+ }
+
+ const canvas = image.getCanvas();
+ const pixelRatio = canvas.pixelRatio || 1;
+
+ if (tokenSourceStatus === "loaded" && tokenWidth > 0 && tokenHeight > 0) {
+ const maxImageSize = token ? Math.max(token.width, token.height) : 512; // Default to 512px
+ const maxTokenSize = Math.max(tokenWidth, tokenHeight);
+ // Constrain image buffer to original image size
+ const maxRatio = maxImageSize / maxTokenSize;
+
image.cache({
- pixelRatio: debouncedStageScale * window.devicePixelRatio,
+ pixelRatio: Math.min(
+ Math.max(debouncedStageScale * pixelRatio, 1),
+ maxRatio
+ ),
});
image.drawHitFromCache();
// Force redraw
image.getLayer().draw();
}
- }, [debouncedStageScale, tokenWidth, tokenHeight, tokenSourceStatus]);
+ }, [debouncedStageScale, tokenWidth, tokenHeight, tokenSourceStatus, token]);
// Animate to new token positions if edited by others
const tokenX = tokenState.x * mapWidth;
diff --git a/src/components/map/controls/EdgeSnappingToggle.js b/src/components/map/controls/EdgeSnappingToggle.js
index f04bedf..44f7a10 100644
--- a/src/components/map/controls/EdgeSnappingToggle.js
+++ b/src/components/map/controls/EdgeSnappingToggle.js
@@ -4,7 +4,11 @@ import { IconButton } from "theme-ui";
import SnappingOnIcon from "../../../icons/SnappingOnIcon";
import SnappingOffIcon from "../../../icons/SnappingOffIcon";
-function EdgeSnappingToggle({ useEdgeSnapping, onEdgeSnappingChange }) {
+function EdgeSnappingToggle({
+ useEdgeSnapping,
+ onEdgeSnappingChange,
+ disabled,
+}) {
return (
onEdgeSnappingChange(!useEdgeSnapping)}
+ disabled={disabled}
>
{useEdgeSnapping ? : }
diff --git a/src/components/map/controls/FogCutToggle.js b/src/components/map/controls/FogCutToggle.js
new file mode 100644
index 0000000..d401e59
--- /dev/null
+++ b/src/components/map/controls/FogCutToggle.js
@@ -0,0 +1,22 @@
+import React from "react";
+import { IconButton } from "theme-ui";
+
+import CutOnIcon from "../../../icons/FogCutOnIcon";
+import CutOffIcon from "../../../icons/FogCutOffIcon";
+
+function FogCutToggle({ useFogCut, onFogCutChange, disabled }) {
+ return (
+ onFogCutChange(!useFogCut)}
+ disabled={disabled}
+ >
+ {useFogCut ? : }
+
+ );
+}
+
+export default FogCutToggle;
diff --git a/src/components/map/controls/FogToolSettings.js b/src/components/map/controls/FogToolSettings.js
index 3ee8a28..e9b7d0f 100644
--- a/src/components/map/controls/FogToolSettings.js
+++ b/src/components/map/controls/FogToolSettings.js
@@ -6,13 +6,13 @@ import RadioIconButton from "../../RadioIconButton";
import EdgeSnappingToggle from "./EdgeSnappingToggle";
import FogPreviewToggle from "./FogPreviewToggle";
+import FogCutToggle from "./FogCutToggle";
import FogBrushIcon from "../../../icons/FogBrushIcon";
import FogPolygonIcon from "../../../icons/FogPolygonIcon";
import FogRemoveIcon from "../../../icons/FogRemoveIcon";
import FogToggleIcon from "../../../icons/FogToggleIcon";
-import FogAddIcon from "../../../icons/FogAddIcon";
-import FogSubtractIcon from "../../../icons/FogSubtractIcon";
+import FogRectangleIcon from "../../../icons/FogRectangleIcon";
import UndoButton from "./UndoButton";
import RedoButton from "./RedoButton";
@@ -31,19 +31,23 @@ function BrushToolSettings({
// Keyboard shortcuts
function handleKeyDown({ key, ctrlKey, metaKey, shiftKey }) {
if (key === "Alt") {
- onSettingChange({ useFogSubtract: !settings.useFogSubtract });
+ onSettingChange({ useFogCut: !settings.useFogCut });
} else if (key === "p") {
onSettingChange({ type: "polygon" });
} else if (key === "b") {
onSettingChange({ type: "brush" });
} else if (key === "t") {
onSettingChange({ type: "toggle" });
- } else if (key === "r") {
+ } else if (key === "e") {
onSettingChange({ type: "remove" });
} else if (key === "s") {
onSettingChange({ useEdgeSnapping: !settings.useEdgeSnapping });
} else if (key === "f") {
onSettingChange({ preview: !settings.preview });
+ } else if (key === "c") {
+ onSettingChange({ useFogCut: !settings.useFogCut });
+ } else if (key === "r") {
+ onSettingChange({ type: "rectangle" });
} else if (
(key === "z" || key === "Z") &&
(ctrlKey || metaKey) &&
@@ -63,7 +67,7 @@ function BrushToolSettings({
function handleKeyUp({ key }) {
if (key === "Alt") {
- onSettingChange({ useFogSubtract: !settings.useFogSubtract });
+ onSettingChange({ useFogCut: !settings.useFogCut });
}
}
@@ -76,27 +80,21 @@ function BrushToolSettings({
title: "Fog Polygon (P)",
isSelected: settings.type === "polygon",
icon: ,
+ disabled: settings.preview,
+ },
+ {
+ id: "rectangle",
+ title: "Fog Rectangle (R)",
+ isSelected: settings.type === "rectangle",
+ icon: ,
+ disabled: settings.preview,
},
{
id: "brush",
title: "Fog Brush (B)",
isSelected: settings.type === "brush",
icon: ,
- },
- ];
-
- const modeTools = [
- {
- id: "add",
- title: "Add Fog",
- isSelected: !settings.useFogSubtract,
- icon: ,
- },
- {
- id: "subtract",
- title: "Subtract Fog",
- isSelected: settings.useFogSubtract,
- icon: ,
+ disabled: settings.preview,
},
];
@@ -112,30 +110,30 @@ function BrushToolSettings({
title="Toggle Fog (T)"
onClick={() => onSettingChange({ type: "toggle" })}
isSelected={settings.type === "toggle"}
+ disabled={settings.preview}
>
onSettingChange({ type: "remove" })}
isSelected={settings.type === "remove"}
+ disabled={settings.preview}
>
-
- onSettingChange({ useFogSubtract: tool.id === "subtract" })
- }
- collapse={isSmallScreen}
+ onSettingChange({ useFogCut })}
+ disabled={settings.preview}
/>
-
onSettingChange({ useEdgeSnapping })
}
+ disabled={settings.preview}
/>
,
},
+ {
+ id: "alternating",
+ title: "Alternating Diagonal Distance (A)",
+ isSelected: settings.type === "alternating",
+ icon: ,
+ },
{
id: "euclidean",
title: "Line Distance (L)",
diff --git a/src/components/map/controls/ToolSection.js b/src/components/map/controls/ToolSection.js
index a6baeb7..25b37a4 100644
--- a/src/components/map/controls/ToolSection.js
+++ b/src/components/map/controls/ToolSection.js
@@ -36,6 +36,7 @@ function ToolSection({ collapse, tools, onToolClick }) {
onClick={() => handleToolClick(tool)}
key={tool.id}
isSelected={tool.isSelected}
+ disabled={tool.disabled}
>
{tool.icon}
@@ -90,6 +91,7 @@ function ToolSection({ collapse, tools, onToolClick }) {
onClick={() => handleToolClick(tool)}
key={tool.id}
isSelected={tool.isSelected}
+ disabled={tool.disabled}
>
{tool.icon}
diff --git a/src/components/note/Note.js b/src/components/note/Note.js
new file mode 100644
index 0000000..c52e202
--- /dev/null
+++ b/src/components/note/Note.js
@@ -0,0 +1,195 @@
+import React, { useContext, useEffect, useState, useRef } from "react";
+import { Rect, Text } from "react-konva";
+import { useSpring, animated } from "react-spring/konva";
+
+import AuthContext from "../../contexts/AuthContext";
+import MapInteractionContext from "../../contexts/MapInteractionContext";
+
+import { snapNodeToMap } from "../../helpers/map";
+import colors from "../../helpers/colors";
+import usePrevious from "../../helpers/usePrevious";
+
+const snappingThreshold = 1 / 5;
+
+function Note({
+ note,
+ map,
+ onNoteChange,
+ onNoteMenuOpen,
+ draggable,
+ onNoteDragStart,
+ onNoteDragEnd,
+}) {
+ const { userId } = useContext(AuthContext);
+ const { mapWidth, mapHeight, setPreventMapInteraction } = useContext(
+ MapInteractionContext
+ );
+
+ const noteWidth = map && (mapWidth / map.grid.size.x) * note.size;
+ const noteHeight = noteWidth;
+ const notePadding = noteWidth / 10;
+
+ function handleDragStart(event) {
+ onNoteDragStart && onNoteDragStart(event, note.id);
+ }
+
+ function handleDragMove(event) {
+ const noteGroup = event.target;
+ // Snap to corners of grid
+ if (map.snapToGrid) {
+ snapNodeToMap(map, mapWidth, mapHeight, noteGroup, snappingThreshold);
+ }
+ }
+
+ function handleDragEnd(event) {
+ const noteGroup = event.target;
+ onNoteChange &&
+ onNoteChange({
+ ...note,
+ x: noteGroup.x() / mapWidth,
+ y: noteGroup.y() / mapHeight,
+ lastModifiedBy: userId,
+ lastModified: Date.now(),
+ });
+ onNoteDragEnd && onNoteDragEnd(note.id);
+ setPreventMapInteraction(false);
+ }
+
+ function handleClick(event) {
+ if (draggable) {
+ const noteNode = event.target;
+ onNoteMenuOpen && onNoteMenuOpen(note.id, noteNode);
+ }
+ }
+
+ // Store note pointer down time to check for a click when note is locked
+ const notePointerDownTimeRef = useRef();
+ function handlePointerDown(event) {
+ if (draggable) {
+ setPreventMapInteraction(true);
+ }
+ if (note.locked && map.owner === userId) {
+ notePointerDownTimeRef.current = event.evt.timeStamp;
+ }
+ }
+
+ function handlePointerUp(event) {
+ if (draggable) {
+ setPreventMapInteraction(false);
+ }
+ // Check note click when locked and we are the map owner
+ // We can't use onClick because that doesn't check pointer distance
+ if (note.locked && map.owner === userId) {
+ // If down and up time is small trigger a click
+ const delta = event.evt.timeStamp - notePointerDownTimeRef.current;
+ if (delta < 300) {
+ const noteNode = event.target;
+ onNoteMenuOpen(note.id, noteNode);
+ }
+ }
+ }
+
+ const [fontSize, setFontSize] = useState(1);
+ useEffect(() => {
+ const text = textRef.current;
+
+ if (!text) {
+ return;
+ }
+
+ function findFontSize() {
+ // Create an array from 1 / 10 of the note height to the full note height
+ const sizes = Array.from(
+ { length: Math.ceil(noteHeight - notePadding * 2) },
+ (_, i) => i + Math.ceil(noteHeight / 10)
+ );
+
+ if (sizes.length > 0) {
+ const size = sizes.reduce((prev, curr) => {
+ text.fontSize(curr);
+ const width = text.getTextWidth() + notePadding * 2;
+ const height = text.height() + notePadding * 2;
+ if (width < noteWidth && height < noteHeight) {
+ return curr;
+ } else {
+ return prev;
+ }
+ });
+
+ setFontSize(size);
+ }
+ }
+
+ findFontSize();
+ }, [note, note.text, noteWidth, noteHeight, notePadding]);
+
+ const textRef = useRef();
+
+ // Animate to new note positions if edited by others
+ const noteX = note.x * mapWidth;
+ const noteY = note.y * mapHeight;
+ const previousWidth = usePrevious(mapWidth);
+ const previousHeight = usePrevious(mapHeight);
+ const resized = mapWidth !== previousWidth || mapHeight !== previousHeight;
+ const skipAnimation = note.lastModifiedBy === userId || resized;
+ const props = useSpring({
+ x: noteX,
+ y: noteY,
+ immediate: skipAnimation,
+ });
+
+ // When a note is hidden if you aren't the map owner hide it completely
+ if (map && !note.visible && map.owner !== userId) {
+ return null;
+ }
+
+ return (
+
+
+
+ {/* Use an invisible text block to work out text sizing */}
+
+
+ );
+}
+
+export default Note;
diff --git a/src/components/note/NoteDragOverlay.js b/src/components/note/NoteDragOverlay.js
new file mode 100644
index 0000000..9df0713
--- /dev/null
+++ b/src/components/note/NoteDragOverlay.js
@@ -0,0 +1,19 @@
+import React from "react";
+
+import DragOverlay from "../DragOverlay";
+
+function NoteDragOverlay({ onNoteRemove, noteId, noteGroup, dragging }) {
+ function handleNoteRemove() {
+ onNoteRemove(noteId);
+ }
+
+ return (
+
+ );
+}
+
+export default NoteDragOverlay;
diff --git a/src/components/note/NoteMenu.js b/src/components/note/NoteMenu.js
new file mode 100644
index 0000000..cc0379e
--- /dev/null
+++ b/src/components/note/NoteMenu.js
@@ -0,0 +1,219 @@
+import React, { useEffect, useState, useContext } from "react";
+import { Box, Flex, Text, IconButton, Textarea } from "theme-ui";
+
+import Slider from "../Slider";
+
+import MapMenu from "../map/MapMenu";
+
+import colors, { colorOptions } from "../../helpers/colors";
+
+import usePrevious from "../../helpers/usePrevious";
+
+import LockIcon from "../../icons/TokenLockIcon";
+import UnlockIcon from "../../icons/TokenUnlockIcon";
+import ShowIcon from "../../icons/TokenShowIcon";
+import HideIcon from "../../icons/TokenHideIcon";
+
+import AuthContext from "../../contexts/AuthContext";
+
+const defaultNoteMaxSize = 6;
+
+function NoteMenu({
+ isOpen,
+ onRequestClose,
+ note,
+ noteNode,
+ onNoteChange,
+ map,
+}) {
+ const { userId } = useContext(AuthContext);
+
+ const wasOpen = usePrevious(isOpen);
+
+ const [noteMaxSize, setNoteMaxSize] = useState(defaultNoteMaxSize);
+ const [menuLeft, setMenuLeft] = useState(0);
+ const [menuTop, setMenuTop] = useState(0);
+ useEffect(() => {
+ if (isOpen && !wasOpen && note) {
+ setNoteMaxSize(Math.max(note.size, defaultNoteMaxSize));
+ // Update menu position
+ if (noteNode) {
+ const nodeRect = noteNode.getClientRect();
+ const mapElement = document.querySelector(".map");
+ const mapRect = mapElement.getBoundingClientRect();
+
+ // Center X for the menu which is 156px wide
+ setMenuLeft(mapRect.left + nodeRect.x + nodeRect.width / 2 - 156 / 2);
+ // Y 12px from the bottom
+ setMenuTop(mapRect.top + nodeRect.y + nodeRect.height + 12);
+ }
+ }
+ }, [isOpen, note, wasOpen, noteNode]);
+
+ function handleTextChange(event) {
+ const text = event.target.value.substring(0, 144);
+ note && onNoteChange({ ...note, text: text });
+ }
+
+ function handleColorChange(color) {
+ if (!note) {
+ return;
+ }
+ onNoteChange({ ...note, color: color });
+ }
+
+ function handleSizeChange(event) {
+ const newSize = parseFloat(event.target.value);
+ note && onNoteChange({ ...note, size: newSize });
+ }
+
+ function handleVisibleChange() {
+ note && onNoteChange({ ...note, visible: !note.visible });
+ }
+
+ function handleLockChange() {
+ note && onNoteChange({ ...note, locked: !note.locked });
+ }
+
+ function handleModalContent(node) {
+ if (node) {
+ // Focus input
+ const tokenLabelInput = node.querySelector("#changeNoteText");
+ tokenLabelInput.focus();
+ tokenLabelInput.select();
+
+ // Ensure menu is in bounds
+ const nodeRect = node.getBoundingClientRect();
+ const mapElement = document.querySelector(".map");
+ const mapRect = mapElement.getBoundingClientRect();
+ setMenuLeft((prevLeft) =>
+ Math.min(
+ mapRect.right - nodeRect.width,
+ Math.max(mapRect.left, prevLeft)
+ )
+ );
+ setMenuTop((prevTop) =>
+ Math.min(mapRect.bottom - nodeRect.height, prevTop)
+ );
+ }
+ }
+
+ function handleTextKeyPress(e) {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ onRequestClose();
+ }
+ }
+
+ return (
+
+
+ {
+ e.preventDefault();
+ onRequestClose();
+ }}
+ sx={{ alignItems: "center" }}
+ >
+
+
+
+ {colorOptions.map((color) => (
+ handleColorChange(color)}
+ aria-label={`Note label Color ${color}`}
+ >
+ {note && note.color === color && (
+
+ )}
+
+ ))}
+
+
+
+ Size:
+
+
+
+ {/* Only show hide and lock token actions to map owners */}
+ {map && map.owner === userId && (
+
+
+ {note && note.visible ? : }
+
+
+ {note && note.locked ? : }
+
+
+ )}
+
+
+ );
+}
+
+export default NoteMenu;
diff --git a/src/components/party/DiceRolls.js b/src/components/party/DiceRolls.js
index 01d1ccc..bc05f1e 100644
--- a/src/components/party/DiceRolls.js
+++ b/src/components/party/DiceRolls.js
@@ -36,7 +36,6 @@ function DiceRolls({ rolls }) {
{expanded && (
({ ...prevState, timer: newTimer }));
+ }
+
+ function handleTimerStop() {
+ setPlayerState((prevState) => ({ ...prevState, timer: null }));
+ }
+
+ useEffect(() => {
+ let prevTime = performance.now();
+ let request = requestAnimationFrame(update);
+ let counter = 0;
+ function update(time) {
+ request = requestAnimationFrame(update);
+ const deltaTime = time - prevTime;
+ prevTime = time;
+
+ if (playerState.timer) {
+ counter += deltaTime;
+ // Update timer every second
+ if (counter > 1000) {
+ const newTimer = {
+ ...playerState.timer,
+ current: playerState.timer.current - counter,
+ };
+ if (newTimer.current < 0) {
+ setPlayerState((prevState) => ({ ...prevState, timer: null }));
+ } else {
+ setPlayerState((prevState) => ({ ...prevState, timer: newTimer }));
+ }
+ counter = 0;
+ }
+ }
+ }
+ return () => {
+ cancelAnimationFrame(request);
+ };
+ }, [playerState.timer, setPlayerState]);
+
+ function handleNicknameChange(newNickname) {
+ setPlayerState((prevState) => ({ ...prevState, nickname: newNickname }));
+ }
+
+ function handleDiceRollsChange(newDiceRolls) {
+ setPlayerState(
+ (prevState) => ({
+ ...prevState,
+ dice: { share: shareDice, rolls: newDiceRolls },
+ }),
+ shareDice
+ );
+ }
+
+ function handleShareDiceChange(newShareDice) {
+ setShareDice(newShareDice);
+ setPlayerState((prevState) => ({
+ ...prevState,
+ dice: { ...prevState.dice, share: newShareDice },
+ }));
+ }
return (
- {Object.entries(partyNicknames).map(([id, partyNickname]) => (
+ {Object.entries(partyState).map(([id, { nickname, dice }]) => (
- ))}
- {timer && }
- {Object.entries(partyTimers).map(([id, partyTimer], index) => (
-
))}
+ {playerState.timer && }
+ {Object.entries(partyState)
+ .filter(([_, { timer }]) => timer)
+ .map(([id, { timer }], index) => (
+
+ ))}
);
diff --git a/src/components/party/Stream.js b/src/components/party/Stream.js
index 809db3d..d3e7bcc 100644
--- a/src/components/party/Stream.js
+++ b/src/components/party/Stream.js
@@ -1,9 +1,10 @@
import React, { useState, useRef, useEffect } from "react";
-import { Text, IconButton, Box, Slider, Flex } from "theme-ui";
+import { Text, IconButton, Box, Flex } from "theme-ui";
import StreamMuteIcon from "../../icons/StreamMuteIcon";
import Banner from "../Banner";
+import Slider from "../Slider";
function Stream({ stream, nickname }) {
const [streamVolume, setStreamVolume] = useState(1);
diff --git a/src/components/token/TokenDragOverlay.js b/src/components/token/TokenDragOverlay.js
index 68b9d29..4d90e9a 100644
--- a/src/components/token/TokenDragOverlay.js
+++ b/src/components/token/TokenDragOverlay.js
@@ -1,11 +1,10 @@
-import React, { useContext, useEffect, useRef, useState } from "react";
-import { Box, IconButton } from "theme-ui";
-
-import RemoveTokenIcon from "../../icons/RemoveTokenIcon";
+import React, { useContext } from "react";
import AuthContext from "../../contexts/AuthContext";
import MapInteractionContext from "../../contexts/MapInteractionContext";
+import DragOverlay from "../DragOverlay";
+
function TokenDragOverlay({
onTokenStateRemove,
onTokenStateChange,
@@ -16,114 +15,38 @@ function TokenDragOverlay({
mapState,
}) {
const { userId } = useContext(AuthContext);
- const { setPreventMapInteraction, mapWidth, mapHeight } = useContext(
- MapInteractionContext
- );
+ const { mapWidth, mapHeight } = useContext(MapInteractionContext);
- const [isRemoveHovered, setIsRemoveHovered] = useState(false);
- const removeTokenRef = useRef();
-
- // Detect token hover on remove icon manually to support touch devices
- useEffect(() => {
- const map = document.querySelector(".map");
- const mapRect = map.getBoundingClientRect();
-
- function detectRemoveHover() {
- if (!tokenGroup) {
- return;
- }
-
- const pointerPosition = tokenGroup.getStage().getPointerPosition();
- const screenSpacePointerPosition = {
- x: pointerPosition.x + mapRect.left,
- y: pointerPosition.y + mapRect.top,
- };
- if (!removeTokenRef.current) {
- return;
- }
- const removeIconPosition = removeTokenRef.current.getBoundingClientRect();
-
- if (
- screenSpacePointerPosition.x > removeIconPosition.left &&
- screenSpacePointerPosition.y > removeIconPosition.top &&
- screenSpacePointerPosition.x < removeIconPosition.right &&
- screenSpacePointerPosition.y < removeIconPosition.bottom
- ) {
- if (!isRemoveHovered) {
- setIsRemoveHovered(true);
- }
- } else if (isRemoveHovered) {
- setIsRemoveHovered(false);
+ function handleTokenRemove() {
+ // Handle other tokens when a vehicle gets deleted
+ if (token && token.category === "vehicle") {
+ const layer = tokenGroup.getLayer();
+ const mountedTokens = tokenGroup.find(".token");
+ for (let mountedToken of mountedTokens) {
+ // Save and restore token position after moving layer
+ const position = mountedToken.absolutePosition();
+ mountedToken.moveTo(layer);
+ mountedToken.absolutePosition(position);
+ onTokenStateChange({
+ [mountedToken.id()]: {
+ ...mapState.tokens[mountedToken.id()],
+ x: mountedToken.x() / mapWidth,
+ y: mountedToken.y() / mapHeight,
+ lastModifiedBy: userId,
+ lastModified: Date.now(),
+ },
+ });
}
}
-
- let handler;
- if (tokenState && tokenGroup && dragging) {
- handler = setInterval(detectRemoveHover, 100);
- }
-
- return () => {
- if (handler) {
- clearInterval(handler);
- }
- };
- }, [tokenState, tokenGroup, isRemoveHovered, dragging]);
-
- // Detect drag end of token image and remove it if it is over the remove icon
- useEffect(() => {
- function handleTokenDragEnd() {
- // Handle other tokens when a vehicle gets deleted
- if (token && token.category === "vehicle") {
- const layer = tokenGroup.getLayer();
- const mountedTokens = tokenGroup.find(".token");
- for (let mountedToken of mountedTokens) {
- // Save and restore token position after moving layer
- const position = mountedToken.absolutePosition();
- mountedToken.moveTo(layer);
- mountedToken.absolutePosition(position);
- onTokenStateChange({
- [mountedToken.id()]: {
- ...mapState.tokens[mountedToken.id()],
- x: mountedToken.x() / mapWidth,
- y: mountedToken.y() / mapHeight,
- lastModifiedBy: userId,
- lastModified: Date.now(),
- },
- });
- }
- }
- onTokenStateRemove(tokenState);
- setPreventMapInteraction(false);
- }
-
- if (!dragging && tokenState && isRemoveHovered) {
- handleTokenDragEnd();
- }
- });
+ onTokenStateRemove(tokenState);
+ }
return (
- dragging && (
-
-
-
-
-
- )
+
);
}
diff --git a/src/components/token/TokenMenu.js b/src/components/token/TokenMenu.js
index bf9679e..b009a81 100644
--- a/src/components/token/TokenMenu.js
+++ b/src/components/token/TokenMenu.js
@@ -1,5 +1,7 @@
import React, { useEffect, useState, useContext } from "react";
-import { Box, Input, Slider, Flex, Text, IconButton } from "theme-ui";
+import { Box, Input, Flex, Text, IconButton } from "theme-ui";
+
+import Slider from "../Slider";
import MapMenu from "../map/MapMenu";
@@ -48,7 +50,7 @@ function TokenMenu({
}, [isOpen, tokenState, wasOpen, tokenImage]);
function handleLabelChange(event) {
- const label = event.target.value;
+ const label = event.target.value.substring(0, 144);
tokenState &&
onTokenStateChange({ [tokenState.id]: { ...tokenState, label: label } });
}
@@ -70,7 +72,7 @@ function TokenMenu({
}
function handleSizeChange(event) {
- const newSize = parseInt(event.target.value);
+ const newSize = parseFloat(event.target.value);
tokenState &&
onTokenStateChange({ [tokenState.id]: { ...tokenState, size: newSize } });
}
@@ -209,8 +211,8 @@ function TokenMenu({
diff --git a/src/components/token/TokenPreview.js b/src/components/token/TokenPreview.js
index 752a814..6ff8f29 100644
--- a/src/components/token/TokenPreview.js
+++ b/src/components/token/TokenPreview.js
@@ -8,6 +8,7 @@ import usePreventOverscroll from "../../helpers/usePreventOverscroll";
import useStageInteraction from "../../helpers/useStageInteraction";
import useDataSource from "../../helpers/useDataSource";
import useImageCenter from "../../helpers/useImageCenter";
+import useResponsiveLayout from "../../helpers/useResponsiveLayout";
import GridOnIcon from "../../icons/GridOnIcon";
import GridOffIcon from "../../icons/GridOffIcon";
@@ -71,18 +72,20 @@ function TokenPreview({ token }) {
const gridWidth = tokenWidth;
const gridX = token.defaultSize;
const gridSize = gridWidth / gridX;
- const gridY = Math.ceil(tokenHeight / gridSize);
+ const gridY = Math.round(tokenHeight / gridSize);
const gridHeight = gridY > 0 ? gridY * gridSize : tokenHeight;
const borderWidth = Math.max(
(Math.min(tokenWidth, gridHeight) / 200) * Math.max(1 / stageScale, 1),
1
);
+ const layout = useResponsiveLayout();
+
return (
- onSettingsChange("defaultSize", parseInt(e.target.value))
+ onSettingsChange("defaultSize", parseFloat(e.target.value))
}
disabled={tokenEmpty || token.type === "default"}
min={1}
diff --git a/src/components/token/TokenTile.js b/src/components/token/TokenTile.js
index 3431eae..b2192f3 100644
--- a/src/components/token/TokenTile.js
+++ b/src/components/token/TokenTile.js
@@ -13,7 +13,7 @@ function TokenTile({
isSelected,
onTokenSelect,
onTokenEdit,
- large,
+ size,
canEdit,
badges,
}) {
@@ -26,7 +26,7 @@ function TokenTile({
isSelected={isSelected}
onSelect={() => onTokenSelect(token)}
onEdit={() => onTokenEdit(token.id)}
- large={large}
+ size={size}
canEdit={canEdit}
badges={badges}
editTitle="Edit Token"
diff --git a/src/components/token/TokenTiles.js b/src/components/token/TokenTiles.js
index 0e40612..6a132ed 100644
--- a/src/components/token/TokenTiles.js
+++ b/src/components/token/TokenTiles.js
@@ -1,7 +1,6 @@
import React, { useContext } from "react";
import { Flex, Box, Text, IconButton, Close, Label } from "theme-ui";
import SimpleBar from "simplebar-react";
-import { useMedia } from "react-media";
import Case from "case";
import RemoveTokenIcon from "../../icons/RemoveTokenIcon";
@@ -15,6 +14,8 @@ import FilterBar from "../FilterBar";
import DatabaseContext from "../../contexts/DatabaseContext";
+import useResponsiveLayout from "../../helpers/useResponsiveLayout";
+
function TokenTiles({
tokens,
groups,
@@ -31,7 +32,7 @@ function TokenTiles({
onTokensHide,
}) {
const { databaseStatus } = useContext(DatabaseContext);
- const isSmallScreen = useMedia({ query: "(max-width: 500px)" });
+ const layout = useResponsiveLayout();
let hasSelectedDefaultToken = selectedTokens.some(
(token) => token.type === "default"
@@ -47,7 +48,7 @@ function TokenTiles({
isSelected={isSelected}
onTokenSelect={onTokenSelect}
onTokenEdit={onTokenEdit}
- large={isSmallScreen}
+ size={layout.tileSize}
canEdit={
isSelected &&
token.type !== "default" &&
@@ -87,15 +88,18 @@ function TokenTiles({
onAdd={onTokenAdd}
addTitle="Add Token"
/>
-
+
onTokenSelect()}
@@ -118,6 +122,7 @@ function TokenTiles({
left: 0,
right: 0,
textAlign: "center",
+ borderRadius: "2px",
}}
bg="highlight"
p={1}
diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js
index 6726653..8e5ebca 100644
--- a/src/contexts/AuthContext.js
+++ b/src/contexts/AuthContext.js
@@ -3,7 +3,6 @@ import shortid from "shortid";
import DatabaseContext from "./DatabaseContext";
-import { getRandomMonster } from "../helpers/monsters";
import FakeStorage from "../helpers/FakeStorage";
const AuthContext = React.createContext();
@@ -48,39 +47,8 @@ export function AuthProvider({ children }) {
loadUserId();
}, [database, databaseStatus]);
- const [nickname, setNickname] = useState("");
- useEffect(() => {
- if (!database || databaseStatus === "loading") {
- return;
- }
- async function loadNickname() {
- const storedNickname = await database.table("user").get("nickname");
- if (storedNickname) {
- setNickname(storedNickname.value);
- } else {
- const name = getRandomMonster();
- setNickname(name);
- database.table("user").add({ key: "nickname", value: name });
- }
- }
-
- loadNickname();
- }, [database, databaseStatus]);
-
- useEffect(() => {
- if (
- nickname !== undefined &&
- database !== undefined &&
- databaseStatus !== "loading"
- ) {
- database.table("user").update("nickname", { value: nickname });
- }
- }, [nickname, database, databaseStatus]);
-
const value = {
userId,
- nickname,
- setNickname,
password,
setPassword,
authenticationStatus,
diff --git a/src/contexts/DatabaseContext.js b/src/contexts/DatabaseContext.js
index 98ee4d0..323b49a 100644
--- a/src/contexts/DatabaseContext.js
+++ b/src/contexts/DatabaseContext.js
@@ -1,4 +1,7 @@
import React, { useState, useEffect } from "react";
+import { Box, Text } from "theme-ui";
+
+import Banner from "../components/Banner";
import { getDatabase } from "../database";
@@ -7,6 +10,7 @@ const DatabaseContext = React.createContext();
export function DatabaseProvider({ children }) {
const [database, setDatabase] = useState();
const [databaseStatus, setDatabaseStatus] = useState("loading");
+ const [databaseError, setDatabaseError] = useState();
useEffect(() => {
// Create a test database and open it to see if indexedDB is enabled
@@ -34,15 +38,43 @@ export function DatabaseProvider({ children }) {
await db.open();
window.indexedDB.deleteDatabase("__test");
};
+
+ function handleDatabaseError(event) {
+ if (event.reason.name === "QuotaExceededError") {
+ event.preventDefault();
+ setDatabaseError({
+ name: event.reason.name,
+ message: "Storage Quota Exceeded Please Clear Space and Try Again.",
+ });
+ }
+ }
+ window.addEventListener("unhandledrejection", handleDatabaseError);
+
+ return () => {
+ window.removeEventListener("unhandledrejection", handleDatabaseError);
+ };
}, []);
const value = {
database,
databaseStatus,
+ databaseError,
};
return (
- {children}
+ <>
+ {children}
+ setDatabaseError()}
+ >
+
+
+ {databaseError && databaseError.message}
+
+
+
+ >
);
}
diff --git a/src/contexts/KeyboardContext.js b/src/contexts/KeyboardContext.js
index 867c28d..8736f46 100644
--- a/src/contexts/KeyboardContext.js
+++ b/src/contexts/KeyboardContext.js
@@ -8,7 +8,10 @@ export function KeyboardProvider({ children }) {
useEffect(() => {
function handleKeyDown(event) {
// Ignore text input
- if (event.target instanceof HTMLInputElement) {
+ if (
+ event.target instanceof HTMLInputElement ||
+ event.target instanceof HTMLTextAreaElement
+ ) {
return;
}
keyEmitter.emit("keyDown", event);
@@ -16,7 +19,10 @@ export function KeyboardProvider({ children }) {
function handleKeyUp(event) {
// Ignore text input
- if (event.target instanceof HTMLInputElement) {
+ if (
+ event.target instanceof HTMLInputElement ||
+ event.target instanceof HTMLTextAreaElement
+ ) {
return;
}
keyEmitter.emit("keyUp", event);
diff --git a/src/contexts/MapDataContext.js b/src/contexts/MapDataContext.js
index d9e2092..2119e23 100644
--- a/src/contexts/MapDataContext.js
+++ b/src/contexts/MapDataContext.js
@@ -1,8 +1,11 @@
import React, { useEffect, useState, useContext } from "react";
+import * as Comlink from "comlink";
import AuthContext from "./AuthContext";
import DatabaseContext from "./DatabaseContext";
+import DatabaseWorker from "worker-loader!../workers/DatabaseWorker"; // eslint-disable-line import/no-webpack-loader-syntax
+
import { maps as defaultMaps } from "../maps";
const MapDataContext = React.createContext();
@@ -19,7 +22,8 @@ const defaultMapState = {
fogDrawActionIndex: -1,
fogDrawActions: [],
// Flags to determine what other people can edit
- editFlags: ["drawing", "tokens"],
+ editFlags: ["drawing", "tokens", "notes"],
+ notes: {},
};
export function MapDataProvider({ children }) {
@@ -28,6 +32,8 @@ export function MapDataProvider({ children }) {
const [maps, setMaps] = useState([]);
const [mapStates, setMapStates] = useState([]);
+ const [mapsLoading, setMapsLoading] = useState(true);
+
// Load maps from the database and ensure state is properly setup
useEffect(() => {
if (!userId || !database || databaseStatus === "loading") {
@@ -59,15 +65,16 @@ export function MapDataProvider({ children }) {
}
async function loadMaps() {
- let storedMaps = [];
- // Use a cursor instead of toArray to prevent IPC max size error
- await database.table("maps").each((map) => storedMaps.push(map));
+ const worker = Comlink.wrap(new DatabaseWorker());
+ await worker.loadData("maps");
+ const storedMaps = await worker.data;
const sortedMaps = storedMaps.sort((a, b) => b.created - a.created);
const defaultMapsWithIds = await getDefaultMaps();
const allMaps = [...sortedMaps, ...defaultMapsWithIds];
setMaps(allMaps);
const storedStates = await database.table("states").toArray();
setMapStates(storedStates);
+ setMapsLoading(false);
}
loadMaps();
@@ -136,8 +143,10 @@ export function MapDataProvider({ children }) {
try {
await database.table("maps").update(id, update);
} catch (error) {
+ // if (error.name !== "QuotaExceededError") {
const map = (await getMapFromDB(id)) || {};
await database.table("maps").put({ ...map, id, ...update });
+ // }
}
setMaps((prevMaps) => {
const newMaps = [...prevMaps];
@@ -246,6 +255,7 @@ export function MapDataProvider({ children }) {
putMap,
getMap,
getMapFromDB,
+ mapsLoading,
};
return (
{children}
diff --git a/src/contexts/PartyContext.js b/src/contexts/PartyContext.js
new file mode 100644
index 0000000..1b90af7
--- /dev/null
+++ b/src/contexts/PartyContext.js
@@ -0,0 +1,30 @@
+import React, { useState, useEffect } from "react";
+
+const PartyContext = React.createContext();
+
+export function PartyProvider({ session, children }) {
+ const [partyState, setPartyState] = useState({});
+
+ useEffect(() => {
+ function handleSocketPartyState(partyState) {
+ if (partyState) {
+ const { [session.id]: _, ...otherMembersState } = partyState;
+ setPartyState(otherMembersState);
+ } else {
+ setPartyState({});
+ }
+ }
+
+ session.socket?.on("party_state", handleSocketPartyState);
+
+ return () => {
+ session.socket?.off("party_state", handleSocketPartyState);
+ };
+ });
+
+ return (
+ {children}
+ );
+}
+
+export default PartyContext;
diff --git a/src/contexts/PlayerContext.js b/src/contexts/PlayerContext.js
new file mode 100644
index 0000000..19f1fe0
--- /dev/null
+++ b/src/contexts/PlayerContext.js
@@ -0,0 +1,94 @@
+import React, { useEffect, useContext } from "react";
+
+import useNetworkedState from "../helpers/useNetworkedState";
+import DatabaseContext from "./DatabaseContext";
+import AuthContext from "./AuthContext";
+
+import { getRandomMonster } from "../helpers/monsters";
+
+export const PlayerStateContext = React.createContext();
+export const PlayerUpdaterContext = React.createContext(() => {});
+
+export function PlayerProvider({ session, children }) {
+ const { userId } = useContext(AuthContext);
+ const { database, databaseStatus } = useContext(DatabaseContext);
+
+ const [playerState, setPlayerState] = useNetworkedState(
+ {
+ nickname: "",
+ timer: null,
+ dice: { share: false, rolls: [] },
+ sessionId: null,
+ userId,
+ },
+ session,
+ "player_state",
+ 100,
+ false
+ );
+
+ useEffect(() => {
+ if (!database || databaseStatus === "loading") {
+ return;
+ }
+ async function loadNickname() {
+ const storedNickname = await database.table("user").get("nickname");
+ if (storedNickname !== undefined) {
+ setPlayerState((prevState) => ({
+ ...prevState,
+ nickname: storedNickname.value,
+ }));
+ } else {
+ const name = getRandomMonster();
+ setPlayerState((prevState) => ({ ...prevState, nickname: name }));
+ database.table("user").add({ key: "nickname", value: name });
+ }
+ }
+
+ loadNickname();
+ }, [database, databaseStatus, setPlayerState]);
+
+ useEffect(() => {
+ if (
+ playerState.nickname &&
+ database !== undefined &&
+ databaseStatus !== "loading"
+ ) {
+ database
+ .table("user")
+ .update("nickname", { value: playerState.nickname });
+ }
+ }, [playerState, database, databaseStatus]);
+
+ useEffect(() => {
+ setPlayerState((prevState) => ({
+ ...prevState,
+ userId,
+ }));
+ }, [userId, setPlayerState]);
+
+ useEffect(() => {
+ function handleSocketConnect() {
+ // Set the player state to trigger a sync
+ setPlayerState({ ...playerState, sessionId: session.id });
+ }
+
+ session.on("connected", handleSocketConnect);
+ session.socket?.on("connect", handleSocketConnect);
+ session.socket?.on("reconnect", handleSocketConnect);
+
+ return () => {
+ session.off("connected", handleSocketConnect);
+ session.socket?.off("connect", handleSocketConnect);
+ session.socket?.off("reconnect", handleSocketConnect);
+ };
+ });
+
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/contexts/TokenDataContext.js b/src/contexts/TokenDataContext.js
index ed576de..f94026d 100644
--- a/src/contexts/TokenDataContext.js
+++ b/src/contexts/TokenDataContext.js
@@ -1,8 +1,11 @@
import React, { useEffect, useState, useContext } from "react";
+import * as Comlink from "comlink";
import AuthContext from "./AuthContext";
import DatabaseContext from "./DatabaseContext";
+import DatabaseWorker from "worker-loader!../workers/DatabaseWorker"; // eslint-disable-line import/no-webpack-loader-syntax
+
import { tokens as defaultTokens } from "../tokens";
const TokenDataContext = React.createContext();
@@ -14,6 +17,7 @@ export function TokenDataProvider({ children }) {
const { userId } = useContext(AuthContext);
const [tokens, setTokens] = useState([]);
+ const [tokensLoading, setTokensLoading] = useState(true);
useEffect(() => {
if (!userId || !database || databaseStatus === "loading") {
@@ -33,13 +37,14 @@ export function TokenDataProvider({ children }) {
}
async function loadTokens() {
- let storedTokens = [];
- // Use a cursor instead of toArray to prevent IPC max size error
- await database.table("tokens").each((token) => storedTokens.push(token));
+ const worker = Comlink.wrap(new DatabaseWorker());
+ await worker.loadData("tokens");
+ const storedTokens = await worker.data;
const sortedTokens = storedTokens.sort((a, b) => b.created - a.created);
const defaultTokensWithIds = getDefaultTokes();
const allTokens = [...sortedTokens, ...defaultTokensWithIds];
setTokens(allTokens);
+ setTokensLoading(false);
}
loadTokens();
@@ -160,6 +165,7 @@ export function TokenDataProvider({ children }) {
putToken,
getToken,
tokensById,
+ tokensLoading,
};
return (
diff --git a/src/database.js b/src/database.js
index 8c233fd..6164f28 100644
--- a/src/database.js
+++ b/src/database.js
@@ -268,6 +268,18 @@ function loadVersions(db) {
token.height = tokenSizes[token.id].height;
});
});
+ // v1.7.0 - Added note tool
+ db.version(16)
+ .stores({})
+ .upgrade((tx) => {
+ return tx
+ .table("states")
+ .toCollection()
+ .modify((state) => {
+ state.notes = {};
+ state.editFlags = [...state.editFlags, "notes"];
+ });
+ });
}
// Get the dexie database used in DatabaseContext
diff --git a/src/dice/gemstone/GemstoneDice.js b/src/dice/gemstone/GemstoneDice.js
index fc20df7..1d2b0b3 100644
--- a/src/dice/gemstone/GemstoneDice.js
+++ b/src/dice/gemstone/GemstoneDice.js
@@ -28,7 +28,7 @@ class GemstoneDice extends Dice {
pbr.useMetallnessFromMetallicTextureBlue = true;
pbr.subSurface.isTranslucencyEnabled = true;
- pbr.subSurface.translucencyIntensity = 1.0;
+ pbr.subSurface.translucencyIntensity = 0.2;
pbr.subSurface.minimumThickness = 5;
pbr.subSurface.maximumThickness = 10;
pbr.subSurface.tintColor = new Color3(190 / 255, 0, 220 / 255);
diff --git a/src/dice/glass/GlassDice.js b/src/dice/glass/GlassDice.js
index 2631f53..62a5693 100644
--- a/src/dice/glass/GlassDice.js
+++ b/src/dice/glass/GlassDice.js
@@ -26,9 +26,9 @@ class GlassDice extends Dice {
pbr.metallic = 0;
pbr.subSurface.isRefractionEnabled = true;
pbr.subSurface.indexOfRefraction = 2.0;
- pbr.subSurface.refractionIntensity = 1.2;
+ pbr.subSurface.refractionIntensity = 1.0;
pbr.subSurface.isTranslucencyEnabled = true;
- pbr.subSurface.translucencyIntensity = 2.5;
+ pbr.subSurface.translucencyIntensity = 0.5;
pbr.subSurface.minimumThickness = 10;
pbr.subSurface.maximumThickness = 10;
pbr.subSurface.tintColor = new Color3(43 / 255, 1, 115 / 255);
diff --git a/src/docs/assets/AddPartyMember.mp4 b/src/docs/assets/AddPartyMember.mp4
index 4a2051a..8d0798a 100644
Binary files a/src/docs/assets/AddPartyMember.mp4 and b/src/docs/assets/AddPartyMember.mp4 differ
diff --git a/src/docs/assets/ChangeNickname.mp4 b/src/docs/assets/ChangeNickname.mp4
index 66242a3..d1a4539 100644
Binary files a/src/docs/assets/ChangeNickname.mp4 and b/src/docs/assets/ChangeNickname.mp4 differ
diff --git a/src/docs/assets/DefaultTokens.mp4 b/src/docs/assets/DefaultTokens.mp4
index a80c900..971e2ee 100644
Binary files a/src/docs/assets/DefaultTokens.mp4 and b/src/docs/assets/DefaultTokens.mp4 differ
diff --git a/src/docs/assets/DeletingTokens.mp4 b/src/docs/assets/DeletingTokens.mp4
index f4f9c3e..d7147ee 100644
Binary files a/src/docs/assets/DeletingTokens.mp4 and b/src/docs/assets/DeletingTokens.mp4 differ
diff --git a/src/docs/assets/StartGame.mp4 b/src/docs/assets/StartGame.mp4
index fe808a2..3c4eee3 100644
Binary files a/src/docs/assets/StartGame.mp4 and b/src/docs/assets/StartGame.mp4 differ
diff --git a/src/docs/assets/UsingFog.mp4 b/src/docs/assets/UsingFog.mp4
index 53cbc5d..c1f6fc3 100644
Binary files a/src/docs/assets/UsingFog.mp4 and b/src/docs/assets/UsingFog.mp4 differ
diff --git a/src/docs/assets/UsingMeasure.mp4 b/src/docs/assets/UsingMeasure.mp4
index 321d314..281921a 100644
Binary files a/src/docs/assets/UsingMeasure.mp4 and b/src/docs/assets/UsingMeasure.mp4 differ
diff --git a/src/docs/assets/UsingNotes.mp4 b/src/docs/assets/UsingNotes.mp4
new file mode 100644
index 0000000..8e6d159
Binary files /dev/null and b/src/docs/assets/UsingNotes.mp4 differ
diff --git a/src/docs/assets/WorkingWithTokens.mp4 b/src/docs/assets/WorkingWithTokens.mp4
index a96164b..aae6a4a 100644
Binary files a/src/docs/assets/WorkingWithTokens.mp4 and b/src/docs/assets/WorkingWithTokens.mp4 differ
diff --git a/src/docs/assets/index.js b/src/docs/assets/index.js
index 6c7f584..51c7a1d 100644
--- a/src/docs/assets/index.js
+++ b/src/docs/assets/index.js
@@ -25,6 +25,7 @@ import mapEditor from "./MapEditor.mp4";
import filteringMaps from "./FilteringMaps.mp4";
import groupAndRemovingTokens from "./GroupAndRemovingTokens.mp4";
import filteringTokens from "./FilteringTokens.mp4";
+import usingNotes from "./UsingNotes.mp4";
const assets = {
defaultMaps,
@@ -54,6 +55,7 @@ const assets = {
filteringMaps,
groupAndRemovingTokens,
filteringTokens,
+ usingNotes,
};
export default assets;
diff --git a/src/docs/faq/audio-sharing.md b/src/docs/faq/audio-sharing.md
new file mode 100644
index 0000000..56294f6
--- /dev/null
+++ b/src/docs/faq/audio-sharing.md
@@ -0,0 +1,16 @@
+
+## Audio Sharing
+
+---
+
+### How do I use Audio Sharing?
+
+You can find out how to use Audio Sharing in our how-to [docs](https://www.owlbear.rodeo/how-to#sharingAudio).
+
+### Why isn’t audio sharing working?
+
+If you see "Your browser doesn’t support audio sharing":
+- Be sure that you are using Chrome for sharing audio
+
+If you see "No audio found when sharing audio":
+- Be sure that you have the Share audio checkbox ticked when clicking the Share button.
diff --git a/src/docs/faq/connection.md b/src/docs/faq/connection.md
deleted file mode 100644
index 8c035f7..0000000
--- a/src/docs/faq/connection.md
+++ /dev/null
@@ -1,23 +0,0 @@
-## Connection
-
-### Connection failure.
-
-If you are getting a Connection failed error when trying to connect to a game try these following things.
-
-- Ensure your internet connection is working.
-- If you are using an incognito or private browsing tab try using normal browsing.
-- If both computers are on the same network try connecting them to separate networks. For more info see below.
-
-Owlbear Rodeo uses peer to peer connections to send data between the players. Specifically the [WebRTC API](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API) is used. WebRTC allows the sending of two types of data, the first is media such as a camera or microphone and the second is raw data such as chat messages or in this case the state of the game map.
-
-As at this time we don't support voice or video chat as such we only use the raw data feature of WebRTC. This however can lead to connection issues, specifically with the Safari web browser and connecting between two devices on the same network. This is due a decision made by the Safari team to only allow fully peer to peer connections when the user grants camera permission to the website. Unfortunately that means in order to fully support Safari we would need to ask for camera permission even though we wouldn't be using it. To us that is a bad user experience so we have decided against it at this time.
-
-The good news is that Safari will still work if the two devices are connected to a separate network as we make use of [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) servers which will handle the IP sharing and are not blocked by Safari. So if you're seeing errors and are on the same network as the other person if possible switch to separate networks and try again. For more information about Safari's restrictions on WebRTC see this [bug report](https://bugs.webkit.org/show_bug.cgi?id=173052) on the Webkit site or this [blog post](https://webkit.org/blog/7763/a-closer-look-into-webrtc/).
-
-### WebRTC not supported.
-
-Owlbear Rodeo uses WebRTC to communicate between players. Ensure your browser supports WebRTC. A list of supported browsers can be found [here](https://caniuse.com/#feat=rtcpeerconnection).
-
-### Unable to connect to party.
-
-This can happen when your internet connection is stable but a peer to peer connection wasn't able to be established between party members. Refreshing the page can help in fixing this.
diff --git a/src/docs/faq/saving.md b/src/docs/faq/database.md
similarity index 85%
rename from src/docs/faq/saving.md
rename to src/docs/faq/database.md
index 7190cbe..7372d9c 100644
--- a/src/docs/faq/saving.md
+++ b/src/docs/faq/database.md
@@ -1,5 +1,7 @@
-## Saving
+## Database
-### Database is disabled.
+---
-Owlbear Rodeo uses a local database to store saved data. If you are seeing a database is disabled message this usually means you have data storage disabled. The most common occurrences of this is if you are using Private Browsing modes or in Firefox have the Never Remember History option enabled. The site will still function in these cases however all data will be lost when the page closes or reloads.
+### Database is diasbled.
+
+Owlbear Rodeo uses a local database to store saved data. If you are seeing a database is disabled message this usually means you have data storage disabled. The most common occurrences of this is if you are using Private Browsing modes or in Firefox have the Never Remember History option enabled. The site will still function in these cases however all data will be lost when the page closes or reloads.
\ No newline at end of file
diff --git a/src/docs/faq/general.md b/src/docs/faq/general.md
new file mode 100644
index 0000000..eac38ed
--- /dev/null
+++ b/src/docs/faq/general.md
@@ -0,0 +1,9 @@
+## General
+
+---
+
+### Can I self host Owlbear Rodeo
+At this time we have no plans to offer a self hosted version of Owlbear Rodeo.
+
+### Is Owlbear Rodeo open source
+Owlbear Rodeo is not open source at the moment.
\ No newline at end of file
diff --git a/src/docs/faq/maps.md b/src/docs/faq/maps.md
new file mode 100644
index 0000000..6194ff7
--- /dev/null
+++ b/src/docs/faq/maps.md
@@ -0,0 +1,33 @@
+## Games & Maps
+
+---
+
+### I’ve added a map to my game, why can’t my players see it?
+
+This could be because it’s taking a while to upload. Ensure that your players can see a loading bar at the top of the site. Owlbear Rodeo will do its best to load a lower quality map, before continuing to load in higher quality. See 'How big can my maps be?' in our faqs.
+
+If your players do not see a loading bar at the top of their screen ensure that you don't have an extension that is blocking WebRTC connections. Some VPN extensions can causes this.
+
+### Why do some of my tokens have a question mark on them?
+
+This means that the token hasn’t loaded in just yet. If they still haven’t loaded in after some time, try giving the page a refresh. You should see load progress on the loading bar at the top of the website.
+
+### How big can my maps be?
+
+Owlbear Rodeo doesn't impose a limit on map sizes but keep in mind the larger the map you upload the longer it will take for your players to load. We recommend trying to keep your maps under 10MB with a good internet connection and under 5MB with slower internet. If you accidently upload a map that is too big you can use the quality option in the map's settings to lower the size without needing to re-upload your map.
+
+### Where are my maps stored?
+
+Your maps are stored on your local device. This means that clearing your site data will delete your maps, so please only do so if you have no other options.
+
+### Why am I being prompted for a password when I didn't set one?
+
+If you're game link is over 24hrs old then this could be why you are being prompted for a password. Creating a new game won't affect any maps you have used or prepared previously. See 'How long does a game I create last?' in our faqs.
+
+### How long does a game I create last?
+
+We encourage users to create games every 24hrs. Any maps you have added or made edits to will not be affected and will still be available when you create another game.
+
+### I can join my game but the spinner is constantly loading, why?
+
+This could mean that the service is currently down. Please visit us on Twitter or Reddit and let us know.
\ No newline at end of file
diff --git a/src/docs/howTo/gettingStarted.md b/src/docs/howTo/gettingStarted.md
new file mode 100644
index 0000000..bd26118
--- /dev/null
+++ b/src/docs/howTo/gettingStarted.md
@@ -0,0 +1,11 @@
+1. Start a game to generate a unique URL that can connect you and
+ your players.
+
+ Each game is recycled after 24 hours so make sure you create a new game when you play your next session.
+
+2. Invite players with your unique URL from step 1.
+3. Share a map, roll dice or share audio with your players.
+
+ All data is saved automatically to your computer so next session simply use the same computer and all your maps and tokens will be ready to go.
+
+That's it, no accounts, no paywalls, no ads, just a virtual tabletop.
diff --git a/src/docs/howTo/usingFog.md b/src/docs/howTo/usingFog.md
index 79fc896..544fc36 100644
--- a/src/docs/howTo/usingFog.md
+++ b/src/docs/howTo/usingFog.md
@@ -8,15 +8,15 @@ The Fog Tool allows you to add hidden areas to control what the other party memb
A summary of the Fog Tool options are listed below.
-| Option | Description | Shortcut |
-| ------------- | -------------------------------------------------------------------- | ---------------------------------------------- |
-| Fog Polygon | Click to add points to a fog shape | P, Enter (Accept Shape), Escape (Cancel Shape) |
-| Fog Brush | Drag to add a free form fog shape | B |
-| Toggle Fog | Click a fog shape to hide/show it | T |
-| Remove Fog | Click a fog shape to remove it | R |
-| Add Fog | When selected drawing a fog shape will add it to the scene | Alt (Toggle) |
-| Subtract Fog | When selected drawing a fog shape will subtract it from other shapes | Alt (Toggle) |
-| Edge Snapping | Enables/Disables edge snapping | S |
-| Fog Preview | Enables/Disables a preview of the final fog shapes | F |
-| Undo | Undo a fog action | Ctrl + Z |
-| Redo | Redo a fog action | Ctrl + Shift + Z |
+| Option | Description | Shortcut |
+| ------------- | -------------------------------------------------- | ---------------------------------------------- |
+| Fog Polygon | Click to add points to a fog shape | P, Enter (Accept Shape), Escape (Cancel Shape) |
+| Fog Rectangle | Drag to add a rectangle fog shape | R |
+| Fog Brush | Drag to add a free form fog shape | B |
+| Toggle Fog | Click a fog shape to hide/show it | T |
+| Erase Fog | Click a fog shape to remove it | E |
+| Fog Cutting | Enables/Disables fog cutting | C, Alt (Toggle) |
+| Edge Snapping | Enables/Disables edge snapping | S |
+| Fog Preview | Enables/Disables a preview of the final fog shapes | F |
+| Undo | Undo a fog action | Ctrl + Z |
+| Redo | Redo a fog action | Ctrl + Shift + Z |
diff --git a/src/docs/howTo/usingMeasure.md b/src/docs/howTo/usingMeasure.md
index 8334909..0240663 100644
--- a/src/docs/howTo/usingMeasure.md
+++ b/src/docs/howTo/usingMeasure.md
@@ -4,9 +4,10 @@ The Measure Tool allows you to find how far one point on a map is from another p
A summary of the Measure Tool options are listed below.
-| Option | Description | Shortcut |
-| ------------------- | ---------------------------------------------------------------------------------- | -------- |
-| Grid Distance | This is the distance on a grid and is the metric used in D&D | G |
-| Line Distance | This is the actual distance between the two points of the measure tool | L |
-| City Block Distance | This is the distance when only travelling in the horizontal or vertical directions | C |
-| Scale | This allows you to enter a custom scale and unit value to apply | |
+| Option | Description | Shortcut |
+| ----------------------------- | ---------------------------------------------------------------------------------------- | -------- |
+| Grid Distance | This is the distance on a grid and is the metric used in D&D | G |
+| Alternating Diagonal Distance | This is the distance on a grid with diagonals alternating between 1 square and 2 squares | A |
+| Line Distance | This is the actual distance between the two points of the measure tool | L |
+| City Block Distance | This is the distance when only travelling in the horizontal or vertical directions | C |
+| Scale | This allows you to enter a custom scale and unit value to apply | |
diff --git a/src/docs/howTo/usingNotes.md b/src/docs/howTo/usingNotes.md
new file mode 100644
index 0000000..69fa1cd
--- /dev/null
+++ b/src/docs/howTo/usingNotes.md
@@ -0,0 +1,3 @@
+The notes tool allows you to write and share notes for other players to see.
+
+
diff --git a/src/docs/releaseNotes/v1.7.0.md b/src/docs/releaseNotes/v1.7.0.md
new file mode 100644
index 0000000..bd9ef33
--- /dev/null
+++ b/src/docs/releaseNotes/v1.7.0.md
@@ -0,0 +1,61 @@
+[embed:](https://www.youtube.com/embed/MWbfbN3Brhw)
+
+## Major Changes
+
+### Sticky Notes
+
+Easily add text to a map with the new Notes tool.
+
+- Select the Notes tool and click and drag to place a note on a map.
+- Add text, change colours or if you're the GM lock/hide them.
+
+### Network Rewrite
+
+This update brings a complete rewrite of the network layer used to connect players together.
+We now use a hybrid server/peer model compared to the fully peer-peer connection model used previously.
+This has a few benefits:
+
+- Connections should be more reliable as the server reduces the load on the players internet connection.
+- Better support for larger player groups.
+- Lessens the chance that the game state gets out of sync between players.
+
+### Fog Workflow Changes
+
+In this update the fog tool has been changed to hopefully help work better with fog cutting workflows.
+
+- Add and subtract fog options have been replaced with a single fog cut option.
+- Fog cutting works similarly to the previous fog subtraction however the cut shape is no longer automatically deleted. This allows you to draw a large fog shape then cut out sections but still allows you to toggle them back on if needed.
+- New rectangle tool for drawing fog shapes.
+- Fog has been heavily optimised to limit performance issues.
+- Fog now has a new look with a more paper-like black colour and a drop shadow to visually separate it from the map.
+
+## Minor Changes
+
+- New edit flag for maps to disable note editing.
+- Sliders now have a label above them when dragged which show the current value of the slider.
+- Token sizes no longer need to be integers and can now be decimal numbers.
+- Token resizing on map is now handled in 0.5 increments and allows going down to 0.5x size.
+- Added decimal support to ruler scale.
+- Added precision support to ruler scale. For example a scale of 5ft will limit the measurements to integers whereas a scale of 5.0ft will limit to decimal measurements with one decimal place.
+- New alternating diagonals measurement option for the ruler. This works by alternating diagonal distances between 1x and 2x allowing D&D 3.5 edition style measurements. (Thanks to /u/pspeter3 on Reddit for the suggestion and example code)
+- Changed loading indicator for maps and tokens to be more visible.
+- Changed local storage to use the persistent storage API. This means that on FireFox the hidden 2GB storage limit will no longer be an issue.
+- Added an indicator to how much storage is being used in the settings screen for browsers that support it.
+- Added multi-threading to initial map and token loading which should help remove lag with large amounts of data.
+- Changed line height of body text to be more readable.
+- Added a getting started modal to the home screen that should help with basic usage of the site.
+- Added saving to use password option for starting a game.
+- Added support for dragging and dropping images into the map or token screens that originate from a website. This is useful for dragging tokens from the Avrae Discord bot into the token select screen.
+- Added support for a larger layout for map/token selection and editing for bigger displays.
+- Changed map automatic quality options to better represent map details.
+- Updated automatic grid detection model to be more accurate and better handle lower resolution images.
+- Updated FAQ to actually have frequently asked questions.
+- Fixed crash when sometimes interacting with the page while it is loading.
+- Fixed crash when sometimes zooming out too far with a custom token on the map.
+- Fixed a bug where the drawing erase tool could still be used outside of the drawing tool.
+- Fixed a bug causing the colour of the glass and gemstone dice to be wrong.
+- Fixed a bug that would cause custom tokens to not load until a refresh.
+
+---
+
+Jan 20 2021
diff --git a/src/helpers/Settings.js b/src/helpers/Settings.js
index ffd029b..348fc3f 100644
--- a/src/helpers/Settings.js
+++ b/src/helpers/Settings.js
@@ -1,7 +1,7 @@
import FakeStorage from "./FakeStorage";
/**
- * An interface to a local storage back settings store with a versioning mechanism
+ * An interface to a local storage backed settings store with a versioning mechanism
*/
class Settings {
name;
diff --git a/src/helpers/colors.js b/src/helpers/colors.js
index cc10302..ccb5e32 100644
--- a/src/helpers/colors.js
+++ b/src/helpers/colors.js
@@ -8,7 +8,7 @@ const colors = {
green: "rgb(133, 255, 102)",
pink: "rgb(235, 138, 255)",
teal: "rgb(68, 224, 241)",
- black: "rgb(0, 0, 0)",
+ black: "rgb(34, 34, 34)",
darkGray: "rgb(90, 90, 90)",
lightGray: "rgb(179, 179, 179)",
white: "rgb(255, 255, 255)",
diff --git a/src/helpers/diff.js b/src/helpers/diff.js
new file mode 100644
index 0000000..f2853fd
--- /dev/null
+++ b/src/helpers/diff.js
@@ -0,0 +1,9 @@
+import { applyChange, diff as deepDiff } from "deep-diff";
+
+export function applyChanges(target, changes) {
+ for (let change of changes) {
+ applyChange(target, true, change);
+ }
+}
+
+export const diff = deepDiff;
diff --git a/src/helpers/drawing.js b/src/helpers/drawing.js
index 50c7e57..69853b2 100644
--- a/src/helpers/drawing.js
+++ b/src/helpers/drawing.js
@@ -8,22 +8,13 @@ const snappingThreshold = 1 / 5;
export function getBrushPositionForTool(
map,
brushPosition,
- tool,
- toolSettings,
+ useGridSnappning,
+ useEdgeSnapping,
gridSize,
shapes
) {
let position = brushPosition;
- const useGridSnappning =
- map.snapToGrid &&
- ((tool === "drawing" &&
- (toolSettings.type === "line" ||
- toolSettings.type === "rectangle" ||
- toolSettings.type === "circle" ||
- toolSettings.type === "triangle")) ||
- (tool === "fog" && toolSettings.type === "polygon"));
-
if (useGridSnappning) {
// Snap to corners of grid
// Subtract offset to transform into offset space then add it back transform back
@@ -58,8 +49,6 @@ export function getBrushPositionForTool(
}
}
- const useEdgeSnapping = tool === "fog" && toolSettings.useEdgeSnapping;
-
if (useEdgeSnapping) {
const minGrid = Vector2.min(gridSize);
let closestDistance = Number.MAX_VALUE;
@@ -239,28 +228,110 @@ export function drawActionsToShapes(actions, actionIndex) {
);
let shapeGeom = [[shapePoints, ...shapeHoles]];
const difference = polygonClipping.difference(shapeGeom, actionGeom);
- for (let i = 0; i < difference.length; i++) {
- let newId = difference.length > 1 ? `${shape.id}-${i}` : shape.id;
- // Holes detected
- let holes = [];
- if (difference[i].length > 1) {
- for (let j = 1; j < difference[i].length; j++) {
- holes.push(difference[i][j].map(([x, y]) => ({ x, y })));
- }
- }
-
- subtractedShapes[newId] = {
- ...shape,
- id: newId,
- data: {
- points: difference[i][0].map(([x, y]) => ({ x, y })),
- holes,
- },
- };
- }
+ addPolygonDifferenceToShapes(shape, difference, subtractedShapes);
}
shapesById = subtractedShapes;
}
+ if (action.type === "cut") {
+ const actionGeom = action.shapes.map((actionShape) => [
+ actionShape.data.points.map(({ x, y }) => [x, y]),
+ ]);
+ let cutShapes = {};
+ for (let shape of Object.values(shapesById)) {
+ const shapePoints = shape.data.points.map(({ x, y }) => [x, y]);
+ const shapeHoles = shape.data.holes.map((hole) =>
+ hole.map(({ x, y }) => [x, y])
+ );
+ let shapeGeom = [[shapePoints, ...shapeHoles]];
+ const difference = polygonClipping.difference(shapeGeom, actionGeom);
+ const intersection = polygonClipping.intersection(
+ shapeGeom,
+ actionGeom
+ );
+ addPolygonDifferenceToShapes(shape, difference, cutShapes);
+ addPolygonIntersectionToShapes(shape, intersection, cutShapes);
+ }
+ shapesById = cutShapes;
+ }
}
return Object.values(shapesById);
}
+
+function addPolygonDifferenceToShapes(shape, difference, shapes) {
+ for (let i = 0; i < difference.length; i++) {
+ let newId = `${shape.id}-dif-${i}`;
+ // Holes detected
+ let holes = [];
+ if (difference[i].length > 1) {
+ for (let j = 1; j < difference[i].length; j++) {
+ holes.push(difference[i][j].map(([x, y]) => ({ x, y })));
+ }
+ }
+
+ shapes[newId] = {
+ ...shape,
+ id: newId,
+ data: {
+ points: difference[i][0].map(([x, y]) => ({ x, y })),
+ holes,
+ },
+ };
+ }
+}
+
+function addPolygonIntersectionToShapes(shape, intersection, shapes) {
+ for (let i = 0; i < intersection.length; i++) {
+ let newId = `${shape.id}-int-${i}`;
+ shapes[newId] = {
+ ...shape,
+ id: newId,
+ data: {
+ points: intersection[i][0].map(([x, y]) => ({ x, y })),
+ holes: [],
+ },
+ // Default intersection visibility to false
+ visible: false,
+ };
+ }
+}
+
+export function mergeShapes(shapes) {
+ if (shapes.length === 0) {
+ return shapes;
+ }
+ let geometries = [];
+ for (let shape of shapes) {
+ if (!shape.visible) {
+ continue;
+ }
+ const shapePoints = shape.data.points.map(({ x, y }) => [x, y]);
+ const shapeHoles = shape.data.holes.map((hole) =>
+ hole.map(({ x, y }) => [x, y])
+ );
+ let shapeGeom = [[shapePoints, ...shapeHoles]];
+ geometries.push(shapeGeom);
+ }
+ if (geometries.length === 0) {
+ return geometries;
+ }
+ let union = polygonClipping.union(...geometries);
+ let merged = [];
+ for (let i = 0; i < union.length; i++) {
+ let holes = [];
+ if (union[i].length > 1) {
+ for (let j = 1; j < union[i].length; j++) {
+ holes.push(union[i][j].map(([x, y]) => ({ x, y })));
+ }
+ }
+ merged.push({
+ // Use the data of the first visible shape as the merge
+ ...shapes.find((shape) => shape.visible),
+ id: `merged-${i}`,
+ data: {
+ points: union[i][0].map(([x, y]) => ({ x, y })),
+ holes,
+ },
+ });
+ }
+ return merged;
+}
diff --git a/src/helpers/konva.js b/src/helpers/konva.js
index bfa602d..227a2cc 100644
--- a/src/helpers/konva.js
+++ b/src/helpers/konva.js
@@ -243,3 +243,17 @@ export function getRelativePointerPositionNormalized(node) {
y: relativePosition.y / node.height(),
};
}
+
+/**
+ * Converts points from alternating array form to vector array form
+ * @param {number[]} points points in an x, y alternating array
+ * @returns {Vector2[]} a `Vector2` array
+ */
+export function convertPointArray(points) {
+ return points.reduce((acc, _, i, arr) => {
+ if (i % 2 === 0) {
+ acc.push({ x: arr[i], y: arr[i + 1] });
+ }
+ return acc;
+ }, []);
+}
diff --git a/src/helpers/logging.js b/src/helpers/logging.js
index a80ff82..e8be741 100644
--- a/src/helpers/logging.js
+++ b/src/helpers/logging.js
@@ -2,7 +2,7 @@ import { captureException } from "@sentry/react";
export function logError(error) {
console.error(error);
- if (process.env.NODE_ENV === "production") {
+ if (process.env.REACT_APP_LOGGING === "true") {
captureException(error);
}
}
diff --git a/src/helpers/map.js b/src/helpers/map.js
index 3ded592..ae8e44f 100644
--- a/src/helpers/map.js
+++ b/src/helpers/map.js
@@ -1,4 +1,5 @@
import GridSizeModel from "../ml/gridSize/GridSizeModel";
+import * as Vector2 from "./vector2";
import { logError } from "./logging";
@@ -138,7 +139,6 @@ export async function getGridSize(image) {
let prediction;
// Try and use ML grid detection
- // TODO: Fix possible error on Android
try {
prediction = await gridSizeML(image, candidates);
} catch (error) {
@@ -160,5 +160,66 @@ export function getMapMaxZoom(map) {
return 10;
}
// Return max grid size / 2
- return Math.max(Math.min(map.grid.size.x, map.grid.size.y) / 2, 5);
+ return Math.max(Math.max(map.grid.size.x, map.grid.size.y) / 2, 5);
+}
+
+export function snapNodeToMap(
+ map,
+ mapWidth,
+ mapHeight,
+ node,
+ snappingThreshold
+) {
+ const offset = Vector2.multiply(map.grid.inset.topLeft, {
+ x: mapWidth,
+ y: mapHeight,
+ });
+ const gridSize = {
+ x:
+ (mapWidth * (map.grid.inset.bottomRight.x - map.grid.inset.topLeft.x)) /
+ map.grid.size.x,
+ y:
+ (mapHeight * (map.grid.inset.bottomRight.y - map.grid.inset.topLeft.y)) /
+ map.grid.size.y,
+ };
+
+ const position = node.position();
+ const halfSize = Vector2.divide({ x: node.width(), y: node.height() }, 2);
+
+ // Offsets to tranform the centered position into the four corners
+ const cornerOffsets = [
+ halfSize,
+ { x: -halfSize.x, y: -halfSize.y },
+ { x: halfSize.x, y: -halfSize.y },
+ { x: -halfSize.x, y: halfSize.y },
+ ];
+
+ // Minimum distance from a corner to the grid
+ let minCornerGridDistance = Number.MAX_VALUE;
+ // Minimum component of the difference between the min corner and the grid
+ let minCornerMinComponent;
+ // Closest grid value
+ let minGridSnap;
+
+ // Find the closest corner to the grid
+ for (let cornerOffset of cornerOffsets) {
+ const corner = Vector2.add(position, cornerOffset);
+ // Transform into offset space, round, then transform back
+ const gridSnap = Vector2.add(
+ Vector2.roundTo(Vector2.subtract(corner, offset), gridSize),
+ offset
+ );
+ const gridDistance = Vector2.length(Vector2.subtract(gridSnap, corner));
+ const minComponent = Vector2.min(gridSize);
+ if (gridDistance < minCornerGridDistance) {
+ minCornerGridDistance = gridDistance;
+ minCornerMinComponent = minComponent;
+ // Move the grid value back to the center
+ minGridSnap = Vector2.subtract(gridSnap, cornerOffset);
+ }
+ }
+
+ if (minCornerGridDistance < minCornerMinComponent * snappingThreshold) {
+ node.position(minGridSnap);
+ }
}
diff --git a/src/helpers/useDataSource.js b/src/helpers/useDataSource.js
index 6dc39b8..6feb5a0 100644
--- a/src/helpers/useDataSource.js
+++ b/src/helpers/useDataSource.js
@@ -11,7 +11,28 @@ function useDataSource(data, defaultSources, unknownSource) {
}
let url = unknownSource;
if (data.type === "file") {
- url = URL.createObjectURL(new Blob([data.file]));
+ if (data.resolutions) {
+ // Check is a resolution is specified
+ if (data.quality && data.resolutions[data.quality]) {
+ url = URL.createObjectURL(
+ new Blob([data.resolutions[data.quality].file])
+ );
+ }
+ // If no file available fallback to the highest resolution
+ else if (!data.file) {
+ const resolutionArray = Object.keys(data.resolutions);
+ url = URL.createObjectURL(
+ new Blob([
+ data.resolutions[resolutionArray[resolutionArray.length - 1]]
+ .file,
+ ])
+ );
+ } else {
+ url = URL.createObjectURL(new Blob([data.file]));
+ }
+ } else {
+ url = URL.createObjectURL(new Blob([data.file]));
+ }
} else if (data.type === "default") {
url = defaultSources[data.key];
}
@@ -19,7 +40,10 @@ function useDataSource(data, defaultSources, unknownSource) {
return () => {
if (data.type === "file" && url) {
- URL.revokeObjectURL(url);
+ // Remove file url after 5 seconds as we still may be using it while the next image loads
+ setTimeout(() => {
+ URL.revokeObjectURL(url);
+ }, 5000);
}
};
}, [data, defaultSources, unknownSource]);
diff --git a/src/helpers/useMapImage.js b/src/helpers/useMapImage.js
index b6f408e..db28781 100644
--- a/src/helpers/useMapImage.js
+++ b/src/helpers/useMapImage.js
@@ -3,49 +3,10 @@ import useImage from "use-image";
import useDataSource from "./useDataSource";
-import { isEmpty } from "./shared";
-
import { mapSources as defaultMapSources } from "../maps";
function useMapImage(map) {
- const [mapSourceMap, setMapSourceMap] = useState({});
- // Update source map data when either the map or map quality changes
- useEffect(() => {
- function updateMapSource() {
- if (map && map.type === "file" && map.resolutions) {
- // If quality is set and the quality is available
- if (map.quality !== "original" && map.resolutions[map.quality]) {
- setMapSourceMap({
- ...map.resolutions[map.quality],
- id: map.id,
- quality: map.quality,
- });
- } else if (!map.file) {
- // If no file fallback to the highest resolution
- const resolutionArray = Object.keys(map.resolutions);
- setMapSourceMap({
- ...map.resolutions[resolutionArray[resolutionArray.length - 1]],
- id: map.id,
- });
- } else {
- setMapSourceMap(map);
- }
- } else {
- setMapSourceMap(map);
- }
- }
- if (map && map.id !== mapSourceMap.id) {
- updateMapSource();
- } else if (map && map.type === "file") {
- if (map.file && map.quality !== mapSourceMap.quality) {
- updateMapSource();
- }
- } else if (!map && !isEmpty(mapSourceMap)) {
- setMapSourceMap({});
- }
- }, [map, mapSourceMap]);
-
- const mapSource = useDataSource(mapSourceMap, defaultMapSources);
+ const mapSource = useDataSource(map, defaultMapSources);
const [mapSourceImage, mapSourceImageStatus] = useImage(mapSource);
// Create a map source that only updates when the image is fully loaded
diff --git a/src/helpers/useNetworkedState.js b/src/helpers/useNetworkedState.js
new file mode 100644
index 0000000..df3e7f7
--- /dev/null
+++ b/src/helpers/useNetworkedState.js
@@ -0,0 +1,113 @@
+import { useEffect, useState, useRef, useCallback } from "react";
+
+import useDebounce from "./useDebounce";
+import { diff, applyChanges } from "./diff";
+
+/**
+ * @callback setNetworkedState
+ * @param {any} update The updated state or a state function passed into setState
+ * @param {boolean} sync Whether to sync the update with the session
+ * @param {boolean} force Whether to force a full update, usefull when partialUpdates is enabled
+ */
+
+/**
+ * Helper to sync a react state to a `Session`
+ *
+ * @param {any} initialState
+ * @param {Session} session `Session` instance
+ * @param {string} eventName Name of the event to send to the session
+ * @param {number} debounceRate Amount to debounce before sending to the session (ms)
+ * @param {boolean} partialUpdates Allow sending of partial updates to the session
+ * @param {string} partialUpdatesKey Key to lookup in the state to identify a partial update
+ *
+ * @returns {[any, setNetworkedState]}
+ */
+function useNetworkedState(
+ initialState,
+ session,
+ eventName,
+ debounceRate = 100,
+ partialUpdates = true,
+ partialUpdatesKey = "id"
+) {
+ const [state, _setState] = useState(initialState);
+ // Used to control whether the state needs to be sent to the socket
+ const dirtyRef = useRef(false);
+
+ // Used to force a full update
+ const forceUpdateRef = useRef(false);
+
+ // Update dirty at the same time as state
+ const setState = useCallback((update, sync = true, force = false) => {
+ dirtyRef.current = sync;
+ forceUpdateRef.current = force;
+ _setState(update);
+ }, []);
+
+ const eventNameRef = useRef(eventName);
+ useEffect(() => {
+ eventNameRef.current = eventName;
+ }, [eventName]);
+
+ const debouncedState = useDebounce(state, debounceRate);
+ const lastSyncedStateRef = useRef();
+ useEffect(() => {
+ if (session.socket && dirtyRef.current) {
+ // If partial updates enabled, send just the changes to the socket
+ if (
+ lastSyncedStateRef.current &&
+ debouncedState &&
+ partialUpdates &&
+ !forceUpdateRef.current
+ ) {
+ const changes = diff(lastSyncedStateRef.current, debouncedState);
+ if (changes) {
+ const update = { id: debouncedState[partialUpdatesKey], changes };
+ session.socket.emit(`${eventName}_update`, update);
+ }
+ } else {
+ session.socket.emit(eventName, debouncedState);
+ }
+ dirtyRef.current = false;
+ forceUpdateRef.current = false;
+ lastSyncedStateRef.current = debouncedState;
+ }
+ }, [
+ session.socket,
+ eventName,
+ debouncedState,
+ partialUpdates,
+ partialUpdatesKey,
+ ]);
+
+ useEffect(() => {
+ function handleSocketEvent(data) {
+ _setState(data);
+ lastSyncedStateRef.current = data;
+ }
+
+ function handleSocketUpdateEvent(update) {
+ _setState((prevState) => {
+ if (prevState[partialUpdatesKey] === update.id) {
+ let newState = { ...prevState };
+ applyChanges(newState, update.changes);
+ lastSyncedStateRef.current = newState;
+ return newState;
+ } else {
+ return prevState;
+ }
+ });
+ }
+
+ session.socket?.on(eventName, handleSocketEvent);
+ session.socket?.on(`${eventName}_update`, handleSocketUpdateEvent);
+ return () => {
+ session.socket?.off(eventName, handleSocketEvent);
+ session.socket?.off(`${eventName}_update`, handleSocketUpdateEvent);
+ };
+ }, [session.socket, eventName, partialUpdatesKey]);
+
+ return [state, setState];
+}
+
+export default useNetworkedState;
diff --git a/src/helpers/useResponsiveLayout.js b/src/helpers/useResponsiveLayout.js
new file mode 100644
index 0000000..cb9e583
--- /dev/null
+++ b/src/helpers/useResponsiveLayout.js
@@ -0,0 +1,27 @@
+import { useMedia } from "react-media";
+
+function useResponsiveLayout() {
+ const isMediumScreen = useMedia({ query: "(min-width: 500px)" });
+ const isLargeScreen = useMedia({ query: "(min-width: 1500px)" });
+ const screenSize = isLargeScreen
+ ? "large"
+ : isMediumScreen
+ ? "medium"
+ : "small";
+
+ const modalSize = isLargeScreen
+ ? "842px"
+ : isMediumScreen
+ ? "642px"
+ : "500px";
+
+ const tileSize = isLargeScreen
+ ? "small"
+ : isMediumScreen
+ ? "medium"
+ : "large";
+
+ return { screenSize, modalSize, tileSize };
+}
+
+export default useResponsiveLayout;
diff --git a/src/helpers/useStageInteraction.js b/src/helpers/useStageInteraction.js
index 52fedda..26b45a6 100644
--- a/src/helpers/useStageInteraction.js
+++ b/src/helpers/useStageInteraction.js
@@ -27,7 +27,7 @@ function useStageInteraction(
onWheelStart: (props) => {
const { event } = props;
isInteractingWithCanvas.current =
- event.target === layer.getCanvas()._canvas;
+ layer && event.target === layer.getCanvas()._canvas;
gesture.onWheelStart && gesture.onWheelStart(props);
},
onWheel: (props) => {
@@ -62,7 +62,7 @@ function useStageInteraction(
onPinchStart: (props) => {
const { event } = props;
isInteractingWithCanvas.current =
- event.target === layer.getCanvas()._canvas;
+ layer && event.target === layer.getCanvas()._canvas;
const { da, origin } = props;
const [distance] = da;
const [originX, originY] = origin;
@@ -124,7 +124,7 @@ function useStageInteraction(
onDragStart: (props) => {
const { event } = props;
isInteractingWithCanvas.current =
- event.target === layer.getCanvas()._canvas;
+ layer && event.target === layer.getCanvas()._canvas;
gesture.onDragStart && gesture.onDragStart(props);
},
onDrag: (props) => {
diff --git a/src/helpers/vector2.js b/src/helpers/vector2.js
index 8eecb65..c2a6561 100644
--- a/src/helpers/vector2.js
+++ b/src/helpers/vector2.js
@@ -363,7 +363,7 @@ export function compare(a, b, threshold) {
* Returns the distance between two vectors
* @param {Vector2} a
* @param {Vector2} b
- * @param {string} type - `chebyshev | euclidean | manhattan`
+ * @param {string} type - `chebyshev | euclidean | manhattan | alternating`
*/
export function distance(a, b, type) {
switch (type) {
@@ -373,6 +373,12 @@ export function distance(a, b, type) {
return length(subtract(a, b));
case "manhattan":
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
+ case "alternating":
+ // Alternating diagonal distance like D&D 3.5 and Pathfinder
+ const delta = abs(subtract(a, b));
+ const ma = max(delta);
+ const mi = min(delta);
+ return ma - mi + Math.floor(1.5 * mi);
default:
return length(subtract(a, b));
}
diff --git a/src/icons/FogAddIcon.js b/src/icons/FogAddIcon.js
deleted file mode 100644
index aa21d79..0000000
--- a/src/icons/FogAddIcon.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from "react";
-
-function FogAddIcon() {
- return (
-
- );
-}
-
-export default FogAddIcon;
diff --git a/src/icons/FogCutOffIcon.js b/src/icons/FogCutOffIcon.js
new file mode 100644
index 0000000..f04bbe0
--- /dev/null
+++ b/src/icons/FogCutOffIcon.js
@@ -0,0 +1,18 @@
+import React from "react";
+
+function FogCutOffIcon() {
+ return (
+
+ );
+}
+
+export default FogCutOffIcon;
diff --git a/src/icons/FogCutOnIcon.js b/src/icons/FogCutOnIcon.js
new file mode 100644
index 0000000..fef6529
--- /dev/null
+++ b/src/icons/FogCutOnIcon.js
@@ -0,0 +1,18 @@
+import React from "react";
+
+function FogCutOnIcon() {
+ return (
+
+ );
+}
+
+export default FogCutOnIcon;
diff --git a/src/icons/FogRectangleIcon.js b/src/icons/FogRectangleIcon.js
new file mode 100644
index 0000000..7a41096
--- /dev/null
+++ b/src/icons/FogRectangleIcon.js
@@ -0,0 +1,18 @@
+import React from "react";
+
+function FogRectangleIcon() {
+ return (
+
+ );
+}
+
+export default FogRectangleIcon;
diff --git a/src/icons/FogSubtractIcon.js b/src/icons/FogSubtractIcon.js
deleted file mode 100644
index af6c2e6..0000000
--- a/src/icons/FogSubtractIcon.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from "react";
-
-function FogRemoveIcon() {
- return (
-
- );
-}
-
-export default FogRemoveIcon;
diff --git a/src/icons/HelpIcon.js b/src/icons/HelpIcon.js
new file mode 100644
index 0000000..f45c9a8
--- /dev/null
+++ b/src/icons/HelpIcon.js
@@ -0,0 +1,19 @@
+import React from "react";
+
+function HelpIcon() {
+ return (
+
+ );
+}
+
+export default HelpIcon;
diff --git a/src/icons/MeasureAlternatingIcon.js b/src/icons/MeasureAlternatingIcon.js
new file mode 100644
index 0000000..a65e504
--- /dev/null
+++ b/src/icons/MeasureAlternatingIcon.js
@@ -0,0 +1,18 @@
+import React from "react";
+
+function MeasureAlternatingIcon() {
+ return (
+
+ );
+}
+
+export default MeasureAlternatingIcon;
diff --git a/src/icons/MoveIcon.js b/src/icons/MoveIcon.js
new file mode 100644
index 0000000..233a691
--- /dev/null
+++ b/src/icons/MoveIcon.js
@@ -0,0 +1,19 @@
+import React from "react";
+
+function MoveIcon() {
+ return (
+
+ );
+}
+
+export default MoveIcon;
diff --git a/src/icons/NoteToolIcon.js b/src/icons/NoteToolIcon.js
new file mode 100644
index 0000000..e9bb0e5
--- /dev/null
+++ b/src/icons/NoteToolIcon.js
@@ -0,0 +1,19 @@
+import React from "react";
+
+function NoteToolIcon() {
+ return (
+
+ );
+}
+
+export default NoteToolIcon;
diff --git a/src/icons/SnappingOffIcon.js b/src/icons/SnappingOffIcon.js
index a51c22b..ef0bcf5 100644
--- a/src/icons/SnappingOffIcon.js
+++ b/src/icons/SnappingOffIcon.js
@@ -10,7 +10,7 @@ function SnappingOffIcon() {
fill="currentcolor"
>
-
+
);
}
diff --git a/src/icons/SnappingOnIcon.js b/src/icons/SnappingOnIcon.js
index 355831c..d78c12f 100644
--- a/src/icons/SnappingOnIcon.js
+++ b/src/icons/SnappingOnIcon.js
@@ -10,7 +10,7 @@ function SnappingOnIcon() {
fill="currentcolor"
>
-
+
);
}
diff --git a/src/images/TokenLabel.png b/src/images/TokenLabel.png
deleted file mode 100644
index 637ddab..0000000
Binary files a/src/images/TokenLabel.png and /dev/null differ
diff --git a/src/index.js b/src/index.js
index 8597757..a503ace 100644
--- a/src/index.js
+++ b/src/index.js
@@ -11,7 +11,7 @@ import * as serviceWorker from "./serviceWorker";
import "./index.css";
-if (process.env.NODE_ENV === "production") {
+if (process.env.REACT_APP_LOGGING === "true") {
Sentry.init({
dsn:
"https://bc1e2edfe7ca453f8e7357a48693979e@o467475.ingest.sentry.io/5493956",
@@ -19,10 +19,12 @@ if (process.env.NODE_ENV === "production") {
// Ignore resize error as it is triggered by going fullscreen on slower computers
// Ignore quota error
// Ignore XDR encoding failure bug in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1678243
+ // Ignore LastPass extension text error
ignoreErrors: [
"ResizeObserver loop limit exceeded",
"QuotaExceededError",
"XDR encoding failure",
+ "Assertion failed: Input argument is not an HTMLInputElement",
],
});
}
diff --git a/src/ml/gridSize/group1-shard1of1.bin b/src/ml/gridSize/group1-shard1of1.bin
index 8033d4b..6413f01 100755
Binary files a/src/ml/gridSize/group1-shard1of1.bin and b/src/ml/gridSize/group1-shard1of1.bin differ
diff --git a/src/ml/gridSize/model.json b/src/ml/gridSize/model.json
index 58a86e1..0037c9d 100755
--- a/src/ml/gridSize/model.json
+++ b/src/ml/gridSize/model.json
@@ -1 +1 @@
-{"format": "layers-model", "generatedBy": "keras v2.4.0", "convertedBy": "TensorFlow.js Converter v2.4.0", "modelTopology": {"keras_version": "2.4.0", "backend": "tensorflow", "model_config": {"class_name": "Functional", "config": {"name": "model", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 32, 2048, 1], "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}, "name": "input_1", "inbound_nodes": []}, {"class_name": "Conv2D", "config": {"name": "conv2d", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [3, 3], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d", "inbound_nodes": [[["input_1", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization", "inbound_nodes": [[["conv2d", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation", "inbound_nodes": [[["batch_normalization", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d", "inbound_nodes": [[["activation", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_1", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_1", "inbound_nodes": [[["depthwise_conv2d", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_1", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_1", "inbound_nodes": [[["batch_normalization_1", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_1", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_1", "inbound_nodes": [[["activation_1", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_2", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_2", "inbound_nodes": [[["conv2d_1", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_2", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_2", "inbound_nodes": [[["batch_normalization_2", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_2", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_2", "inbound_nodes": [[["activation_2", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_3", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_3", "inbound_nodes": [[["conv2d_2", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_3", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_3", "inbound_nodes": [[["batch_normalization_3", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_1", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_1", "inbound_nodes": [[["activation_3", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_4", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_4", "inbound_nodes": [[["depthwise_conv2d_1", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_4", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_4", "inbound_nodes": [[["batch_normalization_4", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_3", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [1, 1], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_3", "inbound_nodes": [[["activation_4", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_5", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_5", "inbound_nodes": [[["conv2d_3", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_5", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_5", "inbound_nodes": [[["batch_normalization_5", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_4", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_4", "inbound_nodes": [[["activation_5", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_6", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_6", "inbound_nodes": [[["conv2d_4", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_6", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_6", "inbound_nodes": [[["batch_normalization_6", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_2", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_2", "inbound_nodes": [[["activation_6", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_7", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_7", "inbound_nodes": [[["depthwise_conv2d_2", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_7", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_7", "inbound_nodes": [[["batch_normalization_7", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_5", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_5", "inbound_nodes": [[["activation_7", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_8", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_8", "inbound_nodes": [[["conv2d_5", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_8", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_8", "inbound_nodes": [[["batch_normalization_8", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_6", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_6", "inbound_nodes": [[["activation_8", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_9", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_9", "inbound_nodes": [[["conv2d_6", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_9", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_9", "inbound_nodes": [[["batch_normalization_9", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_3", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_3", "inbound_nodes": [[["activation_9", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_10", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_10", "inbound_nodes": [[["depthwise_conv2d_3", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_10", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_10", "inbound_nodes": [[["batch_normalization_10", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_7", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": [1, 1], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_7", "inbound_nodes": [[["activation_10", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_11", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_11", "inbound_nodes": [[["conv2d_7", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_11", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_11", "inbound_nodes": [[["batch_normalization_11", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_8", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_8", "inbound_nodes": [[["activation_11", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_12", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_12", "inbound_nodes": [[["conv2d_8", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_12", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_12", "inbound_nodes": [[["batch_normalization_12", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_4", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_4", "inbound_nodes": [[["activation_12", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_13", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_13", "inbound_nodes": [[["depthwise_conv2d_4", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_13", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_13", "inbound_nodes": [[["batch_normalization_13", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_9", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_9", "inbound_nodes": [[["activation_13", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_14", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_14", "inbound_nodes": [[["conv2d_9", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_14", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_14", "inbound_nodes": [[["batch_normalization_14", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_10", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_10", "inbound_nodes": [[["activation_14", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_15", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_15", "inbound_nodes": [[["conv2d_10", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_15", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_15", "inbound_nodes": [[["batch_normalization_15", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_5", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_5", "inbound_nodes": [[["activation_15", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_16", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_16", "inbound_nodes": [[["depthwise_conv2d_5", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_16", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_16", "inbound_nodes": [[["batch_normalization_16", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_11", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": [1, 1], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_11", "inbound_nodes": [[["activation_16", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_17", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_17", "inbound_nodes": [[["conv2d_11", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_17", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_17", "inbound_nodes": [[["batch_normalization_17", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_12", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_12", "inbound_nodes": [[["activation_17", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_18", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_18", "inbound_nodes": [[["conv2d_12", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_18", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_18", "inbound_nodes": [[["batch_normalization_18", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_6", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_6", "inbound_nodes": [[["activation_18", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_19", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_19", "inbound_nodes": [[["depthwise_conv2d_6", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_19", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_19", "inbound_nodes": [[["batch_normalization_19", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_13", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": [1, 1], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_13", "inbound_nodes": [[["activation_19", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_20", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_20", "inbound_nodes": [[["conv2d_13", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_20", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_20", "inbound_nodes": [[["batch_normalization_20", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_14", "trainable": true, "dtype": "float32", "filters": 1024, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_14", "inbound_nodes": [[["activation_20", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_21", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_21", "inbound_nodes": [[["conv2d_14", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_21", "trainable": true, "dtype": "float32", "activation": "relu"}, "name": "activation_21", "inbound_nodes": [[["batch_normalization_21", 0, 0, {}]]]}, {"class_name": "AveragePooling2D", "config": {"name": "average_pooling2d", "trainable": true, "dtype": "float32", "pool_size": [1, 1], "padding": "valid", "strides": [1, 1], "data_format": "channels_last"}, "name": "average_pooling2d", "inbound_nodes": [[["activation_21", 0, 0, {}]]]}, {"class_name": "Flatten", "config": {"name": "flatten", "trainable": true, "dtype": "float32", "data_format": "channels_last"}, "name": "flatten", "inbound_nodes": [[["average_pooling2d", 0, 0, {}]]]}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense", "inbound_nodes": [[["flatten", 0, 0, {}]]]}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense", 0, 0]]}}, "training_config": {"loss": "mean_absolute_error", "metrics": ["mean_absolute_error", "mean_squared_error"], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 9.999999747378752e-05, "decay": 0.0, "beta_1": 0.8999999761581421, "beta_2": 0.9990000128746033, "epsilon": 1e-07, "amsgrad": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "batch_normalization/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8917937874794006, "scale": 0.0006625612576802571, "original_dtype": "float32"}}, {"name": "batch_normalization/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.04727966615966722, "scale": 0.0011531625892601761, "original_dtype": "float32"}}, {"name": "batch_normalization/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.16775821470746807, "scale": 0.0011982729621962005, "original_dtype": "float32"}}, {"name": "batch_normalization/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.00015918159624561667, "scale": 5.9315270296864064e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_1/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8960686326026917, "scale": 0.0013461105963763068, "original_dtype": "float32"}}, {"name": "batch_normalization_1/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.09169458431356094, "scale": 0.0016980478576585358, "original_dtype": "float32"}}, {"name": "batch_normalization_1/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.21042059887857997, "scale": 0.0017107365762486176, "original_dtype": "float32"}}, {"name": "batch_normalization_1/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0011625189799815416, "scale": 0.00041422780657954076, "original_dtype": "float32"}}, {"name": "batch_normalization_10/gamma", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8022289872169495, "scale": 0.0012832040880240646, "original_dtype": "float32"}}, {"name": "batch_normalization_10/beta", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1544602616744883, "scale": 0.0014435538474251242, "original_dtype": "float32"}}, {"name": "batch_normalization_10/moving_mean", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.22737223751404706, "scale": 0.0017095656955943388, "original_dtype": "float32"}}, {"name": "batch_normalization_10/moving_variance", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0006427246262319386, "scale": 0.0002451787673064745, "original_dtype": "float32"}}, {"name": "batch_normalization_11/gamma", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9095439314842224, "scale": 0.0006849141681895537, "original_dtype": "float32"}}, {"name": "batch_normalization_11/beta", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1595606619236516, "scale": 0.001049741196866129, "original_dtype": "float32"}}, {"name": "batch_normalization_11/moving_mean", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.9653870098731097, "scale": 0.007848674877017152, "original_dtype": "float32"}}, {"name": "batch_normalization_11/moving_variance", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.44822046160697937, "scale": 0.004700253640904146, "original_dtype": "float32"}}, {"name": "batch_normalization_12/gamma", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8613139986991882, "scale": 0.000936437354368322, "original_dtype": "float32"}}, {"name": "batch_normalization_12/beta", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10329354569023731, "scale": 0.0011350939086839265, "original_dtype": "float32"}}, {"name": "batch_normalization_12/moving_mean", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.2511213176390705, "scale": 0.009267565315844967, "original_dtype": "float32"}}, {"name": "batch_normalization_12/moving_variance", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.25429174304008484, "scale": 0.008595813606299607, "original_dtype": "float32"}}, {"name": "batch_normalization_13/gamma", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8576006889343262, "scale": 0.0011071209814034256, "original_dtype": "float32"}}, {"name": "batch_normalization_13/beta", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15285988923381358, "scale": 0.0011849603816574696, "original_dtype": "float32"}}, {"name": "batch_normalization_13/moving_mean", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.20211861297196035, "scale": 0.0014437043783711453, "original_dtype": "float32"}}, {"name": "batch_normalization_13/moving_variance", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0008781188516877592, "scale": 0.00016002754027973496, "original_dtype": "float32"}}, {"name": "batch_normalization_14/gamma", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8995044231414795, "scale": 0.0007338168574314492, "original_dtype": "float32"}}, {"name": "batch_normalization_14/beta", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11354928735424491, "scale": 0.0007373330347678241, "original_dtype": "float32"}}, {"name": "batch_normalization_14/moving_mean", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.5917711650623994, "scale": 0.01090254222645479, "original_dtype": "float32"}}, {"name": "batch_normalization_14/moving_variance", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.46753692626953125, "scale": 0.007813832339118509, "original_dtype": "float32"}}, {"name": "batch_normalization_15/gamma", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8598309755325317, "scale": 0.0012080113093058268, "original_dtype": "float32"}}, {"name": "batch_normalization_15/beta", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.09622894391125324, "scale": 0.0009819279990944208, "original_dtype": "float32"}}, {"name": "batch_normalization_15/moving_mean", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -2.02098054745618, "scale": 0.01413273110109217, "original_dtype": "float32"}}, {"name": "batch_normalization_15/moving_variance", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.6067975163459778, "scale": 0.010192822241315654, "original_dtype": "float32"}}, {"name": "batch_normalization_16/gamma", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8498532772064209, "scale": 0.001011182280147777, "original_dtype": "float32"}}, {"name": "batch_normalization_16/beta", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10255144735177359, "scale": 0.000861776868502299, "original_dtype": "float32"}}, {"name": "batch_normalization_16/moving_mean", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1815812204398361, "scale": 0.0014186032846862195, "original_dtype": "float32"}}, {"name": "batch_normalization_16/moving_variance", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0008919105748645961, "scale": 0.0001652924684058948, "original_dtype": "float32"}}, {"name": "batch_normalization_17/gamma", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8930070996284485, "scale": 0.0008597147231008492, "original_dtype": "float32"}}, {"name": "batch_normalization_17/beta", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.13273122941746432, "scale": 0.000790066841770621, "original_dtype": "float32"}}, {"name": "batch_normalization_17/moving_mean", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.6443209157270544, "scale": 0.012180154931311514, "original_dtype": "float32"}}, {"name": "batch_normalization_17/moving_variance", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.5147286653518677, "scale": 0.0065208205989762845, "original_dtype": "float32"}}, {"name": "batch_normalization_18/gamma", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8828704953193665, "scale": 0.001233203972087187, "original_dtype": "float32"}}, {"name": "batch_normalization_18/beta", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1849022626876831, "scale": 0.0014911472797393798, "original_dtype": "float32"}}, {"name": "batch_normalization_18/moving_mean", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -2.0279203377518, "scale": 0.018106431587069642, "original_dtype": "float32"}}, {"name": "batch_normalization_18/moving_variance", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.22968244552612305, "scale": 0.0070794460820216756, "original_dtype": "float32"}}, {"name": "batch_normalization_19/gamma", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8985792994499207, "scale": 0.000948636438332352, "original_dtype": "float32"}}, {"name": "batch_normalization_19/beta", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.19049792500103221, "scale": 0.0011339162202442393, "original_dtype": "float32"}}, {"name": "batch_normalization_19/moving_mean", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15238853295644125, "scale": 0.001120503918797362, "original_dtype": "float32"}}, {"name": "batch_normalization_19/moving_variance", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0007517342455685139, "scale": 0.0001214311092945875, "original_dtype": "float32"}}, {"name": "batch_normalization_2/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9075239896774292, "scale": 0.0008511833116119983, "original_dtype": "float32"}}, {"name": "batch_normalization_2/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.07233847245281819, "scale": 0.0011667495556906158, "original_dtype": "float32"}}, {"name": "batch_normalization_2/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.6973860151627485, "scale": 0.00501716557671042, "original_dtype": "float32"}}, {"name": "batch_normalization_2/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.15674017369747162, "scale": 0.003898479950194265, "original_dtype": "float32"}}, {"name": "batch_normalization_20/gamma", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.862261950969696, "scale": 0.0009571549939174278, "original_dtype": "float32"}}, {"name": "batch_normalization_20/beta", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.32799300809993465, "scale": 0.001333304910975344, "original_dtype": "float32"}}, {"name": "batch_normalization_20/moving_mean", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.9134425471810732, "scale": 0.020574651044957776, "original_dtype": "float32"}}, {"name": "batch_normalization_20/moving_variance", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.4879002869129181, "scale": 0.010536953865313062, "original_dtype": "float32"}}, {"name": "batch_normalization_21/gamma", "shape": [1024], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.7194499373435974, "scale": 0.0010455692515653721, "original_dtype": "float32"}}, {"name": "batch_normalization_21/beta", "shape": [1024], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.5807488455491907, "scale": 0.0024197868564549614, "original_dtype": "float32"}}, {"name": "batch_normalization_21/moving_mean", "shape": [1024], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -3.2015606211680994, "scale": 0.017885813526078768, "original_dtype": "float32"}}, {"name": "batch_normalization_21/moving_variance", "shape": [1024], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.5199308395385742, "scale": 0.07742762472115311, "original_dtype": "float32"}}, {"name": "batch_normalization_3/gamma", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8049679398536682, "scale": 0.001245090306973925, "original_dtype": "float32"}}, {"name": "batch_normalization_3/beta", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.09514156486473832, "scale": 0.001340022040348427, "original_dtype": "float32"}}, {"name": "batch_normalization_3/moving_mean", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.9547533666386324, "scale": 0.006494920861487295, "original_dtype": "float32"}}, {"name": "batch_normalization_3/moving_variance", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.11602143198251724, "scale": 0.0022763355982069874, "original_dtype": "float32"}}, {"name": "batch_normalization_4/gamma", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8665672540664673, "scale": 0.0009945373909146177, "original_dtype": "float32"}}, {"name": "batch_normalization_4/beta", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15689800101168017, "scale": 0.001341008555655386, "original_dtype": "float32"}}, {"name": "batch_normalization_4/moving_mean", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.23295288717045504, "scale": 0.0017647945997761746, "original_dtype": "float32"}}, {"name": "batch_normalization_4/moving_variance", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0038869462441653013, "scale": 0.0003388032874128982, "original_dtype": "float32"}}, {"name": "batch_normalization_5/gamma", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8956666588783264, "scale": 0.0008007453937156528, "original_dtype": "float32"}}, {"name": "batch_normalization_5/beta", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.08552218745736515, "scale": 0.0009718430392882403, "original_dtype": "float32"}}, {"name": "batch_normalization_5/moving_mean", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.8875681821037742, "scale": 0.007044191921458525, "original_dtype": "float32"}}, {"name": "batch_normalization_5/moving_variance", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.2902527451515198, "scale": 0.004181738227021461, "original_dtype": "float32"}}, {"name": "batch_normalization_6/gamma", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8555991053581238, "scale": 0.0009618036887224983, "original_dtype": "float32"}}, {"name": "batch_normalization_6/beta", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1314079559316822, "scale": 0.0016633918472364836, "original_dtype": "float32"}}, {"name": "batch_normalization_6/moving_mean", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.9594602958828795, "scale": 0.006853287827734853, "original_dtype": "float32"}}, {"name": "batch_normalization_6/moving_variance", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.1818692535161972, "scale": 0.005610991985190148, "original_dtype": "float32"}}, {"name": "batch_normalization_7/gamma", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8718380331993103, "scale": 0.0009909828503926596, "original_dtype": "float32"}}, {"name": "batch_normalization_7/beta", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15812141971260893, "scale": 0.0011375641705943088, "original_dtype": "float32"}}, {"name": "batch_normalization_7/moving_mean", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.16419264668343114, "scale": 0.001453032271534789, "original_dtype": "float32"}}, {"name": "batch_normalization_7/moving_variance", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0010750072542577982, "scale": 0.00023130254326936077, "original_dtype": "float32"}}, {"name": "batch_normalization_8/gamma", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9192572832107544, "scale": 0.000793612237070121, "original_dtype": "float32"}}, {"name": "batch_normalization_8/beta", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11838542710916668, "scale": 0.0008516937202098322, "original_dtype": "float32"}}, {"name": "batch_normalization_8/moving_mean", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.9023126195458805, "scale": 0.007161211266237147, "original_dtype": "float32"}}, {"name": "batch_normalization_8/moving_variance", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.39027139544487, "scale": 0.002346922018948723, "original_dtype": "float32"}}, {"name": "batch_normalization_9/gamma", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8008539080619812, "scale": 0.0012547920731937185, "original_dtype": "float32"}}, {"name": "batch_normalization_9/beta", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10203244519584319, "scale": 0.0011464319684926201, "original_dtype": "float32"}}, {"name": "batch_normalization_9/moving_mean", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.479229459108091, "scale": 0.009796221583497291, "original_dtype": "float32"}}, {"name": "batch_normalization_9/moving_variance", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.3386923670768738, "scale": 0.0073804829634872135, "original_dtype": "float32"}}, {"name": "conv2d/kernel", "shape": [3, 3, 1, 32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2221566514641631, "scale": 0.0015321148376838834, "original_dtype": "float32"}}, {"name": "conv2d_1/kernel", "shape": [1, 1, 32, 32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.42333432085373823, "scale": 0.003413986458497889, "original_dtype": "float32"}}, {"name": "conv2d_10/kernel", "shape": [1, 1, 256, 256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2860528218979929, "scale": 0.002200406322292253, "original_dtype": "float32"}}, {"name": "conv2d_11/kernel", "shape": [1, 1, 256, 256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2831313468661963, "scale": 0.002128807119294709, "original_dtype": "float32"}}, {"name": "conv2d_12/kernel", "shape": [1, 1, 256, 512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.25069956183433534, "scale": 0.0019434074560801188, "original_dtype": "float32"}}, {"name": "conv2d_13/kernel", "shape": [1, 1, 512, 512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.25456632469214646, "scale": 0.001958202497631896, "original_dtype": "float32"}}, {"name": "conv2d_14/kernel", "shape": [1, 1, 512, 1024], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2959425442359027, "scale": 0.002144511190115237, "original_dtype": "float32"}}, {"name": "conv2d_2/kernel", "shape": [1, 1, 32, 64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.388486385345459, "scale": 0.0028565175393048454, "original_dtype": "float32"}}, {"name": "conv2d_3/kernel", "shape": [1, 1, 64, 64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.36906272523543415, "scale": 0.002636162323110244, "original_dtype": "float32"}}, {"name": "conv2d_4/kernel", "shape": [1, 1, 64, 128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.3059000337825102, "scale": 0.00231742449835235, "original_dtype": "float32"}}, {"name": "conv2d_5/kernel", "shape": [1, 1, 128, 128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.3167109966278076, "scale": 0.002328757328145644, "original_dtype": "float32"}}, {"name": "conv2d_6/kernel", "shape": [1, 1, 128, 128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.3360256821501489, "scale": 0.002400183443929635, "original_dtype": "float32"}}, {"name": "conv2d_7/kernel", "shape": [1, 1, 128, 128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2826858476096508, "scale": 0.0022258728158240223, "original_dtype": "float32"}}, {"name": "conv2d_8/kernel", "shape": [1, 1, 128, 256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.26113242658914304, "scale": 0.0019933773022071987, "original_dtype": "float32"}}, {"name": "conv2d_9/kernel", "shape": [1, 1, 256, 256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.27220653786378746, "scale": 0.0020938964451060575, "original_dtype": "float32"}}, {"name": "dense/kernel", "shape": [65536, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2788586934407552, "scale": 0.0021785835425059, "original_dtype": "float32"}}, {"name": "dense/bias", "shape": [1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.036377083510160446, "scale": 1.0, "original_dtype": "float32"}}, {"name": "depthwise_conv2d/depthwise_kernel", "shape": [3, 3, 32, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.27618510027726495, "scale": 0.0019313643376032512, "original_dtype": "float32"}}, {"name": "depthwise_conv2d/bias", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.062146706397042555, "scale": 0.0005701532696976381, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_1/depthwise_kernel", "shape": [3, 3, 64, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.19229700460153468, "scale": 0.0015261667031867831, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_1/bias", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.08224044889211655, "scale": 0.0005375192738046833, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_2/depthwise_kernel", "shape": [3, 3, 128, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1743617246548335, "scale": 0.0014652245769313738, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_2/bias", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.08746356517076492, "scale": 0.0007110858956972758, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_3/depthwise_kernel", "shape": [3, 3, 128, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2024411422364852, "scale": 0.0014564110952265123, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_3/bias", "shape": [128], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.07673602822948905, "scale": 0.000783020696219276, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_4/depthwise_kernel", "shape": [3, 3, 256, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.16412535017611934, "scale": 0.0012625026936624564, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_4/bias", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.08945655705882054, "scale": 0.0006389754075630038, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_5/depthwise_kernel", "shape": [3, 3, 256, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.17559047902331634, "scale": 0.0012632408562828514, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_5/bias", "shape": [256], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.06260400017102559, "scale": 0.0005260840350506353, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_6/depthwise_kernel", "shape": [3, 3, 512, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1392748005249921, "scale": 0.0011606233377082676, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_6/bias", "shape": [512], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.08358401051338982, "scale": 0.000584503570023705, "original_dtype": "float32"}}]}]}
\ No newline at end of file
+{"format": "layers-model", "generatedBy": "keras v2.4.0", "convertedBy": "TensorFlow.js Converter v2.8.2", "modelTopology": {"keras_version": "2.4.0", "backend": "tensorflow", "model_config": {"class_name": "Functional", "config": {"name": "model", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 32, 2048, 1], "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}, "name": "input_1", "inbound_nodes": []}, {"class_name": "Conv2D", "config": {"name": "conv2d", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [3, 3], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d", "inbound_nodes": [[["input_1", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization", "inbound_nodes": [[["conv2d", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation", "inbound_nodes": [[["batch_normalization", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_1", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_1", "inbound_nodes": [[["activation", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_1", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_1", "inbound_nodes": [[["conv2d_1", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_1", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_1", "inbound_nodes": [[["batch_normalization_1", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d", "inbound_nodes": [[["activation_1", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_2", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_2", "inbound_nodes": [[["depthwise_conv2d", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_2", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_2", "inbound_nodes": [[["batch_normalization_2", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_2", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_2", "inbound_nodes": [[["activation_2", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_3", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_3", "inbound_nodes": [[["conv2d_2", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_3", "trainable": true, "dtype": "float32", "filters": 96, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_3", "inbound_nodes": [[["batch_normalization_3", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_4", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_4", "inbound_nodes": [[["conv2d_3", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_3", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_3", "inbound_nodes": [[["batch_normalization_4", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_1", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_1", "inbound_nodes": [[["activation_3", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_5", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_5", "inbound_nodes": [[["depthwise_conv2d_1", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_4", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_4", "inbound_nodes": [[["batch_normalization_5", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_4", "trainable": true, "dtype": "float32", "filters": 24, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_4", "inbound_nodes": [[["activation_4", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_6", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_6", "inbound_nodes": [[["conv2d_4", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_5", "trainable": true, "dtype": "float32", "filters": 144, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_5", "inbound_nodes": [[["batch_normalization_6", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_7", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_7", "inbound_nodes": [[["conv2d_5", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_5", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_5", "inbound_nodes": [[["batch_normalization_7", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_2", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_2", "inbound_nodes": [[["activation_5", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_8", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_8", "inbound_nodes": [[["depthwise_conv2d_2", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_6", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_6", "inbound_nodes": [[["batch_normalization_8", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_6", "trainable": true, "dtype": "float32", "filters": 24, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_6", "inbound_nodes": [[["activation_6", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_9", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_9", "inbound_nodes": [[["conv2d_6", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add", "trainable": true, "dtype": "float32"}, "name": "add", "inbound_nodes": [[["batch_normalization_9", 0, 0, {}], ["batch_normalization_6", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_7", "trainable": true, "dtype": "float32", "filters": 144, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_7", "inbound_nodes": [[["add", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_10", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_10", "inbound_nodes": [[["conv2d_7", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_7", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_7", "inbound_nodes": [[["batch_normalization_10", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_3", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_3", "inbound_nodes": [[["activation_7", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_11", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_11", "inbound_nodes": [[["depthwise_conv2d_3", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_8", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_8", "inbound_nodes": [[["batch_normalization_11", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_8", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_8", "inbound_nodes": [[["activation_8", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_12", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_12", "inbound_nodes": [[["conv2d_8", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_9", "trainable": true, "dtype": "float32", "filters": 192, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_9", "inbound_nodes": [[["batch_normalization_12", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_13", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_13", "inbound_nodes": [[["conv2d_9", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_9", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_9", "inbound_nodes": [[["batch_normalization_13", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_4", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_4", "inbound_nodes": [[["activation_9", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_14", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_14", "inbound_nodes": [[["depthwise_conv2d_4", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_10", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_10", "inbound_nodes": [[["batch_normalization_14", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_10", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_10", "inbound_nodes": [[["activation_10", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_15", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_15", "inbound_nodes": [[["conv2d_10", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_1", "trainable": true, "dtype": "float32"}, "name": "add_1", "inbound_nodes": [[["batch_normalization_15", 0, 0, {}], ["batch_normalization_12", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_11", "trainable": true, "dtype": "float32", "filters": 192, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_11", "inbound_nodes": [[["add_1", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_16", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_16", "inbound_nodes": [[["conv2d_11", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_11", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_11", "inbound_nodes": [[["batch_normalization_16", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_5", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_5", "inbound_nodes": [[["activation_11", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_17", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_17", "inbound_nodes": [[["depthwise_conv2d_5", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_12", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_12", "inbound_nodes": [[["batch_normalization_17", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_12", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_12", "inbound_nodes": [[["activation_12", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_18", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_18", "inbound_nodes": [[["conv2d_12", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_2", "trainable": true, "dtype": "float32"}, "name": "add_2", "inbound_nodes": [[["batch_normalization_18", 0, 0, {}], ["add_1", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_13", "trainable": true, "dtype": "float32", "filters": 192, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_13", "inbound_nodes": [[["add_2", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_19", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_19", "inbound_nodes": [[["conv2d_13", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_13", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_13", "inbound_nodes": [[["batch_normalization_19", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_6", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_6", "inbound_nodes": [[["activation_13", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_20", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_20", "inbound_nodes": [[["depthwise_conv2d_6", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_14", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_14", "inbound_nodes": [[["batch_normalization_20", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_14", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_14", "inbound_nodes": [[["activation_14", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_21", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_21", "inbound_nodes": [[["conv2d_14", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_15", "trainable": true, "dtype": "float32", "filters": 384, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_15", "inbound_nodes": [[["batch_normalization_21", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_22", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_22", "inbound_nodes": [[["conv2d_15", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_15", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_15", "inbound_nodes": [[["batch_normalization_22", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_7", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_7", "inbound_nodes": [[["activation_15", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_23", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_23", "inbound_nodes": [[["depthwise_conv2d_7", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_16", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_16", "inbound_nodes": [[["batch_normalization_23", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_16", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_16", "inbound_nodes": [[["activation_16", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_24", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_24", "inbound_nodes": [[["conv2d_16", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_3", "trainable": true, "dtype": "float32"}, "name": "add_3", "inbound_nodes": [[["batch_normalization_24", 0, 0, {}], ["batch_normalization_21", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_17", "trainable": true, "dtype": "float32", "filters": 384, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_17", "inbound_nodes": [[["add_3", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_25", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_25", "inbound_nodes": [[["conv2d_17", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_17", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_17", "inbound_nodes": [[["batch_normalization_25", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_8", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_8", "inbound_nodes": [[["activation_17", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_26", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_26", "inbound_nodes": [[["depthwise_conv2d_8", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_18", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_18", "inbound_nodes": [[["batch_normalization_26", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_18", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_18", "inbound_nodes": [[["activation_18", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_27", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_27", "inbound_nodes": [[["conv2d_18", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_4", "trainable": true, "dtype": "float32"}, "name": "add_4", "inbound_nodes": [[["batch_normalization_27", 0, 0, {}], ["add_3", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_19", "trainable": true, "dtype": "float32", "filters": 384, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_19", "inbound_nodes": [[["add_4", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_28", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_28", "inbound_nodes": [[["conv2d_19", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_19", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_19", "inbound_nodes": [[["batch_normalization_28", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_9", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_9", "inbound_nodes": [[["activation_19", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_29", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_29", "inbound_nodes": [[["depthwise_conv2d_9", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_20", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_20", "inbound_nodes": [[["batch_normalization_29", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_20", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_20", "inbound_nodes": [[["activation_20", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_30", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_30", "inbound_nodes": [[["conv2d_20", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_5", "trainable": true, "dtype": "float32"}, "name": "add_5", "inbound_nodes": [[["batch_normalization_30", 0, 0, {}], ["add_4", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_21", "trainable": true, "dtype": "float32", "filters": 384, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_21", "inbound_nodes": [[["add_5", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_31", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_31", "inbound_nodes": [[["conv2d_21", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_21", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_21", "inbound_nodes": [[["batch_normalization_31", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_10", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_10", "inbound_nodes": [[["activation_21", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_32", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_32", "inbound_nodes": [[["depthwise_conv2d_10", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_22", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_22", "inbound_nodes": [[["batch_normalization_32", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_22", "trainable": true, "dtype": "float32", "filters": 96, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_22", "inbound_nodes": [[["activation_22", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_33", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_33", "inbound_nodes": [[["conv2d_22", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_23", "trainable": true, "dtype": "float32", "filters": 576, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_23", "inbound_nodes": [[["batch_normalization_33", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_34", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_34", "inbound_nodes": [[["conv2d_23", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_23", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_23", "inbound_nodes": [[["batch_normalization_34", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_11", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_11", "inbound_nodes": [[["activation_23", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_35", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_35", "inbound_nodes": [[["depthwise_conv2d_11", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_24", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_24", "inbound_nodes": [[["batch_normalization_35", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_24", "trainable": true, "dtype": "float32", "filters": 96, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_24", "inbound_nodes": [[["activation_24", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_36", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_36", "inbound_nodes": [[["conv2d_24", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_6", "trainable": true, "dtype": "float32"}, "name": "add_6", "inbound_nodes": [[["batch_normalization_36", 0, 0, {}], ["batch_normalization_33", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_25", "trainable": true, "dtype": "float32", "filters": 576, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_25", "inbound_nodes": [[["add_6", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_37", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_37", "inbound_nodes": [[["conv2d_25", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_25", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_25", "inbound_nodes": [[["batch_normalization_37", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_12", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_12", "inbound_nodes": [[["activation_25", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_38", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_38", "inbound_nodes": [[["depthwise_conv2d_12", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_26", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_26", "inbound_nodes": [[["batch_normalization_38", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_26", "trainable": true, "dtype": "float32", "filters": 96, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_26", "inbound_nodes": [[["activation_26", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_39", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_39", "inbound_nodes": [[["conv2d_26", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_7", "trainable": true, "dtype": "float32"}, "name": "add_7", "inbound_nodes": [[["batch_normalization_39", 0, 0, {}], ["add_6", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_27", "trainable": true, "dtype": "float32", "filters": 576, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_27", "inbound_nodes": [[["add_7", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_40", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_40", "inbound_nodes": [[["conv2d_27", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_27", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_27", "inbound_nodes": [[["batch_normalization_40", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_13", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_13", "inbound_nodes": [[["activation_27", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_41", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_41", "inbound_nodes": [[["depthwise_conv2d_13", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_28", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_28", "inbound_nodes": [[["batch_normalization_41", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_28", "trainable": true, "dtype": "float32", "filters": 160, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_28", "inbound_nodes": [[["activation_28", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_42", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_42", "inbound_nodes": [[["conv2d_28", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_29", "trainable": true, "dtype": "float32", "filters": 960, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_29", "inbound_nodes": [[["batch_normalization_42", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_43", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_43", "inbound_nodes": [[["conv2d_29", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_29", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_29", "inbound_nodes": [[["batch_normalization_43", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_14", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_14", "inbound_nodes": [[["activation_29", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_44", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_44", "inbound_nodes": [[["depthwise_conv2d_14", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_30", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_30", "inbound_nodes": [[["batch_normalization_44", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_30", "trainable": true, "dtype": "float32", "filters": 160, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_30", "inbound_nodes": [[["activation_30", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_45", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_45", "inbound_nodes": [[["conv2d_30", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_8", "trainable": true, "dtype": "float32"}, "name": "add_8", "inbound_nodes": [[["batch_normalization_45", 0, 0, {}], ["batch_normalization_42", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_31", "trainable": true, "dtype": "float32", "filters": 960, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_31", "inbound_nodes": [[["add_8", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_46", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_46", "inbound_nodes": [[["conv2d_31", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_31", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_31", "inbound_nodes": [[["batch_normalization_46", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_15", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_15", "inbound_nodes": [[["activation_31", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_47", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_47", "inbound_nodes": [[["depthwise_conv2d_15", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_32", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_32", "inbound_nodes": [[["batch_normalization_47", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_32", "trainable": true, "dtype": "float32", "filters": 160, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_32", "inbound_nodes": [[["activation_32", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_48", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_48", "inbound_nodes": [[["conv2d_32", 0, 0, {}]]]}, {"class_name": "Add", "config": {"name": "add_9", "trainable": true, "dtype": "float32"}, "name": "add_9", "inbound_nodes": [[["batch_normalization_48", 0, 0, {}], ["add_8", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_33", "trainable": true, "dtype": "float32", "filters": 960, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_33", "inbound_nodes": [[["add_9", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_49", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_49", "inbound_nodes": [[["conv2d_33", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_33", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_33", "inbound_nodes": [[["batch_normalization_49", 0, 0, {}]]]}, {"class_name": "DepthwiseConv2D", "config": {"name": "depthwise_conv2d_16", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "bias_initializer": {"class_name": "Zeros", "config": {}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "name": "depthwise_conv2d_16", "inbound_nodes": [[["activation_33", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_50", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_50", "inbound_nodes": [[["depthwise_conv2d_16", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_34", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_34", "inbound_nodes": [[["batch_normalization_50", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_34", "trainable": true, "dtype": "float32", "filters": 320, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_34", "inbound_nodes": [[["activation_34", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_51", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_51", "inbound_nodes": [[["conv2d_34", 0, 0, {}]]]}, {"class_name": "Conv2D", "config": {"name": "conv2d_35", "trainable": true, "dtype": "float32", "filters": 1280, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "conv2d_35", "inbound_nodes": [[["batch_normalization_51", 0, 0, {}]]]}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization_52", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "name": "batch_normalization_52", "inbound_nodes": [[["conv2d_35", 0, 0, {}]]]}, {"class_name": "Activation", "config": {"name": "activation_35", "trainable": true, "dtype": "float32", "activation": "relu6"}, "name": "activation_35", "inbound_nodes": [[["batch_normalization_52", 0, 0, {}]]]}, {"class_name": "GlobalAveragePooling2D", "config": {"name": "global_average_pooling2d", "trainable": true, "dtype": "float32", "data_format": "channels_last"}, "name": "global_average_pooling2d", "inbound_nodes": [[["activation_35", 0, 0, {}]]]}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense", "inbound_nodes": [[["global_average_pooling2d", 0, 0, {}]]]}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense", 0, 0]]}}, "training_config": {"loss": "mean_absolute_error", "metrics": [[{"class_name": "MeanMetricWrapper", "config": {"name": "mae", "dtype": "float32", "fn": "mean_absolute_error"}}, {"class_name": "MeanMetricWrapper", "config": {"name": "mse", "dtype": "float32", "fn": "mean_squared_error"}}]], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 9.999999747378752e-05, "decay": 0.0, "beta_1": 0.8999999761581421, "beta_2": 0.9990000128746033, "epsilon": 1e-07, "amsgrad": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "batch_normalization/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9385895729064941, "scale": 0.0007102218328737745, "original_dtype": "float32"}}, {"name": "batch_normalization/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1677851158029893, "scale": 0.0011899653603048886, "original_dtype": "float32"}}, {"name": "batch_normalization/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.07954961061477661, "scale": 0.0013258268435796102, "original_dtype": "float32"}}, {"name": "batch_normalization/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0003125735092908144, "scale": 0.00013062765767030856, "original_dtype": "float32"}}, {"name": "batch_normalization_1/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8687363266944885, "scale": 0.0016239182621824975, "original_dtype": "float32"}}, {"name": "batch_normalization_1/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1368191016655342, "scale": 0.0015909197868085375, "original_dtype": "float32"}}, {"name": "batch_normalization_1/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.4350293484388613, "scale": 0.0035658143314660764, "original_dtype": "float32"}}, {"name": "batch_normalization_1/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.05367473512887955, "scale": 0.003262728423464532, "original_dtype": "float32"}}, {"name": "batch_normalization_10/gamma", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.860715925693512, "scale": 0.0009144490840388279, "original_dtype": "float32"}}, {"name": "batch_normalization_10/beta", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10952971151062085, "scale": 0.0010236421636506622, "original_dtype": "float32"}}, {"name": "batch_normalization_10/moving_mean", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0188760848606334, "scale": 0.00016853647196994108, "original_dtype": "float32"}}, {"name": "batch_normalization_10/moving_variance", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.26874446868896484, "scale": 0.00879958844652363, "original_dtype": "float32"}}, {"name": "batch_normalization_11/gamma", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8602131009101868, "scale": 0.00097730931113748, "original_dtype": "float32"}}, {"name": "batch_normalization_11/beta", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12420455497853897, "scale": 0.0010350379581544914, "original_dtype": "float32"}}, {"name": "batch_normalization_11/moving_mean", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.161060010860948, "scale": 0.0012485272159763411, "original_dtype": "float32"}}, {"name": "batch_normalization_11/moving_variance", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0006880097207613289, "scale": 0.00023882296735274733, "original_dtype": "float32"}}, {"name": "batch_normalization_12/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8795132040977478, "scale": 0.0007745492692087211, "original_dtype": "float32"}}, {"name": "batch_normalization_12/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.021299870465608203, "scale": 0.00017039896372486563, "original_dtype": "float32"}}, {"name": "batch_normalization_12/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.8655187260870839, "scale": 0.006095202296387915, "original_dtype": "float32"}}, {"name": "batch_normalization_12/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.5695487260818481, "scale": 0.0077187842013789155, "original_dtype": "float32"}}, {"name": "batch_normalization_13/gamma", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9061563014984131, "scale": 0.0007664666456334732, "original_dtype": "float32"}}, {"name": "batch_normalization_13/beta", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10793713491921332, "scale": 0.0010087582702730216, "original_dtype": "float32"}}, {"name": "batch_normalization_13/moving_mean", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.014036830952938865, "scale": 0.00012205939959077275, "original_dtype": "float32"}}, {"name": "batch_normalization_13/moving_variance", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.09700797498226166, "scale": 0.006215738373644212, "original_dtype": "float32"}}, {"name": "batch_normalization_14/gamma", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9059752225875854, "scale": 0.0008353200613283643, "original_dtype": "float32"}}, {"name": "batch_normalization_14/beta", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10011272515152014, "scale": 0.0007307498186242346, "original_dtype": "float32"}}, {"name": "batch_normalization_14/moving_mean", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1675617161919089, "scale": 0.0012142153347239775, "original_dtype": "float32"}}, {"name": "batch_normalization_14/moving_variance", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.000997444149106741, "scale": 0.0001054308961565588, "original_dtype": "float32"}}, {"name": "batch_normalization_15/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9497214555740356, "scale": 0.00039855078154919197, "original_dtype": "float32"}}, {"name": "batch_normalization_15/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.015654228773771546, "scale": 9.43025829745274e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_15/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.9174099950229421, "scale": 0.008340090863844928, "original_dtype": "float32"}}, {"name": "batch_normalization_15/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.6102715134620667, "scale": 0.005521138275370878, "original_dtype": "float32"}}, {"name": "batch_normalization_16/gamma", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9190177917480469, "scale": 0.0006538811851950253, "original_dtype": "float32"}}, {"name": "batch_normalization_16/beta", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.09621828987317926, "scale": 0.0007822625192941404, "original_dtype": "float32"}}, {"name": "batch_normalization_16/moving_mean", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.02017283389965693, "scale": 0.0001516754428545634, "original_dtype": "float32"}}, {"name": "batch_normalization_16/moving_variance", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.2681731581687927, "scale": 0.010027683716194303, "original_dtype": "float32"}}, {"name": "batch_normalization_17/gamma", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9154923558235168, "scale": 0.0008031627711127786, "original_dtype": "float32"}}, {"name": "batch_normalization_17/beta", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.09676196464136536, "scale": 0.0007996856581931021, "original_dtype": "float32"}}, {"name": "batch_normalization_17/moving_mean", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.14776077112730812, "scale": 0.001145432334320218, "original_dtype": "float32"}}, {"name": "batch_normalization_17/moving_variance", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.00103881792165339, "scale": 0.00014816223753287512, "original_dtype": "float32"}}, {"name": "batch_normalization_18/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9493341445922852, "scale": 0.0006042971330530503, "original_dtype": "float32"}}, {"name": "batch_normalization_18/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.014466250351831024, "scale": 9.33306474311679e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_18/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.053626728992836, "scale": 0.009578424809025782, "original_dtype": "float32"}}, {"name": "batch_normalization_18/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.5485694408416748, "scale": 0.003859102492238961, "original_dtype": "float32"}}, {"name": "batch_normalization_19/gamma", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8636776804924011, "scale": 0.0008988817532857259, "original_dtype": "float32"}}, {"name": "batch_normalization_19/beta", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.08729399024855858, "scale": 0.0009188841078795639, "original_dtype": "float32"}}, {"name": "batch_normalization_19/moving_mean", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.02125972280023145, "scale": 0.00018016714237484278, "original_dtype": "float32"}}, {"name": "batch_normalization_19/moving_variance", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.6557935476303101, "scale": 0.01568893965552835, "original_dtype": "float32"}}, {"name": "batch_normalization_2/gamma", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8869000673294067, "scale": 0.0008877207251156078, "original_dtype": "float32"}}, {"name": "batch_normalization_2/beta", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12045021255811056, "scale": 0.0017713266552663318, "original_dtype": "float32"}}, {"name": "batch_normalization_2/moving_mean", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2368187540886449, "scale": 0.001864714599123188, "original_dtype": "float32"}}, {"name": "batch_normalization_2/moving_variance", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.006741279736161232, "scale": 0.00040430180436255885, "original_dtype": "float32"}}, {"name": "batch_normalization_20/gamma", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8614142537117004, "scale": 0.000957342456368839, "original_dtype": "float32"}}, {"name": "batch_normalization_20/beta", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10491393547432096, "scale": 0.0009044304782269048, "original_dtype": "float32"}}, {"name": "batch_normalization_20/moving_mean", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.16864682178871304, "scale": 0.0012046201556336646, "original_dtype": "float32"}}, {"name": "batch_normalization_20/moving_variance", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0008970884955488145, "scale": 0.00016247515644257266, "original_dtype": "float32"}}, {"name": "batch_normalization_21/gamma", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9034570455551147, "scale": 0.000489503262089748, "original_dtype": "float32"}}, {"name": "batch_normalization_21/beta", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.010560100828753968, "scale": 9.345221972348643e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_21/moving_mean", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.5721539048587574, "scale": 0.011229670748991125, "original_dtype": "float32"}}, {"name": "batch_normalization_21/moving_variance", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.6636109948158264, "scale": 0.005308706386416566, "original_dtype": "float32"}}, {"name": "batch_normalization_22/gamma", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8652487397193909, "scale": 0.0008848043049083037, "original_dtype": "float32"}}, {"name": "batch_normalization_22/beta", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10434600184945499, "scale": 0.0008552950971266802, "original_dtype": "float32"}}, {"name": "batch_normalization_22/moving_mean", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.008626448894467424, "scale": 6.389962144049943e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_22/moving_variance", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.18021883070468903, "scale": 0.006848613362686307, "original_dtype": "float32"}}, {"name": "batch_normalization_23/gamma", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8599535822868347, "scale": 0.0009613633155822754, "original_dtype": "float32"}}, {"name": "batch_normalization_23/beta", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11703306792997847, "scale": 0.0008241765347181582, "original_dtype": "float32"}}, {"name": "batch_normalization_23/moving_mean", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10281915173811072, "scale": 0.0008225532139048857, "original_dtype": "float32"}}, {"name": "batch_normalization_23/moving_variance", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.000384291895898059, "scale": 5.7635601386245266e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_24/gamma", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9210916757583618, "scale": 0.0005350220437143363, "original_dtype": "float32"}}, {"name": "batch_normalization_24/beta", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.005708654255916675, "scale": 4.56692340473334e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_24/moving_mean", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.2914804009830252, "scale": 0.011038294025495941, "original_dtype": "float32"}}, {"name": "batch_normalization_24/moving_variance", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8105413913726807, "scale": 0.0051140177483652155, "original_dtype": "float32"}}, {"name": "batch_normalization_25/gamma", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.895435631275177, "scale": 0.0009475471926670448, "original_dtype": "float32"}}, {"name": "batch_normalization_25/beta", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1368403715245864, "scale": 0.0010690654025358312, "original_dtype": "float32"}}, {"name": "batch_normalization_25/moving_mean", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.010331618413329125, "scale": 8.682032280108508e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_25/moving_variance", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.3446427881717682, "scale": 0.008605291679793714, "original_dtype": "float32"}}, {"name": "batch_normalization_26/gamma", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8933859467506409, "scale": 0.0008684436480204265, "original_dtype": "float32"}}, {"name": "batch_normalization_26/beta", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12922400002386056, "scale": 0.0008912000001645555, "original_dtype": "float32"}}, {"name": "batch_normalization_26/moving_mean", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12258917236444997, "scale": 0.0009217231004845862, "original_dtype": "float32"}}, {"name": "batch_normalization_26/moving_variance", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0006230715662240982, "scale": 8.088148692074943e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_27/gamma", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9628424644470215, "scale": 0.0005515065847658643, "original_dtype": "float32"}}, {"name": "batch_normalization_27/beta", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0053327425492598725, "scale": 3.782086914368704e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_27/moving_mean", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.563332602557014, "scale": 0.011328497119978362, "original_dtype": "float32"}}, {"name": "batch_normalization_27/moving_variance", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.5551180243492126, "scale": 0.0032128032516030706, "original_dtype": "float32"}}, {"name": "batch_normalization_28/gamma", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.910164475440979, "scale": 0.0008189192005232269, "original_dtype": "float32"}}, {"name": "batch_normalization_28/beta", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11231156219454373, "scale": 0.0008138518999604618, "original_dtype": "float32"}}, {"name": "batch_normalization_28/moving_mean", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.017311604145695183, "scale": 0.00012823410478292728, "original_dtype": "float32"}}, {"name": "batch_normalization_28/moving_variance", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.6624442934989929, "scale": 0.009449986149283017, "original_dtype": "float32"}}, {"name": "batch_normalization_29/gamma", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.909457802772522, "scale": 0.0008671442667643229, "original_dtype": "float32"}}, {"name": "batch_normalization_29/beta", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0932079295317332, "scale": 0.0007639994223912557, "original_dtype": "float32"}}, {"name": "batch_normalization_29/moving_mean", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0997746113468619, "scale": 0.0007558682677792568, "original_dtype": "float32"}}, {"name": "batch_normalization_29/moving_variance", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0006971557158976793, "scale": 4.538240221639474e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_3/gamma", "shape": [16], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9149367809295654, "scale": 0.0006869409598556219, "original_dtype": "float32"}}, {"name": "batch_normalization_3/beta", "shape": [16], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.010259166732430457, "scale": 0.00020116013200844034, "original_dtype": "float32"}}, {"name": "batch_normalization_3/moving_mean", "shape": [16], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.49882051944732664, "scale": 0.004890397249483595, "original_dtype": "float32"}}, {"name": "batch_normalization_3/moving_variance", "shape": [16], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.3843691647052765, "scale": 0.0027634741044511983, "original_dtype": "float32"}}, {"name": "batch_normalization_30/gamma", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9883432984352112, "scale": 0.0006621335067001044, "original_dtype": "float32"}}, {"name": "batch_normalization_30/beta", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.004480185629982574, "scale": 3.343422111927294e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_30/moving_mean", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.744024243074305, "scale": 0.011179642583809647, "original_dtype": "float32"}}, {"name": "batch_normalization_30/moving_variance", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.4864237904548645, "scale": 0.0024614682384565766, "original_dtype": "float32"}}, {"name": "batch_normalization_31/gamma", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.913253903388977, "scale": 0.0007615351209453508, "original_dtype": "float32"}}, {"name": "batch_normalization_31/beta", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.138912464269236, "scale": 0.0008628103370760001, "original_dtype": "float32"}}, {"name": "batch_normalization_31/moving_mean", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.01586448445039637, "scale": 0.00012394128476872165, "original_dtype": "float32"}}, {"name": "batch_normalization_31/moving_variance", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 1.1550679206848145, "scale": 0.016255889219396254, "original_dtype": "float32"}}, {"name": "batch_normalization_32/gamma", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9059028625488281, "scale": 0.000734557824976304, "original_dtype": "float32"}}, {"name": "batch_normalization_32/beta", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10338608090784035, "scale": 0.0008544304207259533, "original_dtype": "float32"}}, {"name": "batch_normalization_32/moving_mean", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12331505613584146, "scale": 0.0008276178264150433, "original_dtype": "float32"}}, {"name": "batch_normalization_32/moving_variance", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0010319455759599805, "scale": 5.023706950904692e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_33/gamma", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8988609313964844, "scale": 0.0004735764335183536, "original_dtype": "float32"}}, {"name": "batch_normalization_33/beta", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0072798339704818575, "scale": 6.933175209982722e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_33/moving_mean", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.533296324225033, "scale": 0.011110842929166906, "original_dtype": "float32"}}, {"name": "batch_normalization_33/moving_variance", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.6509542465209961, "scale": 0.005507546780156154, "original_dtype": "float32"}}, {"name": "batch_normalization_34/gamma", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8723058104515076, "scale": 0.0010866373193030264, "original_dtype": "float32"}}, {"name": "batch_normalization_34/beta", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15298380057017008, "scale": 0.0011248808865453683, "original_dtype": "float32"}}, {"name": "batch_normalization_34/moving_mean", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.006078708432980028, "scale": 4.8629667463840225e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_34/moving_variance", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.2553531527519226, "scale": 0.0037995055610058354, "original_dtype": "float32"}}, {"name": "batch_normalization_35/gamma", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.872315526008606, "scale": 0.0008975047691195619, "original_dtype": "float32"}}, {"name": "batch_normalization_35/beta", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11134449541568757, "scale": 0.0007277418001025331, "original_dtype": "float32"}}, {"name": "batch_normalization_35/moving_mean", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1326252488528981, "scale": 0.0008501618516211417, "original_dtype": "float32"}}, {"name": "batch_normalization_35/moving_variance", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0002015085337916389, "scale": 5.5213133272641866e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_36/gamma", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9410464763641357, "scale": 0.0006025024488860485, "original_dtype": "float32"}}, {"name": "batch_normalization_36/beta", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.004007704419029109, "scale": 3.676793044980834e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_36/moving_mean", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -2.357596991108913, "scale": 0.015822798598046396, "original_dtype": "float32"}}, {"name": "batch_normalization_36/moving_variance", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.6366763114929199, "scale": 0.006012847376804725, "original_dtype": "float32"}}, {"name": "batch_normalization_37/gamma", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8819846510887146, "scale": 0.0010171252138474409, "original_dtype": "float32"}}, {"name": "batch_normalization_37/beta", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15137921373049418, "scale": 0.0009230439861615499, "original_dtype": "float32"}}, {"name": "batch_normalization_37/moving_mean", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.007908884267888817, "scale": 6.536267989990758e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_37/moving_variance", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.5288673639297485, "scale": 0.01273637145173316, "original_dtype": "float32"}}, {"name": "batch_normalization_38/gamma", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8959226012229919, "scale": 0.0007717593043458227, "original_dtype": "float32"}}, {"name": "batch_normalization_38/beta", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10262371687328115, "scale": 0.0006578443389312894, "original_dtype": "float32"}}, {"name": "batch_normalization_38/moving_mean", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.18219632804393768, "scale": 0.0011177688837051391, "original_dtype": "float32"}}, {"name": "batch_normalization_38/moving_variance", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0004686879983637482, "scale": 9.850509791160185e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_39/gamma", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9631342887878418, "scale": 0.0008341527452655867, "original_dtype": "float32"}}, {"name": "batch_normalization_39/beta", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.00338733047246933, "scale": 3.320912227911108e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_39/moving_mean", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.8099937452989465, "scale": 0.013211633177364573, "original_dtype": "float32"}}, {"name": "batch_normalization_39/moving_variance", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.4799489378929138, "scale": 0.004081273780149572, "original_dtype": "float32"}}, {"name": "batch_normalization_4/gamma", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8062904477119446, "scale": 0.001457680206672818, "original_dtype": "float32"}}, {"name": "batch_normalization_4/beta", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0631200134754181, "scale": 0.0012376473230474136, "original_dtype": "float32"}}, {"name": "batch_normalization_4/moving_mean", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.017212461022769705, "scale": 0.00013447235174038832, "original_dtype": "float32"}}, {"name": "batch_normalization_4/moving_variance", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0855109766125679, "scale": 0.00287064287592383, "original_dtype": "float32"}}, {"name": "batch_normalization_40/gamma", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8593794107437134, "scale": 0.0009297688802083333, "original_dtype": "float32"}}, {"name": "batch_normalization_40/beta", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.13424063649247675, "scale": 0.0009943750851294573, "original_dtype": "float32"}}, {"name": "batch_normalization_40/moving_mean", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.011334368466016126, "scale": 8.273261654026369e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_40/moving_variance", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 1.093737244606018, "scale": 0.02690145969390869, "original_dtype": "float32"}}, {"name": "batch_normalization_41/gamma", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.850437343120575, "scale": 0.000985400583229813, "original_dtype": "float32"}}, {"name": "batch_normalization_41/beta", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1533861100381496, "scale": 0.0008474370720339756, "original_dtype": "float32"}}, {"name": "batch_normalization_41/moving_mean", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15359292264078178, "scale": 0.0010520063194574094, "original_dtype": "float32"}}, {"name": "batch_normalization_41/moving_variance", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 2.733341716520954e-05, "scale": 6.022916765918946e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_42/gamma", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8411954641342163, "scale": 0.0007255418627869849, "original_dtype": "float32"}}, {"name": "batch_normalization_42/beta", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.006538666242404896, "scale": 5.0687335212441055e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_42/moving_mean", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -2.5003265829647288, "scale": 0.01811830857220818, "original_dtype": "float32"}}, {"name": "batch_normalization_42/moving_variance", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.7523094415664673, "scale": 0.016226577291301652, "original_dtype": "float32"}}, {"name": "batch_normalization_43/gamma", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8479723930358887, "scale": 0.0009831966138353534, "original_dtype": "float32"}}, {"name": "batch_normalization_43/beta", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1931873559951782, "scale": 0.0012626624574848249, "original_dtype": "float32"}}, {"name": "batch_normalization_43/moving_mean", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.005205456664164861, "scale": 3.8275416648271035e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_43/moving_variance", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.23457525670528412, "scale": 0.02729050137248694, "original_dtype": "float32"}}, {"name": "batch_normalization_44/gamma", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8392086029052734, "scale": 0.001027978167814367, "original_dtype": "float32"}}, {"name": "batch_normalization_44/beta", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1375285639482386, "scale": 0.0008186224044538011, "original_dtype": "float32"}}, {"name": "batch_normalization_44/moving_mean", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.08298184398342581, "scale": 0.0006746491380766326, "original_dtype": "float32"}}, {"name": "batch_normalization_44/moving_variance", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 5.521741996972196e-08, "scale": 9.180249942410652e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_45/gamma", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9137166142463684, "scale": 0.000685279275856766, "original_dtype": "float32"}}, {"name": "batch_normalization_45/beta", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0036871062491235194, "scale": 2.814584922995053e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_45/moving_mean", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.93830853396771, "scale": 0.017153172866970885, "original_dtype": "float32"}}, {"name": "batch_normalization_45/moving_variance", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.41984736919403076, "scale": 0.009156489372253418, "original_dtype": "float32"}}, {"name": "batch_normalization_46/gamma", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8495439887046814, "scale": 0.0011656852329478544, "original_dtype": "float32"}}, {"name": "batch_normalization_46/beta", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.19732067877171086, "scale": 0.0012813031089072135, "original_dtype": "float32"}}, {"name": "batch_normalization_46/moving_mean", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.005540106589814612, "scale": 4.902749194526205e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_46/moving_variance", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.2896397113800049, "scale": 0.03410262594036027, "original_dtype": "float32"}}, {"name": "batch_normalization_47/gamma", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8449714183807373, "scale": 0.0009920265160354913, "original_dtype": "float32"}}, {"name": "batch_normalization_47/beta", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.19877113004525504, "scale": 0.0010629472194933424, "original_dtype": "float32"}}, {"name": "batch_normalization_47/moving_mean", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.10279930120005326, "scale": 0.0007290730581564061, "original_dtype": "float32"}}, {"name": "batch_normalization_47/moving_variance", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 1.1859075499387473e-08, "scale": 8.815953276618527e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_48/gamma", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9616020917892456, "scale": 0.0008595532062006931, "original_dtype": "float32"}}, {"name": "batch_normalization_48/beta", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.003194434513502261, "scale": 2.1584016983123385e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_48/moving_mean", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -2.3021259868846218, "scale": 0.016212154837215647, "original_dtype": "float32"}}, {"name": "batch_normalization_48/moving_variance", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.3343963325023651, "scale": 0.00813579758008321, "original_dtype": "float32"}}, {"name": "batch_normalization_49/gamma", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8310521245002747, "scale": 0.0010574072015051748, "original_dtype": "float32"}}, {"name": "batch_normalization_49/beta", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.20364664395650228, "scale": 0.0014974017937978109, "original_dtype": "float32"}}, {"name": "batch_normalization_49/moving_mean", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.006477773014236899, "scale": 5.3535314167247096e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_49/moving_variance", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.36214160919189453, "scale": 0.06643476673201018, "original_dtype": "float32"}}, {"name": "batch_normalization_5/gamma", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8684298396110535, "scale": 0.001073173681894938, "original_dtype": "float32"}}, {"name": "batch_normalization_5/beta", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11099960067692924, "scale": 0.0011935440933003145, "original_dtype": "float32"}}, {"name": "batch_normalization_5/moving_mean", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.18004005200722636, "scale": 0.0012768798014696906, "original_dtype": "float32"}}, {"name": "batch_normalization_5/moving_variance", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.001406269264407456, "scale": 0.0003329785645702014, "original_dtype": "float32"}}, {"name": "batch_normalization_50/gamma", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8183675408363342, "scale": 0.0010813579839818617, "original_dtype": "float32"}}, {"name": "batch_normalization_50/beta", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.23172025107869915, "scale": 0.0011822461789729548, "original_dtype": "float32"}}, {"name": "batch_normalization_50/moving_mean", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11936784436305364, "scale": 0.0007323180635770162, "original_dtype": "float32"}}, {"name": "batch_normalization_50/moving_variance", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 5.5257656228491214e-09, "scale": 6.538189825895641e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_51/gamma", "shape": [320], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9253116846084595, "scale": 0.0008041152767106598, "original_dtype": "float32"}}, {"name": "batch_normalization_51/beta", "shape": [320], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.004989307636723799, "scale": 3.7797785126695446e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_51/moving_mean", "shape": [320], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -2.107944774627686, "scale": 0.01672972043355306, "original_dtype": "float32"}}, {"name": "batch_normalization_51/moving_variance", "shape": [320], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.48218080401420593, "scale": 0.0238720810880848, "original_dtype": "float32"}}, {"name": "batch_normalization_52/gamma", "shape": [1280], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.947289228439331, "scale": 0.0009862474366730334, "original_dtype": "float32"}}, {"name": "batch_normalization_52/beta", "shape": [1280], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.153318410059985, "scale": 0.0014328823370092056, "original_dtype": "float32"}}, {"name": "batch_normalization_52/moving_mean", "shape": [1280], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.002979560037527014, "scale": 2.613649155725451e-05, "original_dtype": "float32"}}, {"name": "batch_normalization_52/moving_variance", "shape": [1280], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 6.9613871574401855, "scale": 0.2355717995587517, "original_dtype": "float32"}}, {"name": "batch_normalization_6/gamma", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.9278210401535034, "scale": 0.0005613069908291686, "original_dtype": "float32"}}, {"name": "batch_normalization_6/beta", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.02542790055421053, "scale": 0.00018293453636122686, "original_dtype": "float32"}}, {"name": "batch_normalization_6/moving_mean", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.2457820291612662, "scale": 0.007737776578641406, "original_dtype": "float32"}}, {"name": "batch_normalization_6/moving_variance", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.5959154367446899, "scale": 0.004918565937117034, "original_dtype": "float32"}}, {"name": "batch_normalization_7/gamma", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8418448567390442, "scale": 0.0011479506305619783, "original_dtype": "float32"}}, {"name": "batch_normalization_7/beta", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.08443734026422688, "scale": 0.0011110176350556168, "original_dtype": "float32"}}, {"name": "batch_normalization_7/moving_mean", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.021163670017438775, "scale": 0.00016033083346544527, "original_dtype": "float32"}}, {"name": "batch_normalization_7/moving_variance", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.08133947104215622, "scale": 0.0028051329886212067, "original_dtype": "float32"}}, {"name": "batch_normalization_8/gamma", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8789770603179932, "scale": 0.001144583552491431, "original_dtype": "float32"}}, {"name": "batch_normalization_8/beta", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12246864151720907, "scale": 0.0008624552219521765, "original_dtype": "float32"}}, {"name": "batch_normalization_8/moving_mean", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15234035200932447, "scale": 0.001065317146918353, "original_dtype": "float32"}}, {"name": "batch_normalization_8/moving_variance", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.0010820104507729411, "scale": 0.00016680495749574667, "original_dtype": "float32"}}, {"name": "batch_normalization_9/gamma", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.8921323418617249, "scale": 0.0009954492251078287, "original_dtype": "float32"}}, {"name": "batch_normalization_9/beta", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.016850188812788793, "scale": 0.00013588861945797414, "original_dtype": "float32"}}, {"name": "batch_normalization_9/moving_mean", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -1.1476667689342126, "scale": 0.009036746212080414, "original_dtype": "float32"}}, {"name": "batch_normalization_9/moving_variance", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.52339768409729, "scale": 0.004100509718352673, "original_dtype": "float32"}}, {"name": "conv2d/kernel", "shape": [3, 3, 1, 32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.20180024206638336, "scale": 0.0019219070672988892, "original_dtype": "float32"}}, {"name": "conv2d_1/kernel", "shape": [1, 1, 32, 32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.3434422640239491, "scale": 0.0030126514388065712, "original_dtype": "float32"}}, {"name": "conv2d_10/kernel", "shape": [1, 1, 192, 32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.26509522550246295, "scale": 0.0021039303611306583, "original_dtype": "float32"}}, {"name": "conv2d_10/bias", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.009703452698886395, "scale": 8.154161931837306e-05, "original_dtype": "float32"}}, {"name": "conv2d_11/kernel", "shape": [1, 1, 32, 192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2550060496610754, "scale": 0.0019922347629771514, "original_dtype": "float32"}}, {"name": "conv2d_12/kernel", "shape": [1, 1, 192, 32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.26651192833395565, "scale": 0.002258575663847082, "original_dtype": "float32"}}, {"name": "conv2d_12/bias", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.009615687529246011, "scale": 7.070358477386773e-05, "original_dtype": "float32"}}, {"name": "conv2d_13/kernel", "shape": [1, 1, 32, 192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.25344642470864687, "scale": 0.002112053539238724, "original_dtype": "float32"}}, {"name": "conv2d_14/kernel", "shape": [1, 1, 192, 64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2595984914723565, "scale": 0.002060305487875845, "original_dtype": "float32"}}, {"name": "conv2d_14/bias", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.00726956309495019, "scale": 5.8625508830243465e-05, "original_dtype": "float32"}}, {"name": "conv2d_15/kernel", "shape": [1, 1, 64, 384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.21454621325520906, "scale": 0.00168934026185204, "original_dtype": "float32"}}, {"name": "conv2d_16/kernel", "shape": [1, 1, 384, 64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2096828978435666, "scale": 0.0016510464397131229, "original_dtype": "float32"}}, {"name": "conv2d_16/bias", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.004486488222199328, "scale": 4.154155761295674e-05, "original_dtype": "float32"}}, {"name": "conv2d_17/kernel", "shape": [1, 1, 64, 384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.21171207328637442, "scale": 0.0017790930528266758, "original_dtype": "float32"}}, {"name": "conv2d_18/kernel", "shape": [1, 1, 384, 64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.22080608588807724, "scale": 0.0017664486871046179, "original_dtype": "float32"}}, {"name": "conv2d_18/bias", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0047163533704245785, "scale": 3.8344336344915275e-05, "original_dtype": "float32"}}, {"name": "conv2d_19/kernel", "shape": [1, 1, 64, 384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2343325138092041, "scale": 0.0017230331897735596, "original_dtype": "float32"}}, {"name": "conv2d_2/kernel", "shape": [1, 1, 32, 16], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.4437114824266995, "scale": 0.003607410426233329, "original_dtype": "float32"}}, {"name": "conv2d_2/bias", "shape": [16], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.035083632010455224, "scale": 0.00031047461956155065, "original_dtype": "float32"}}, {"name": "conv2d_20/kernel", "shape": [1, 1, 384, 64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.22526967034620396, "scale": 0.0017878545265571743, "original_dtype": "float32"}}, {"name": "conv2d_20/bias", "shape": [64], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0034707414862864155, "scale": 2.6494209818980272e-05, "original_dtype": "float32"}}, {"name": "conv2d_21/kernel", "shape": [1, 1, 64, 384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2304322798462475, "scale": 0.00184345823876998, "original_dtype": "float32"}}, {"name": "conv2d_22/kernel", "shape": [1, 1, 384, 96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2281865360690098, "scale": 0.0018254922885520785, "original_dtype": "float32"}}, {"name": "conv2d_22/bias", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.005716230574191785, "scale": 5.1965732492652594e-05, "original_dtype": "float32"}}, {"name": "conv2d_23/kernel", "shape": [1, 1, 96, 576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.20260243550235152, "scale": 0.0015952947677350512, "original_dtype": "float32"}}, {"name": "conv2d_24/kernel", "shape": [1, 1, 576, 96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.17954227643854478, "scale": 0.0014838204664342543, "original_dtype": "float32"}}, {"name": "conv2d_24/bias", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.004046539465586345, "scale": 3.161358957489332e-05, "original_dtype": "float32"}}, {"name": "conv2d_25/kernel", "shape": [1, 1, 96, 576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.22389687101046243, "scale": 0.001670872171719869, "original_dtype": "float32"}}, {"name": "conv2d_26/kernel", "shape": [1, 1, 576, 96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.21014521495968688, "scale": 0.0017808916522007363, "original_dtype": "float32"}}, {"name": "conv2d_26/bias", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0035509329070063196, "scale": 2.9591107558385997e-05, "original_dtype": "float32"}}, {"name": "conv2d_27/kernel", "shape": [1, 1, 96, 576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.20938276601772682, "scale": 0.00157430651141148, "original_dtype": "float32"}}, {"name": "conv2d_28/kernel", "shape": [1, 1, 576, 160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.20634553157815747, "scale": 0.0016507642526252598, "original_dtype": "float32"}}, {"name": "conv2d_28/bias", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.003613398846823211, "scale": 3.062202412562043e-05, "original_dtype": "float32"}}, {"name": "conv2d_29/kernel", "shape": [1, 1, 160, 960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.19437438775511348, "scale": 0.0016902120674357694, "original_dtype": "float32"}}, {"name": "conv2d_3/kernel", "shape": [1, 1, 16, 96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.3276700917412253, "scale": 0.002663984485701019, "original_dtype": "float32"}}, {"name": "conv2d_30/kernel", "shape": [1, 1, 960, 160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.19974773421007044, "scale": 0.0015852994778577018, "original_dtype": "float32"}}, {"name": "conv2d_30/bias", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.003624629448441898, "scale": 3.0205245403682486e-05, "original_dtype": "float32"}}, {"name": "conv2d_31/kernel", "shape": [1, 1, 160, 960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.21468992052124994, "scale": 0.0016904718151279524, "original_dtype": "float32"}}, {"name": "conv2d_32/kernel", "shape": [1, 1, 960, 160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1987110930330613, "scale": 0.001577072166929058, "original_dtype": "float32"}}, {"name": "conv2d_32/bias", "shape": [160], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.003254141299711431, "scale": 2.78131735018071e-05, "original_dtype": "float32"}}, {"name": "conv2d_33/kernel", "shape": [1, 1, 160, 960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2291654993506039, "scale": 0.0017361022678076053, "original_dtype": "float32"}}, {"name": "conv2d_34/kernel", "shape": [1, 1, 960, 320], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.20284677270580742, "scale": 0.001733733100049636, "original_dtype": "float32"}}, {"name": "conv2d_34/bias", "shape": [320], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.003617672585681373, "scale": 2.4279681783096463e-05, "original_dtype": "float32"}}, {"name": "conv2d_35/kernel", "shape": [1, 1, 320, 1280], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.38750308401444383, "scale": 0.0025833538934296255, "original_dtype": "float32"}}, {"name": "conv2d_4/kernel", "shape": [1, 1, 96, 24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.30113972121593996, "scale": 0.0025520315357283048, "original_dtype": "float32"}}, {"name": "conv2d_4/bias", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.012759525285047642, "scale": 0.00011192566039515476, "original_dtype": "float32"}}, {"name": "conv2d_5/kernel", "shape": [1, 1, 24, 144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.30086157111560596, "scale": 0.002296653214622946, "original_dtype": "float32"}}, {"name": "conv2d_6/kernel", "shape": [1, 1, 144, 24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.3100184258292703, "scale": 0.0025411346379448385, "original_dtype": "float32"}}, {"name": "conv2d_6/bias", "shape": [24], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.012914199162932004, "scale": 0.0001076183263577667, "original_dtype": "float32"}}, {"name": "conv2d_7/kernel", "shape": [1, 1, 24, 144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.2788113463158701, "scale": 0.0021782136430927353, "original_dtype": "float32"}}, {"name": "conv2d_8/kernel", "shape": [1, 1, 144, 32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.28808488915948305, "scale": 0.0022863880092022465, "original_dtype": "float32"}}, {"name": "conv2d_8/bias", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.009217022644246326, "scale": 0.000118166956977517, "original_dtype": "float32"}}, {"name": "conv2d_9/kernel", "shape": [1, 1, 32, 192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.27523019220314776, "scale": 0.002053956658232446, "original_dtype": "float32"}}, {"name": "dense/kernel", "shape": [1280, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.07960405200719833, "scale": 0.0010613873600959778, "original_dtype": "float32"}}, {"name": "dense/bias", "shape": [1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": 0.10007622838020325, "scale": 1.0, "original_dtype": "float32"}}, {"name": "depthwise_conv2d/depthwise_kernel", "shape": [3, 3, 32, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.22045697470506032, "scale": 0.0018525796193702548, "original_dtype": "float32"}}, {"name": "depthwise_conv2d/bias", "shape": [32], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.05260428076281267, "scale": 0.0003955209079910727, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_1/depthwise_kernel", "shape": [3, 3, 96, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.18724133372306825, "scale": 0.0013568212588628134, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_1/bias", "shape": [96], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.04174532212463079, "scale": 0.00037272609039848924, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_10/depthwise_kernel", "shape": [3, 3, 384, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12599557129775776, "scale": 0.000961798254181357, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_10/bias", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.030550414170412456, "scale": 0.00022299572387162378, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_11/depthwise_kernel", "shape": [3, 3, 576, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11512673470903845, "scale": 0.0009514606174300699, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_11/bias", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.02959962288538615, "scale": 0.00021764428592195697, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_12/depthwise_kernel", "shape": [3, 3, 576, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12383942206700643, "scale": 0.001040667412327785, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_12/bias", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.022422170609820123, "scale": 0.00019001839499847563, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_13/depthwise_kernel", "shape": [3, 3, 576, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.1275325908380396, "scale": 0.0010121634193495208, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_13/bias", "shape": [576], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.026893250205937556, "scale": 0.00022411041838281295, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_14/depthwise_kernel", "shape": [3, 3, 960, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.17229470260003035, "scale": 0.001148631350666869, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_14/bias", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.03039640141468422, "scale": 0.0002713964412025377, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_15/depthwise_kernel", "shape": [3, 3, 960, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.18936720966708426, "scale": 0.0011339353872280495, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_15/bias", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.03457849244861042, "scale": 0.00027443247975087636, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_16/depthwise_kernel", "shape": [3, 3, 960, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.16098296011195465, "scale": 0.00099372197599972, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_16/bias", "shape": [960], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0718920731223097, "scale": 0.0005247596578270782, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_2/depthwise_kernel", "shape": [3, 3, 144, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15519814403618082, "scale": 0.0012030863878773708, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_2/bias", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.04160482936045703, "scale": 0.0003525832996648901, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_3/depthwise_kernel", "shape": [3, 3, 144, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.16775593424544616, "scale": 0.001261322813875535, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_3/bias", "shape": [144], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.04402594078405231, "scale": 0.00040023582530956643, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_4/depthwise_kernel", "shape": [3, 3, 192, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.15759858348790334, "scale": 0.0011098491794922772, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_4/bias", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.03755896284299738, "scale": 0.0003294645863420823, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_5/depthwise_kernel", "shape": [3, 3, 192, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.13389286714441637, "scale": 0.0010460380245657529, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_5/bias", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.03185094777567714, "scale": 0.00031535591857106076, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_6/depthwise_kernel", "shape": [3, 3, 192, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.14482068641513002, "scale": 0.0010198639888389439, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_6/bias", "shape": [192], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.04323281049728393, "scale": 0.00031788831248002894, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_7/depthwise_kernel", "shape": [3, 3, 384, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.11844857229905971, "scale": 0.0010211083818884457, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_7/bias", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.034505014165359386, "scale": 0.00028516540632528417, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_8/depthwise_kernel", "shape": [3, 3, 384, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.13352357143280555, "scale": 0.0010681885714624443, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_8/bias", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.03658276921978184, "scale": 0.00026318539007037296, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_9/depthwise_kernel", "shape": [3, 3, 384, 1], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.12185267326878566, "scale": 0.0010070468865188897, "original_dtype": "float32"}}, {"name": "depthwise_conv2d_9/bias", "shape": [384], "dtype": "float32", "quantization": {"dtype": "uint8", "min": -0.0274419559859762, "scale": 0.00022493406545882132, "original_dtype": "float32"}}]}]}
\ No newline at end of file
diff --git a/src/modals/EditMapModal.js b/src/modals/EditMapModal.js
index da46b01..e25d2ff 100644
--- a/src/modals/EditMapModal.js
+++ b/src/modals/EditMapModal.js
@@ -9,6 +9,7 @@ import MapDataContext from "../contexts/MapDataContext";
import { isEmpty } from "../helpers/shared";
import { getMapDefaultInset } from "../helpers/map";
+import useResponsiveLayout from "../helpers/useResponsiveLayout";
function EditMapModal({ isOpen, onDone, map, mapState }) {
const { updateMap, updateMapState } = useContext(MapDataContext);
@@ -98,11 +99,13 @@ function EditMapModal({ isOpen, onDone, map, mapState }) {
const [showMoreSettings, setShowMoreSettings] = useState(true);
+ const layout = useResponsiveLayout();
+
return (
diff --git a/src/modals/GettingStartedModal.js b/src/modals/GettingStartedModal.js
new file mode 100644
index 0000000..9a8e157
--- /dev/null
+++ b/src/modals/GettingStartedModal.js
@@ -0,0 +1,29 @@
+import React from "react";
+import { Box, Label, Text } from "theme-ui";
+import raw from "raw.macro";
+
+import Modal from "../components/Modal";
+import Markdown from "../components/Markdown";
+import Link from "../components/Link";
+
+const gettingStarted = raw("../docs/howTo/gettingStarted.md");
+
+function GettingStartedModal({ isOpen, onRequestClose }) {
+ return (
+
+
+
+
+
+ For more tutorials visit the How To page
+
+
+
+ );
+}
+
+export default GettingStartedModal;
diff --git a/src/modals/SelectMapModal.js b/src/modals/SelectMapModal.js
index 8f745f1..2236bc5 100644
--- a/src/modals/SelectMapModal.js
+++ b/src/modals/SelectMapModal.js
@@ -17,6 +17,8 @@ import useKeyboard from "../helpers/useKeyboard";
import { resizeImage } from "../helpers/image";
import { useSearch, useGroup, handleItemSelect } from "../helpers/select";
import { getMapDefaultInset, getGridSize, gridSizeVaild } from "../helpers/map";
+import useResponsiveLayout from "../helpers/useResponsiveLayout";
+import * as Vector2 from "../helpers/vector2";
import MapDataContext from "../contexts/MapDataContext";
import AuthContext from "../contexts/AuthContext";
@@ -30,10 +32,14 @@ const defaultMapProps = {
};
const mapResolutions = [
- { size: 512, quality: 0.5, id: "low" },
- { size: 1024, quality: 0.6, id: "medium" },
- { size: 2048, quality: 0.7, id: "high" },
- { size: 4096, quality: 0.8, id: "ultra" },
+ {
+ size: 30, // Pixels per grid
+ quality: 0.5, // JPEG compression quality
+ id: "low",
+ },
+ { size: 70, quality: 0.6, id: "medium" },
+ { size: 140, quality: 0.7, id: "high" },
+ { size: 300, quality: 0.8, id: "ultra" },
];
function SelectMapModal({
@@ -53,6 +59,7 @@ function SelectMapModal({
resetMap,
updateMap,
updateMaps,
+ mapsLoading,
} = useContext(MapDataContext);
/**
@@ -90,11 +97,18 @@ function SelectMapModal({
const [imageLoading, setImageLoading] = useState(false);
async function handleImagesUpload(files) {
+ if (navigator.storage) {
+ // Attempt to enable persistant storage
+ await navigator.storage.persist();
+ }
+
for (let file of files) {
await handleImageUpload(file);
}
// Set file input to null to allow adding the same image 2 times in a row
- fileInputRef.current.value = null;
+ if (fileInputRef.current) {
+ fileInputRef.current.value = null;
+ }
}
async function handleImageUpload(file) {
@@ -148,13 +162,24 @@ function SelectMapModal({
name = Case.capital(name);
}
+ if (!gridSize) {
+ gridSize = { x: 22, y: 22 };
+ }
+
// Create resolutions
const resolutions = {};
for (let resolution of mapResolutions) {
- if (Math.max(image.width, image.height) > resolution.size) {
+ const resolutionPixelSize = Vector2.multiply(
+ gridSize,
+ resolution.size
+ );
+ if (
+ image.width >= resolutionPixelSize.x &&
+ image.height >= resolutionPixelSize.y
+ ) {
const resized = await resizeImage(
image,
- resolution.size,
+ Vector2.max(resolutionPixelSize),
file.type,
resolution.quality
);
@@ -338,11 +363,13 @@ function SelectMapModal({
};
}, []);
+ const layout = useResponsiveLayout();
+
return (
- {imageLoading && }
+ {(imageLoading || mapsLoading) && }
setIsEditModalOpen(false)}
diff --git a/src/modals/SelectTokensModal.js b/src/modals/SelectTokensModal.js
index 8351654..7d92d55 100644
--- a/src/modals/SelectTokensModal.js
+++ b/src/modals/SelectTokensModal.js
@@ -10,19 +10,25 @@ import ConfirmModal from "./ConfirmModal";
import Modal from "../components/Modal";
import ImageDrop from "../components/ImageDrop";
import TokenTiles from "../components/token/TokenTiles";
+import LoadingOverlay from "../components/LoadingOverlay";
import blobToBuffer from "../helpers/blobToBuffer";
import useKeyboard from "../helpers/useKeyboard";
import { useSearch, useGroup, handleItemSelect } from "../helpers/select";
+import useResponsiveLayout from "../helpers/useResponsiveLayout";
import TokenDataContext from "../contexts/TokenDataContext";
import AuthContext from "../contexts/AuthContext";
function SelectTokensModal({ isOpen, onRequestClose }) {
const { userId } = useContext(AuthContext);
- const { ownedTokens, addToken, removeTokens, updateTokens } = useContext(
- TokenDataContext
- );
+ const {
+ ownedTokens,
+ addToken,
+ removeTokens,
+ updateTokens,
+ tokensLoading,
+ } = useContext(TokenDataContext);
/**
* Search
@@ -65,15 +71,22 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
}
async function handleImagesUpload(files) {
+ if (navigator.storage) {
+ // Attempt to enable persistant storage
+ await navigator.storage.persist();
+ }
+
for (let file of files) {
await handleImageUpload(file);
}
// Set file input to null to allow adding the same image 2 times in a row
- fileInputRef.current.value = null;
+ if (fileInputRef.current) {
+ fileInputRef.current.value = null;
+ }
}
async function handleImageUpload(file) {
- let name = "Unknown Map";
+ let name = "Unknown Token";
if (file.name) {
// Remove file extension
name = file.name.replace(/\.[^/.]+$/, "");
@@ -209,11 +222,13 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
};
}, []);
+ const layout = useResponsiveLayout();
+
return (
+ {tokensLoading && }
setIsEditModalOpen(false)}
diff --git a/src/modals/SettingsModal.js b/src/modals/SettingsModal.js
index d1441de..10af204 100644
--- a/src/modals/SettingsModal.js
+++ b/src/modals/SettingsModal.js
@@ -1,15 +1,17 @@
-import React, { useState, useContext } from "react";
+import React, { useState, useContext, useEffect } from "react";
import {
Label,
Flex,
Button,
useColorMode,
Checkbox,
- Slider,
Divider,
+ Text,
} from "theme-ui";
+import prettyBytes from "pretty-bytes";
import Modal from "../components/Modal";
+import Slider from "../components/Slider";
import AuthContext from "../contexts/AuthContext";
import DatabaseContext from "../contexts/DatabaseContext";
@@ -23,6 +25,26 @@ function SettingsModal({ isOpen, onRequestClose }) {
const { userId } = useContext(AuthContext);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [labelSize, setLabelSize] = useSetting("map.labelSize");
+ const [storageEstimate, setStorageEstimate] = useState();
+
+ useEffect(() => {
+ async function estimateStorage() {
+ // Persisted data on firefox doesn't count towards the usage quota so ignore it
+ const persisted = await navigator.storage.persisted();
+ const isFirefox =
+ navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
+ if (persisted && isFirefox) {
+ return;
+ }
+
+ const estimate = await navigator.storage.estimate();
+ setStorageEstimate(estimate);
+ }
+
+ if (isOpen && navigator.storage) {
+ estimateStorage();
+ }
+ }, [isOpen]);
async function handleEraseAllData() {
localStorage.clear();
@@ -86,6 +108,7 @@ function SettingsModal({ isOpen, onRequestClose }) {
sx={{ width: "initial" }}
value={labelSize}
onChange={(e) => setLabelSize(parseFloat(e.target.value))}
+ labelFunc={(value) => `${value}x`}
/>
@@ -102,6 +125,19 @@ function SettingsModal({ isOpen, onRequestClose }) {
Erase all content and reset
+ {storageEstimate && (
+
+
+ Storage Used: {prettyBytes(storageEstimate.usage)} of{" "}
+ {prettyBytes(storageEstimate.quota)} (
+ {Math.round(
+ (storageEstimate.usage / Math.max(storageEstimate.quota, 1)) *
+ 100
+ )}
+ %)
+
+
+ )}
-
+
MAX_BUFFER_SIZE) {
const chunks = this.chunk(packedData);
for (let chunk of chunks) {
if (this.dataChannels[channel]) {
- // Write to the stream to allow for buffer / backpressure handling
this.dataChannels[channel].write(encode(chunk));
} else {
- super.send(encode(chunk));
+ this.write(encode(chunk));
}
}
return;
} else {
if (this.dataChannels[channel]) {
- this.dataChannels[channel].send(packedData);
+ this.dataChannels[channel].write(packedData);
} else {
- super.send(packedData);
+ this.write(packedData);
}
}
} catch (error) {
diff --git a/src/network/NetworkedMapAndTokens.js b/src/network/NetworkedMapAndTokens.js
index 0640690..8d39b50 100644
--- a/src/network/NetworkedMapAndTokens.js
+++ b/src/network/NetworkedMapAndTokens.js
@@ -1,13 +1,15 @@
-import React, { useState, useContext, useEffect, useCallback } from "react";
+import React, { useState, useContext, useEffect } from "react";
import TokenDataContext from "../contexts/TokenDataContext";
import MapDataContext from "../contexts/MapDataContext";
import MapLoadingContext from "../contexts/MapLoadingContext";
import AuthContext from "../contexts/AuthContext";
import DatabaseContext from "../contexts/DatabaseContext";
+import PartyContext from "../contexts/PartyContext";
import { omit } from "../helpers/shared";
import useDebounce from "../helpers/useDebounce";
+import useNetworkedState from "../helpers/useNetworkedState";
// Load session for auto complete
// eslint-disable-next-line no-unused-vars
import Session from "./Session";
@@ -25,6 +27,7 @@ import Tokens from "../components/token/Tokens";
*/
function NetworkedMapAndTokens({ session }) {
const { userId } = useContext(AuthContext);
+ const partyState = useContext(PartyContext);
const {
assetLoadStart,
assetLoadFinish,
@@ -38,7 +41,117 @@ function NetworkedMapAndTokens({ session }) {
);
const [currentMap, setCurrentMap] = useState(null);
- const [currentMapState, setCurrentMapState] = useState(null);
+ const [currentMapState, setCurrentMapState] = useNetworkedState(
+ null,
+ session,
+ "map_state",
+ 100,
+ true,
+ "mapId"
+ );
+ const [assetManifest, setAssetManifest] = useNetworkedState(
+ [],
+ session,
+ "manifest",
+ 100,
+ false
+ );
+
+ function loadAssetManifestFromMap(map, mapState) {
+ const assets = [];
+ if (map.type === "file") {
+ const { id, lastModified, owner } = map;
+ assets.push({ type: "map", id, lastModified, owner });
+ }
+ let processedTokens = new Set();
+ for (let tokenState of Object.values(mapState.tokens)) {
+ const token = getToken(tokenState.tokenId);
+ if (
+ token &&
+ token.type === "file" &&
+ !processedTokens.has(tokenState.tokenId)
+ ) {
+ processedTokens.add(tokenState.tokenId);
+ // Omit file from token peer will request file if needed
+ const { id, lastModified, owner } = token;
+ assets.push({ type: "token", id, lastModified, owner });
+ }
+ }
+ setAssetManifest(assets);
+ }
+
+ function compareAssets(a, b) {
+ return a.type === b.type && a.id === b.id;
+ }
+
+ // Return true if an asset is out of date
+ function assetNeedsUpdate(oldAsset, newAsset) {
+ return (
+ compareAssets(oldAsset, newAsset) &&
+ oldAsset.lastModified > newAsset.lastModified
+ );
+ }
+
+ function addAssetIfNeeded(asset) {
+ // Asset needs updating
+ const exists = assetManifest.some((oldAsset) =>
+ compareAssets(oldAsset, asset)
+ );
+ const needsUpdate = assetManifest.some((oldAsset) =>
+ assetNeedsUpdate(oldAsset, asset)
+ );
+ if (!exists || needsUpdate) {
+ setAssetManifest((prevAssets) => [
+ ...prevAssets.filter((prevAsset) => !compareAssets(prevAsset, asset)),
+ asset,
+ ]);
+ }
+ }
+
+ useEffect(() => {
+ if (!assetManifest) {
+ return;
+ }
+
+ async function requestAssetsIfNeeded() {
+ for (let asset of assetManifest) {
+ if (asset.owner === userId) {
+ continue;
+ }
+
+ const owner = Object.values(partyState).find(
+ (player) => player.userId === asset.owner
+ );
+ if (!owner) {
+ continue;
+ }
+
+ if (asset.type === "map") {
+ const cachedMap = await getMapFromDB(asset.id);
+ if (cachedMap && cachedMap.lastModified >= asset.lastModified) {
+ // Update last used for cache invalidation
+ const lastUsed = Date.now();
+ await updateMap(cachedMap.id, { lastUsed });
+ setCurrentMap({ ...cachedMap, lastUsed });
+ } else {
+ session.sendTo(owner.sessionId, "mapRequest", asset.id);
+ }
+ } else if (asset.type === "token") {
+ const cachedToken = getToken(asset.id);
+ if (cachedToken && cachedToken.lastModified >= asset.lastModified) {
+ // Update last used for cache invalidation
+ const lastUsed = Date.now();
+ await updateToken(cachedToken.id, { lastUsed });
+ } else {
+ session.sendTo(owner.sessionId, "tokenRequest", asset.id);
+ }
+ }
+ }
+ }
+
+ requestAssetsIfNeeded();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [assetManifest, partyState, session]);
/**
* Map state
@@ -61,36 +174,29 @@ function NetworkedMapAndTokens({ session }) {
}, [currentMap, debouncedMapState, userId, database]);
function handleMapChange(newMap, newMapState) {
- setCurrentMapState(newMapState);
+ // Clear map before sending new one
+ setCurrentMap(null);
+ session.socket?.emit("map", null);
+
+ setCurrentMapState(newMapState, true, true);
setCurrentMap(newMap);
- session.send("map", null, "map");
+
+ if (newMap && newMap.type === "file") {
+ const { file, resolutions, ...rest } = newMap;
+ session.socket?.emit("map", rest);
+ } else {
+ session.socket?.emit("map", newMap);
+ }
if (!newMap || !newMapState) {
return;
}
- session.send("mapState", newMapState);
- session.send("map", getMapDataToSend(newMap), "map");
- const tokensToSend = getMapTokensToSend(newMapState);
- for (let token of tokensToSend) {
- session.send("token", token, "token");
- }
- }
-
- function getMapDataToSend(mapData) {
- // Omit file from map change, receiver will request the file if
- // they have an outdated version
- if (mapData.type === "file") {
- const { file, resolutions, ...rest } = mapData;
- return rest;
- } else {
- return mapData;
- }
+ loadAssetManifestFromMap(newMap, newMapState);
}
function handleMapStateChange(newMapState) {
- setCurrentMapState(newMapState);
- session.send("mapState", newMapState);
+ setCurrentMapState(newMapState, true, true);
}
function addMapDrawActions(actions, indexKey, actionsKey) {
@@ -123,83 +229,55 @@ function NetworkedMapAndTokens({ session }) {
function handleMapDraw(action) {
addMapDrawActions([action], "mapDrawActionIndex", "mapDrawActions");
- session.send("mapDraw", [action]);
}
function handleMapDrawUndo() {
- const index = updateDrawActionIndex(
- -1,
- "mapDrawActionIndex",
- "mapDrawActions"
- );
- session.send("mapDrawIndex", index);
+ updateDrawActionIndex(-1, "mapDrawActionIndex", "mapDrawActions");
}
function handleMapDrawRedo() {
- const index = updateDrawActionIndex(
- 1,
- "mapDrawActionIndex",
- "mapDrawActions"
- );
- session.send("mapDrawIndex", index);
+ updateDrawActionIndex(1, "mapDrawActionIndex", "mapDrawActions");
}
function handleFogDraw(action) {
addMapDrawActions([action], "fogDrawActionIndex", "fogDrawActions");
- session.send("mapFog", [action]);
}
function handleFogDrawUndo() {
- const index = updateDrawActionIndex(
- -1,
- "fogDrawActionIndex",
- "fogDrawActions"
- );
- session.send("mapFogIndex", index);
+ updateDrawActionIndex(-1, "fogDrawActionIndex", "fogDrawActions");
}
function handleFogDrawRedo() {
- const index = updateDrawActionIndex(
- 1,
- "fogDrawActionIndex",
- "fogDrawActions"
- );
- session.send("mapFogIndex", index);
+ updateDrawActionIndex(1, "fogDrawActionIndex", "fogDrawActions");
+ }
+
+ function handleNoteChange(note) {
+ setCurrentMapState((prevMapState) => ({
+ ...prevMapState,
+ notes: {
+ ...prevMapState.notes,
+ [note.id]: note,
+ },
+ }));
+ }
+
+ function handleNoteRemove(noteId) {
+ setCurrentMapState((prevMapState) => ({
+ ...prevMapState,
+ notes: omit(prevMapState.notes, [noteId]),
+ }));
}
/**
* Token state
*/
- // Get all tokens from a token state
- const getMapTokensToSend = useCallback(
- (state) => {
- let sentTokens = {};
- const tokens = [];
- for (let tokenState of Object.values(state.tokens)) {
- const token = getToken(tokenState.tokenId);
- if (
- token &&
- token.type === "file" &&
- !(tokenState.tokenId in sentTokens)
- ) {
- sentTokens[tokenState.tokenId] = true;
- // Omit file from token peer will request file if needed
- const { file, ...rest } = token;
- tokens.push(rest);
- }
- }
- return tokens;
- },
- [getToken]
- );
-
async function handleMapTokenStateCreate(tokenState) {
// If file type token send the token to the other peers
const token = getToken(tokenState.tokenId);
if (token && token.type === "file") {
- const { file, ...rest } = token;
- session.send("token", rest);
+ const { id, lastModified, owner } = token;
+ addAssetIfNeeded({ type: "token", id, lastModified, owner });
}
handleMapTokenStateChange({ [tokenState.id]: tokenState });
}
@@ -215,7 +293,6 @@ function NetworkedMapAndTokens({ session }) {
...change,
},
}));
- session.send("tokenStateEdit", change);
}
function handleMapTokenStateRemove(tokenState) {
@@ -223,137 +300,81 @@ function NetworkedMapAndTokens({ session }) {
const { [tokenState.id]: old, ...rest } = prevMapState.tokens;
return { ...prevMapState, tokens: rest };
});
- session.send("tokenStateRemove", { [tokenState.id]: tokenState });
}
useEffect(() => {
async function handlePeerData({ id, data, reply }) {
- if (id === "sync") {
- if (currentMapState) {
- reply("mapState", currentMapState);
- const tokensToSend = getMapTokensToSend(currentMapState);
- for (let token of tokensToSend) {
- reply("token", token, "token");
- }
- }
- if (currentMap) {
- reply("map", getMapDataToSend(currentMap), "map");
- }
- }
- if (id === "map") {
- const newMap = data;
- if (newMap && newMap.type === "file") {
- const cachedMap = await getMapFromDB(newMap.id);
- if (cachedMap && cachedMap.lastModified >= newMap.lastModified) {
- // Update last used for cache invalidation
- const lastUsed = Date.now();
- await updateMap(cachedMap.id, { lastUsed });
- setCurrentMap({ ...cachedMap, lastUsed });
- } else {
- // Save map data but remove last modified so if there is an error
- // during the map request the cache is invalid. Also add last used
- // for cache invalidation
- await putMap({ ...newMap, lastModified: 0, lastUsed: Date.now() });
- reply("mapRequest", newMap.id, "map");
- }
- } else {
- setCurrentMap(newMap);
- }
- }
if (id === "mapRequest") {
const map = await getMapFromDB(data);
- function replyWithPreview(preview) {
+ function replyWithMap(preview, resolution) {
+ let response = {
+ ...map,
+ resolutions: undefined,
+ file: undefined,
+ // Remove last modified so if there is an error
+ // during the map request the cache is invalid
+ lastModified: 0,
+ // Add last used for cache invalidation
+ lastUsed: Date.now(),
+ };
+ // Send preview if available
if (map.resolutions[preview]) {
- reply(
- "mapResponse",
- {
- id: map.id,
- resolutions: { [preview]: map.resolutions[preview] },
- },
- "map"
- );
+ response.resolutions = { [preview]: map.resolutions[preview] };
+ reply("mapResponse", response, "map");
}
- }
-
- function replyWithFile(resolution) {
- let file;
- // If the resolution exists send that
+ // Send full map at the desired resolution if available
if (map.resolutions[resolution]) {
- file = map.resolutions[resolution].file;
+ response.file = map.resolutions[resolution].file;
} else if (map.file) {
// The resolution might not exist for other users so send the file instead
- file = map.file;
+ response.file = map.file;
} else {
return;
}
- reply(
- "mapResponse",
- {
- id: map.id,
- file,
- // Add last modified back to file to set cache as valid
- lastModified: map.lastModified,
- },
- "map"
- );
+ // Add last modified back to file to set cache as valid
+ response.lastModified = map.lastModified;
+ reply("mapResponse", response, "map");
}
switch (map.quality) {
case "low":
- replyWithFile("low");
+ replyWithMap(undefined, "low");
break;
case "medium":
- replyWithPreview("low");
- replyWithFile("medium");
+ replyWithMap("low", "medium");
break;
case "high":
- replyWithPreview("medium");
- replyWithFile("high");
+ replyWithMap("medium", "high");
break;
case "ultra":
- replyWithPreview("medium");
- replyWithFile("ultra");
+ replyWithMap("medium", "ultra");
break;
case "original":
if (map.resolutions) {
if (map.resolutions.medium) {
- replyWithPreview("medium");
+ replyWithMap("medium");
} else if (map.resolutions.low) {
- replyWithPreview("low");
+ replyWithMap("low");
+ } else {
+ replyWithMap();
}
+ } else {
+ replyWithMap();
}
- replyWithFile();
break;
default:
- replyWithFile();
+ replyWithMap();
}
}
+
if (id === "mapResponse") {
- const { id, ...update } = data;
- await updateMap(id, update);
- const updatedMap = await getMapFromDB(data.id);
- setCurrentMap(updatedMap);
- }
- if (id === "mapState") {
- setCurrentMapState(data);
- }
- if (id === "token") {
- const newToken = data;
- if (newToken && newToken.type === "file") {
- const cachedToken = getToken(newToken.id);
- if (
- cachedToken &&
- cachedToken.lastModified >= newToken.lastModified
- ) {
- // Update last used for cache invalidation
- const lastUsed = Date.now();
- await updateToken(cachedToken.id, { lastUsed });
- } else {
- reply("tokenRequest", newToken.id, "token");
- }
- }
+ const newMap = data;
+ setCurrentMap(newMap);
+ await putMap(newMap);
+ assetLoadFinish();
}
+
if (id === "tokenRequest") {
const token = getToken(data);
// Add a last used property for cache invalidation
@@ -361,58 +382,41 @@ function NetworkedMapAndTokens({ session }) {
}
if (id === "tokenResponse") {
const newToken = data;
- if (newToken && newToken.type === "file") {
- putToken(newToken);
- }
- }
- if (id === "tokenStateEdit" && currentMapState) {
- setCurrentMapState((prevMapState) => ({
- ...prevMapState,
- tokens: { ...prevMapState.tokens, ...data },
- }));
- }
- if (id === "tokenStateRemove" && currentMapState) {
- setCurrentMapState((prevMapState) => ({
- ...prevMapState,
- tokens: omit(prevMapState.tokens, Object.keys(data)),
- }));
- }
- if (id === "mapDraw" && currentMapState) {
- addMapDrawActions(data, "mapDrawActionIndex", "mapDrawActions");
- }
- if (id === "mapDrawIndex" && currentMapState) {
- setCurrentMapState((prevMapState) => ({
- ...prevMapState,
- mapDrawActionIndex: data,
- }));
- }
- if (id === "mapFog" && currentMapState) {
- addMapDrawActions(data, "fogDrawActionIndex", "fogDrawActions");
- }
- if (id === "mapFogIndex" && currentMapState) {
- setCurrentMapState((prevMapState) => ({
- ...prevMapState,
- fogDrawActionIndex: data,
- }));
+ await putToken(newToken);
+ assetLoadFinish();
}
}
function handlePeerDataProgress({ id, total, count }) {
if (count === 1) {
+ // Corresponding asset load finished called in token and map response
assetLoadStart();
}
- if (total === count) {
- assetLoadFinish();
- }
assetProgressUpdate({ id, total, count });
}
- session.on("data", handlePeerData);
- session.on("dataProgress", handlePeerDataProgress);
+ async function handleSocketMap(map) {
+ if (map) {
+ // If we're the owner get the full map from the database
+ if (map.type === "file" && map.owner === userId) {
+ const fullMap = await getMapFromDB(map.id);
+ setCurrentMap(fullMap);
+ } else {
+ setCurrentMap(map);
+ }
+ } else {
+ setCurrentMap(null);
+ }
+ }
+
+ session.on("peerData", handlePeerData);
+ session.on("peerDataProgress", handlePeerDataProgress);
+ session.socket?.on("map", handleSocketMap);
return () => {
- session.off("data", handlePeerData);
- session.off("dataProgress", handlePeerDataProgress);
+ session.off("peerData", handlePeerData);
+ session.off("peerDataProgress", handlePeerDataProgress);
+ session.socket?.off("map", handleSocketMap);
};
});
@@ -429,6 +433,12 @@ function NetworkedMapAndTokens({ session }) {
currentMapState !== null &&
(currentMapState.editFlags.includes("fog") || currentMap.owner === userId);
+ const canEditNotes =
+ currentMap !== null &&
+ currentMapState !== null &&
+ (currentMapState.editFlags.includes("notes") ||
+ currentMap.owner === userId);
+
const disabledMapTokens = {};
// If we have a map and state and have the token permission disabled
// and are not the map owner
@@ -460,9 +470,12 @@ function NetworkedMapAndTokens({ session }) {
onFogDraw={handleFogDraw}
onFogDrawUndo={handleFogDrawUndo}
onFogDrawRedo={handleFogDrawRedo}
+ onMapNoteChange={handleNoteChange}
+ onMapNoteRemove={handleNoteRemove}
allowMapDrawing={canEditMapDrawing}
allowFogDrawing={canEditFogDrawing}
allowMapChange={canChangeMap}
+ allowNoteEditing={canEditNotes}
disabledTokens={disabledMapTokens}
session={session}
/>
diff --git a/src/network/NetworkedMapPointer.js b/src/network/NetworkedMapPointer.js
index 5cce3e4..aab6b32 100644
--- a/src/network/NetworkedMapPointer.js
+++ b/src/network/NetworkedMapPointer.js
@@ -5,27 +5,28 @@ import AuthContext from "../contexts/AuthContext";
import MapPointer from "../components/map/MapPointer";
import { isEmpty } from "../helpers/shared";
-import { lerp } from "../helpers/vector2";
+import { lerp, compare } from "../helpers/vector2";
-// Send pointer updates every 33ms
-const sendTickRate = 33;
+// Send pointer updates every 50ms (20fps)
+const sendTickRate = 50;
function NetworkedMapPointer({ session, active, gridSize }) {
const { userId } = useContext(AuthContext);
- const [pointerState, setPointerState] = useState({});
- useEffect(() => {
- if (userId && !(userId in pointerState)) {
- setPointerState({
- [userId]: { position: { x: 0, y: 0 }, visible: false, id: userId },
- });
- }
- }, [userId, pointerState]);
+ const [localPointerState, setLocalPointerState] = useState({});
const sessionRef = useRef(session);
useEffect(() => {
sessionRef.current = session;
}, [session]);
+ useEffect(() => {
+ if (userId && !(userId in localPointerState)) {
+ setLocalPointerState({
+ [userId]: { position: { x: 0, y: 0 }, visible: false, id: userId },
+ });
+ }
+ }, [userId, localPointerState]);
+
// Send pointer updates every sendTickRate to peers to save on bandwidth
// We use requestAnimationFrame as setInterval was being blocked during
// re-renders on Chrome with Windows
@@ -42,8 +43,15 @@ function NetworkedMapPointer({ session, active, gridSize }) {
if (counter > sendTickRate) {
counter -= sendTickRate;
- if (ownPointerUpdateRef.current && sessionRef.current) {
- sessionRef.current.send("pointer", ownPointerUpdateRef.current);
+ if (
+ ownPointerUpdateRef.current &&
+ sessionRef.current &&
+ sessionRef.current.socket
+ ) {
+ sessionRef.current.socket.emit(
+ "player_pointer",
+ ownPointerUpdateRef.current
+ );
ownPointerUpdateRef.current = null;
}
}
@@ -55,7 +63,7 @@ function NetworkedMapPointer({ session, active, gridSize }) {
}, []);
function updateOwnPointerState(position, visible) {
- setPointerState((prev) => ({
+ setLocalPointerState((prev) => ({
...prev,
[userId]: { position, visible, id: userId },
}));
@@ -75,77 +83,78 @@ function NetworkedMapPointer({ session, active, gridSize }) {
}
// Handle pointer data receive
- const syncedPointerStateRef = useRef({});
+ const interpolationsRef = useRef({});
useEffect(() => {
- function handlePeerData({ id, data }) {
- if (id === "pointer") {
- // Setup an interpolation to the current pointer data when receiving a pointer event
- if (syncedPointerStateRef.current[data.id]) {
- const from = syncedPointerStateRef.current[data.id].to;
- syncedPointerStateRef.current[data.id] = {
- id: data.id,
- from: {
- ...from,
- time: performance.now(),
- },
- to: {
- ...data,
- time: performance.now() + sendTickRate,
- },
- };
- } else {
- syncedPointerStateRef.current[data.id] = {
- from: null,
- to: { ...data, time: performance.now() + sendTickRate },
- };
- }
+ // TODO: Handle player disconnect while pointer visible
+ function handleSocketPlayerPointer(pointer) {
+ const interpolations = interpolationsRef.current;
+ const id = pointer.id;
+ if (!(id in interpolations)) {
+ interpolations[id] = {
+ id,
+ from: null,
+ to: { ...pointer, time: performance.now() + sendTickRate },
+ };
+ } else if (
+ !compare(interpolations[id].to.position, pointer.position, 0.0001) ||
+ interpolations[id].to.visible !== pointer.visible
+ ) {
+ const from = interpolations[id].to;
+ interpolations[id] = {
+ id,
+ from: {
+ ...from,
+ time: performance.now(),
+ },
+ to: {
+ ...pointer,
+ time: performance.now() + sendTickRate,
+ },
+ };
}
}
- session.on("data", handlePeerData);
+ session.socket?.on("player_pointer", handleSocketPlayerPointer);
return () => {
- session.off("data", handlePeerData);
+ session.socket?.off("player_pointer", handleSocketPlayerPointer);
};
- });
+ }, [session]);
// Animate to the peer pointer positions
useEffect(() => {
let request = requestAnimationFrame(animate);
- function animate(time) {
+ function animate() {
request = requestAnimationFrame(animate);
+ const time = performance.now();
let interpolatedPointerState = {};
- for (let syncState of Object.values(syncedPointerStateRef.current)) {
- if (!syncState.from || !syncState.to) {
+ for (let interp of Object.values(interpolationsRef.current)) {
+ if (!interp.from || !interp.to) {
continue;
}
- const totalInterpTime = syncState.to.time - syncState.from.time;
- const currentInterpTime = time - syncState.from.time;
+ const totalInterpTime = interp.to.time - interp.from.time;
+ const currentInterpTime = time - interp.from.time;
const alpha = currentInterpTime / totalInterpTime;
if (alpha >= 0 && alpha <= 1) {
- interpolatedPointerState[syncState.id] = {
- id: syncState.to.id,
- visible: syncState.from.visible,
- position: lerp(
- syncState.from.position,
- syncState.to.position,
- alpha
- ),
+ interpolatedPointerState[interp.id] = {
+ id: interp.id,
+ visible: interp.from.visible,
+ position: lerp(interp.from.position, interp.to.position, alpha),
};
}
- if (alpha > 1 && !syncState.to.visible) {
- interpolatedPointerState[syncState.id] = {
- id: syncState.id,
- visible: syncState.to.visible,
- position: syncState.to.position,
+ if (alpha > 1 && !interp.to.visible) {
+ interpolatedPointerState[interp.id] = {
+ id: interp.id,
+ visible: interp.to.visible,
+ position: interp.to.position,
};
- delete syncedPointerStateRef.current[syncState.to.id];
+ delete interpolationsRef.current[interp.id];
}
}
if (!isEmpty(interpolatedPointerState)) {
- setPointerState((prev) => ({
+ setLocalPointerState((prev) => ({
...prev,
...interpolatedPointerState,
}));
@@ -159,7 +168,7 @@ function NetworkedMapPointer({ session, active, gridSize }) {
return (
- {Object.values(pointerState).map((pointer) => (
+ {Object.values(localPointerState).map((pointer) => (
{
- let prevTime = performance.now();
- let request = requestAnimationFrame(update);
- let counter = 0;
- function update(time) {
- request = requestAnimationFrame(update);
- const deltaTime = time - prevTime;
- prevTime = time;
-
- if (timer) {
- counter += deltaTime;
- // Update timer every second
- if (counter > 1000) {
- const newTimer = {
- ...timer,
- current: timer.current - counter,
- };
- if (newTimer.current < 0) {
- setTimer(null);
- session.send("timer", { [session.id]: null });
- } else {
- setTimer(newTimer);
- session.send("timer", { [session.id]: newTimer });
- }
- counter = 0;
+ if (joinedPlayersRef.current.length > 0) {
+ for (let id of joinedPlayersRef.current) {
+ if (partyState[id]) {
+ addToast(`${partyState[id].nickname} joined the party`);
}
}
+ joinedPlayersRef.current = [];
}
- return () => {
- cancelAnimationFrame(request);
- };
- }, [timer, session]);
-
- function handleDiceRollsChange(newDiceRolls) {
- setDiceRolls(newDiceRolls);
- if (shareDice) {
- session.send("dice", { [session.id]: newDiceRolls });
- }
- }
-
- function handleShareDiceChange(newShareDice) {
- setShareDice(newShareDice);
- if (newShareDice) {
- session.send("dice", { [session.id]: diceRolls });
- } else {
- session.send("dice", { [session.id]: null });
- }
- }
+ }, [partyState, addToast]);
useEffect(() => {
- function handlePeerConnect({ peer, reply }) {
- reply("nickname", { [session.id]: nickname });
+ function handlePlayerJoined(sessionId) {
if (stream) {
- peer.connection.addStream(stream);
- }
- if (timer) {
- reply("timer", { [session.id]: timer });
- }
- if (shareDice) {
- reply("dice", { [session.id]: diceRolls });
- }
- }
-
- function handlePeerDisconnect({ peer }) {
- if (partyNicknames[peer.id]) {
- addToast(`${partyNicknames[peer.id]} left the party`);
- }
- setPartyNicknames((prevNicknames) => omit(prevNicknames, [peer.id]));
- setPartyTimers((prevTimers) => omit(prevTimers, [peer.id]));
- }
-
- function handlePeerData({ id, data, peer }) {
- if (id === "nickname") {
- if (!peer.initiator) {
- for (let peerId in data) {
- if (!(peerId in partyNicknames)) {
- addToast(`${data[peerId]} joined the party`);
- }
+ const tracks = stream.getTracks();
+ for (let track of tracks) {
+ if (track.kind === "audio") {
+ session.startStreamTo(sessionId, track, stream);
}
}
- setPartyNicknames((prevNicknames) => ({
- ...prevNicknames,
- ...data,
- }));
}
- if (id === "timer") {
- setPartyTimers((prevTimers) => {
- const newTimers = { ...prevTimers, ...data };
- // filter out timers that are null
- const filtered = Object.entries(newTimers).filter(
- ([, value]) => value !== null
- );
- return fromEntries(filtered);
- });
- }
- if (id === "dice") {
- setPartyDiceRolls((prevDiceRolls) => {
- const newRolls = { ...prevDiceRolls, ...data };
- // filter out dice rolls that are null
- const filtered = Object.entries(newRolls).filter(
- ([, value]) => value !== null
- );
- return fromEntries(filtered);
- });
+ // Add player to join notification list
+ // Can't just show the notification here as the partyState data isn't populated at this point
+ joinedPlayersRef.current.push(sessionId);
+ }
+
+ function handlePlayerLeft(sessionId) {
+ if (partyState[sessionId]) {
+ addToast(`${partyState[sessionId].nickname} left the party`);
}
}
@@ -204,29 +114,18 @@ function NetworkedParty({ gameId, session }) {
}
}
- session.on("connect", handlePeerConnect);
- session.on("disconnect", handlePeerDisconnect);
- session.on("data", handlePeerData);
- session.on("trackAdded", handlePeerTrackAdded);
- session.on("trackRemoved", handlePeerTrackRemoved);
+ session.on("playerJoined", handlePlayerJoined);
+ session.on("playerLeft", handlePlayerLeft);
+ session.on("peerTrackAdded", handlePeerTrackAdded);
+ session.on("peerTrackRemoved", handlePeerTrackRemoved);
return () => {
- session.off("connect", handlePeerConnect);
- session.off("disconnect", handlePeerDisconnect);
- session.off("data", handlePeerData);
- session.off("trackAdded", handlePeerTrackAdded);
- session.off("trackRemoved", handlePeerTrackRemoved);
+ session.off("playerJoined", handlePlayerJoined);
+ session.off("playerLeft", handlePlayerLeft);
+ session.off("peerTrackAdded", handlePeerTrackAdded);
+ session.off("peerTrackRemoved", handlePeerTrackRemoved);
};
- }, [
- session,
- nickname,
- stream,
- timer,
- shareDice,
- diceRolls,
- partyNicknames,
- addToast,
- ]);
+ });
useEffect(() => {
if (stream) {
@@ -248,22 +147,10 @@ function NetworkedParty({ gameId, session }) {
<>
>
);
diff --git a/src/network/Session.js b/src/network/Session.js
index d87f8dd..e38ea6e 100644
--- a/src/network/Session.js
+++ b/src/network/Session.js
@@ -11,24 +11,32 @@ import { logError } from "../helpers/logging";
* @property {string} id - The socket id of the peer
* @property {Connection} connection - The actual peer connection
* @property {boolean} initiator - Is this peer the initiator of the connection
- * @property {boolean} sync - Should this connection sync other connections
+ * @property {boolean} ready - Ready for data to be sent
+ */
+
+/**
+ * @callback peerReply
+ * @param {string} id - The id of the event
+ * @param {object} data - The data to send
+ * @param {string} channel - The channel to send to
*/
/**
*
* Handles connections to multiple peers
*
- * Events:
- * - connect: A party member has connected
- * - data
- * - trackAdded
- * - trackRemoved
- * - disconnect: A party member has disconnected
- * - error
- * - authenticationSuccess
- * - authenticationError
- * - connected: You have connected
- * - disconnected: You have disconnected
+ * @fires Session#peerConnect
+ * @fires Session#peerData
+ * @fires Session#peerTrackAdded
+ * @fires Session#peerTrackRemoved
+ * @fires Session#peerDisconnect
+ * @fires Session#peerError
+ * @fires Session#authenticationSuccess
+ * @fires Session#authenticationError
+ * @fires Session#connected
+ * @fires Session#disconnected
+ * @fires Session#playerJoined
+ * @fires Session#playerLeft
*/
class Session extends EventEmitter {
/**
@@ -45,95 +53,160 @@ class Session extends EventEmitter {
*/
peers;
+ /**
+ * The state of the session
+ *
+ * @type {('unknown'|'online'|'offline')}
+ */
+ state;
+
get id() {
- return this.socket.id;
+ return this.socket && this.socket.id;
}
_iceServers;
// Store party id and password for reconnect
- _partyId;
+ _gameId;
_password;
constructor() {
super();
- this.socket = io(process.env.REACT_APP_BROKER_URL);
-
- this.socket.on(
- "party member joined",
- this._handlePartyMemberJoined.bind(this)
- );
- this.socket.on("party member left", this._handlePartyMemberLeft.bind(this));
- this.socket.on("joined party", this._handleJoinedParty.bind(this));
- this.socket.on("signal", this._handleSignal.bind(this));
- this.socket.on("auth error", this._handleAuthError.bind(this));
- this.socket.on("disconnect", this._handleSocketDisconnect.bind(this));
- this.socket.on("reconnect", this._handleSocketReconnect.bind(this));
-
this.peers = {};
-
+ this.state = "unknown";
// Signal connected peers of a closure on refresh
window.addEventListener("beforeunload", this._handleUnload.bind(this));
}
/**
- * Send data to all connected peers
+ * Connect to the websocket
+ */
+ async connect() {
+ try {
+ const response = await fetch(process.env.REACT_APP_ICE_SERVERS_URL);
+ if (!response.ok) {
+ throw Error("Unable to fetch ICE servers");
+ }
+ const data = await response.json();
+ this._iceServers = data.iceServers;
+
+ this.socket = io(process.env.REACT_APP_BROKER_URL, {
+ withCredentials: true,
+ });
+
+ this.socket.on("player_joined", this._handlePlayerJoined.bind(this));
+ this.socket.on("player_left", this._handlePlayerLeft.bind(this));
+ this.socket.on("joined_game", this._handleJoinedGame.bind(this));
+ this.socket.on("signal", this._handleSignal.bind(this));
+ this.socket.on("auth_error", this._handleAuthError.bind(this));
+ this.socket.on("disconnect", this._handleSocketDisconnect.bind(this));
+ this.socket.io.on("reconnect", this._handleSocketReconnect.bind(this));
+
+ this.state = "online";
+ } catch (error) {
+ logError(error);
+ this.state = "offline";
+ }
+ }
+
+ /**
+ * Send data to a single peer
*
- * @param {string} id - the id of the event to send
+ * @param {string} sessionId - The socket id of the player to send to
+ * @param {string} eventId - The id of the event to send
* @param {object} data
* @param {string} channel
*/
- send(id, data, channel) {
- for (let peer of Object.values(this.peers)) {
- peer.connection.send({ id, data }, channel);
+ sendTo(sessionId, eventId, data, channel) {
+ if (!(sessionId in this.peers)) {
+ this._addPeer(sessionId, true);
+ }
+
+ if (!this.peers[sessionId].ready) {
+ this.peers[sessionId].connection.once("connect", () => {
+ this.peers[sessionId].connection.sendObject(
+ { id: eventId, data },
+ channel
+ );
+ });
+ } else {
+ this.peers[sessionId].connection.sendObject(
+ { id: eventId, data },
+ channel
+ );
+ }
+ }
+
+ /**
+ * Start streaming to a peer
+ *
+ * @param {string} sessionId - The socket id of the player to stream to
+ * @param {MediaStreamTrack} track
+ * @param {MediaStream} stream
+ */
+ startStreamTo(sessionId, track, stream) {
+ if (!(sessionId in this.peers)) {
+ this._addPeer(sessionId, true);
+ }
+
+ if (!this.peers[sessionId].ready) {
+ this.peers[sessionId].connection.once("connect", () => {
+ this.peers[sessionId].connection.addTrack(track, stream);
+ });
+ } else {
+ this.peers[sessionId].connection.addTrack(track, stream);
+ }
+ }
+
+ /**
+ * End streaming to a peer
+ *
+ * @param {string} sessionId - The socket id of the player to stream to
+ * @param {MediaStreamTrack} track
+ * @param {MediaStream} stream
+ */
+ endStreamTo(sessionId, track, stream) {
+ if (sessionId in this.peers) {
+ this.peers[sessionId].connection.removeTrack(track, stream);
}
}
/**
* Join a party
*
- * @param {string} partyId - the id of the party to join
+ * @param {string} gameId - the id of the party to join
* @param {string} password - the password of the party
*/
- async joinParty(partyId, password) {
- if (typeof partyId !== "string" || typeof password !== "string") {
+ async joinGame(gameId, password) {
+ if (typeof gameId !== "string" || typeof password !== "string") {
console.error(
- "Unable to join party: invalid party ID or password",
- partyId,
+ "Unable to join game: invalid game ID or password",
+ gameId,
password
);
- this.emit("disconnected");
return;
}
- this._partyId = partyId;
+ this._gameId = gameId;
this._password = password;
- try {
- const response = await fetch(process.env.REACT_APP_ICE_SERVERS_URL);
- const data = await response.json();
- this._iceServers = data.iceServers;
- this.socket.emit("join party", partyId, password);
- } catch (error) {
- logError(error);
- this.emit("disconnected");
- }
+ this.socket.emit("join_game", gameId, password);
}
- _addPeer(id, initiator, sync) {
+ _addPeer(id, initiator) {
try {
const connection = new Connection({
initiator,
trickle: true,
config: { iceServers: this._iceServers },
});
- if (initiator) {
- connection.createDataChannel("map", { iceServers: this._iceServers });
- connection.createDataChannel("token", { iceServers: this._iceServers });
- }
- const peer = { id, connection, initiator, sync };
+
+ // Up max listeners to 100 to account for up to 100 tokens on load
+ connection.setMaxListeners && connection.setMaxListeners(100);
+
+ const peer = { id, connection, initiator, ready: false };
function sendPeer(id, data, channel) {
- peer.connection.send({ id, data }, channel);
+ peer.connection.sendObject({ id, data }, channel);
}
function handleSignal(signal) {
@@ -141,18 +214,32 @@ class Session extends EventEmitter {
}
function handleConnect() {
- this.emit("connect", { peer, reply: sendPeer });
- if (peer.sync) {
- peer.connection.send({ id: "sync" });
+ if (peer.id in this.peers) {
+ this.peers[peer.id].ready = true;
}
+ /**
+ * Peer Connect Event - A peer has connected
+ *
+ * @event Session#peerConnect
+ * @type {object}
+ * @property {SessionPeer} peer
+ * @property {peerReply} reply
+ */
+ this.emit("peerConnect", { peer, reply: sendPeer });
}
function handleDataComplete(data) {
- if (data.id === "close") {
- // Close connection when signaled to close
- peer.connection.destroy();
- }
- this.emit("data", {
+ /**
+ * Peer Data Event - Data received by a peer
+ *
+ * @event Session#peerData
+ * @type {object}
+ * @property {SessionPeer} peer
+ * @property {string} id
+ * @property {object} data
+ * @property {peerReply} reply
+ */
+ this.emit("peerData", {
peer,
id: data.id,
data: data.data,
@@ -161,18 +248,49 @@ class Session extends EventEmitter {
}
function handleDataProgress({ id, count, total }) {
- this.emit("dataProgress", { peer, id, count, total, reply: sendPeer });
+ this.emit("peerDataProgress", {
+ peer,
+ id,
+ count,
+ total,
+ reply: sendPeer,
+ });
}
function handleTrack(track, stream) {
- this.emit("trackAdded", { peer, track, stream });
+ /**
+ * Peer Track Added Event - A `MediaStreamTrack` was added by a peer
+ *
+ * @event Session#peerTrackAdded
+ * @type {object}
+ * @property {SessionPeer} peer
+ * @property {MediaStreamTrack} track
+ * @property {MediaStream} stream
+ */
+ this.emit("peerTrackAdded", { peer, track, stream });
track.addEventListener("mute", () => {
- this.emit("trackRemoved", { peer, track, stream });
+ /**
+ * Peer Track Removed Event - A `MediaStreamTrack` was removed by a peer
+ *
+ * @event Session#peerTrackRemoved
+ * @type {object}
+ * @property {SessionPeer} peer
+ * @property {MediaStreamTrack} track
+ * @property {MediaStream} stream
+ */
+ this.emit("peerTrackRemoved", { peer, track, stream });
});
}
function handleClose() {
- this.emit("disconnect", { peer });
+ /**
+ * Peer Disconnect Event - A peer has disconnected
+ *
+ * @event Session#peerDisconnect
+ * @type {object}
+ * @property {SessionPeer} peer
+ */
+ this.emit("peerDisconnect", { peer });
if (peer.id in this.peers) {
peer.connection.destroy();
this.peers = omit(this.peers, [peer.id]);
@@ -180,8 +298,15 @@ class Session extends EventEmitter {
}
function handleError(error) {
- console.error(error);
- this.emit("error", { peer, error });
+ /**
+ * Peer Error Event - An error occured with a peer connection
+ *
+ * @event Session#peerError
+ * @type {object}
+ * @property {SessionPeer} peer
+ * @property {Error} error
+ */
+ this.emit("peerError", { peer, error });
if (peer.id in this.peers) {
peer.connection.destroy();
this.peers = omit(this.peers, [peer.id]);
@@ -199,7 +324,7 @@ class Session extends EventEmitter {
this.peers[id] = peer;
} catch (error) {
logError(error);
- this.emit("error", { error });
+ this.emit("peerError", { error });
this.emit("disconnected");
for (let peer of Object.values(this.peers)) {
peer.connection && peer.connection.destroy();
@@ -207,45 +332,74 @@ class Session extends EventEmitter {
}
}
- _handlePartyMemberJoined(id) {
- this._addPeer(id, false, false);
+ _handleJoinedGame() {
+ /**
+ * Authentication Success Event - Successfully authenticated when joining a game
+ *
+ * @event Session#authenticationSuccess
+ */
+ this.emit("authenticationSuccess");
+ /**
+ * Connected Event - You have connected to the game
+ *
+ * @event Session#connected
+ */
+ this.emit("connected");
}
- _handlePartyMemberLeft(id) {
+ _handlePlayerJoined(id) {
+ /**
+ * Player Joined Event - A player has joined the game
+ *
+ * @event Session#playerJoined
+ * @property {string} id
+ */
+ this.emit("playerJoined", id);
+ }
+
+ _handlePlayerLeft(id) {
+ /**
+ * Player Left Event - A player has left the game
+ *
+ * @event Session#playerLeft
+ * @property {string} id
+ */
+ this.emit("playerLeft", id);
if (id in this.peers) {
this.peers[id].connection.destroy();
delete this.peers[id];
}
}
- _handleJoinedParty(otherIds) {
- for (let [index, id] of otherIds.entries()) {
- // Send a sync request to the first member of the party
- const sync = index === 0;
- this._addPeer(id, true, sync);
- }
- this.emit("authenticationSuccess");
- this.emit("connected");
- }
-
_handleSignal(data) {
- const { from, signal } = JSON.parse(data);
- if (from in this.peers) {
- this.peers[from].connection.signal(signal);
+ const { from, signal } = data;
+ if (!(from in this.peers)) {
+ this._addPeer(from, false);
}
+ this.peers[from].connection.signal(signal);
}
_handleAuthError() {
+ /**
+ * Authentication Error Event - Unsuccessfully authenticated when joining a game
+ *
+ * @event Session#authenticationError
+ */
this.emit("authenticationError");
}
_handleUnload() {
for (let peer of Object.values(this.peers)) {
- peer.connection.send({ id: "close" });
+ peer.connection && peer.connection.destroy();
}
}
_handleSocketDisconnect() {
+ /**
+ * Disconnected Event - You have disconnected from the party
+ *
+ * @event Session#disconnected
+ */
this.emit("disconnected");
for (let peer of Object.values(this.peers)) {
peer.connection && peer.connection.destroy();
@@ -253,8 +407,8 @@ class Session extends EventEmitter {
}
_handleSocketReconnect() {
- if (this._partyId) {
- this.joinParty(this._partyId, this._password);
+ if (this._gameId) {
+ this.joinGame(this._gameId, this._password);
}
}
}
diff --git a/src/routes/FAQ.js b/src/routes/FAQ.js
index b1b0a01..6d1ebb1 100644
--- a/src/routes/FAQ.js
+++ b/src/routes/FAQ.js
@@ -1,5 +1,5 @@
import React from "react";
-import { Flex, Text } from "theme-ui";
+import { Flex, Text, Box } from "theme-ui";
import raw from "raw.macro";
import Footer from "../components/Footer";
@@ -7,8 +7,10 @@ import Markdown from "../components/Markdown";
import assets from "../docs/assets";
-const connection = raw("../docs/faq/connection.md");
-const saving = raw("../docs/faq/saving.md");
+const database = raw("../docs/faq/database.md");
+const maps = raw("../docs/faq/maps.md");
+const audioSharing = raw("../docs/faq/audio-sharing.md");
+const general = raw("../docs/faq/general.md");
function FAQ() {
return (
@@ -31,12 +33,18 @@ function FAQ() {
Frequently Asked Questions
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/routes/Game.js b/src/routes/Game.js
index 83953a6..702180e 100644
--- a/src/routes/Game.js
+++ b/src/routes/Game.js
@@ -5,20 +5,21 @@ import { useParams } from "react-router-dom";
import Banner from "../components/Banner";
import LoadingOverlay from "../components/LoadingOverlay";
import Link from "../components/Link";
+import MapLoadingOverlay from "../components/map/MapLoadingOverlay";
import AuthModal from "../modals/AuthModal";
import AuthContext from "../contexts/AuthContext";
import { MapStageProvider } from "../contexts/MapStageContext";
import DatabaseContext from "../contexts/DatabaseContext";
+import { PlayerProvider } from "../contexts/PlayerContext";
+import { PartyProvider } from "../contexts/PartyContext";
import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens";
import NetworkedParty from "../network/NetworkedParty";
import Session from "../network/Session";
-const session = new Session();
-
function Game() {
const { id: gameId } = useParams();
const {
@@ -28,6 +29,16 @@ function Game() {
} = useContext(AuthContext);
const { databaseStatus } = useContext(DatabaseContext);
+ const [session] = useState(new Session());
+ const [offline, setOffline] = useState();
+ useEffect(() => {
+ async function connect() {
+ await session.connect();
+ setOffline(session.state === "offline");
+ }
+ connect();
+ }, [session]);
+
// Handle authentication status
useEffect(() => {
function handleAuthSuccess() {
@@ -43,7 +54,7 @@ function Game() {
session.off("authenticationSuccess", handleAuthSuccess);
session.off("authenticationError", handleAuthError);
};
- }, [setAuthenticationStatus]);
+ }, [setAuthenticationStatus, session]);
// Handle session errors
const [peerError, setPeerError] = useState(null);
@@ -59,7 +70,7 @@ function Game() {
return () => {
session.off("error", handlePeerError);
};
- }, []);
+ }, [session]);
// Handle connection
const [connected, setConnected] = useState(false);
@@ -79,55 +90,70 @@ function Game() {
session.off("connected", handleConnected);
session.off("disconnected", handleDisconnected);
};
- }, []);
+ }, [session]);
// Join game
useEffect(() => {
- if (databaseStatus !== "loading") {
- session.joinParty(gameId, password);
+ if (session.state === "online" && databaseStatus !== "loading") {
+ session.joinGame(gameId, password);
}
- }, [gameId, password, databaseStatus]);
+ }, [gameId, password, databaseStatus, session, offline]);
// A ref to the Konva stage
// the ref will be assigned in the MapInteraction component
const mapStageRef = useRef();
return (
-
-
-
-
-
-
-
- setPeerError(null)}>
-
-
- {peerError} See FAQ for more
- information.
-
-
-
- {}}
- allowClose={false}
- >
-
-
- Disconnected. Attempting to reconnect...
-
-
-
-
- {authenticationStatus === "unknown" && }
-
+
+
+
+
+
+
+
+
+
+ setPeerError(null)}
+ >
+
+
+ {peerError} See FAQ for more
+ information.
+
+
+
+ {}} allowClose={false}>
+
+
+ Unable to connect to game, refresh to reconnect.
+
+
+
+ {}}
+ allowClose={false}
+ >
+
+
+ Disconnected. Attempting to reconnect...
+
+
+
+
+ {authenticationStatus === "unknown" && !offline && }
+
+
+
+
);
}
diff --git a/src/routes/Home.js b/src/routes/Home.js
index f020ad6..6a2589b 100644
--- a/src/routes/Home.js
+++ b/src/routes/Home.js
@@ -6,6 +6,9 @@ import Footer from "../components/Footer";
import StartModal from "../modals/StartModal";
import JoinModal from "../modals/JoinModal";
+import GettingStartedModal from "../modals/GettingStartedModal";
+
+import HelpIcon from "../icons/HelpIcon";
import AuthContext from "../contexts/AuthContext";
@@ -20,6 +23,9 @@ import owlington from "../images/Owlington.png";
function Home() {
const [isStartModalOpen, setIsStartModalOpen] = useState(false);
const [isJoinModalOpen, setIsJoinModalOpen] = useState(false);
+ const [isGettingStartedModalOpen, setIsGettingStartedModalOpen] = useState(
+ false
+ );
// Reset password on visiting home
const { setPassword } = useContext(AuthContext);
@@ -51,6 +57,18 @@ function Home() {
Owlbear Rodeo
+
@@ -117,6 +135,10 @@ function Home() {
isOpen={isStartModalOpen}
onRequestClose={() => setIsStartModalOpen(false)}
/>
+ setIsGettingStartedModalOpen(false)}
+ />
diff --git a/src/routes/HowTo.js b/src/routes/HowTo.js
index b8e6640..351fe20 100644
--- a/src/routes/HowTo.js
+++ b/src/routes/HowTo.js
@@ -20,6 +20,7 @@ const usingMeasure = raw("../docs/howTo/usingMeasure.md");
const sharingAudio = raw("../docs/howTo/sharingAudio.md");
const usingPointer = raw("../docs/howTo/usingPointer.md");
const usingTimer = raw("../docs/howTo/usingTimer.md");
+const usingNotes = raw("../docs/howTo/usingNotes.md");
function HowTo() {
const location = useLocation();
@@ -111,6 +112,14 @@ function HowTo() {
+
Release Notes
+
diff --git a/src/settings.js b/src/settings.js
index 16bfa7c..e2dce0c 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -32,6 +32,11 @@ function loadVersions(settings) {
...prev,
map: { fullScreen: false, labelSize: 1 },
}));
+ // v1.7.0 - Added game password
+ settings.version(3, (prev) => ({
+ ...prev,
+ game: { usePassword: true },
+ }));
}
export function getSettings() {
diff --git a/src/theme.js b/src/theme.js
index 6a1d4c8..305dcbb 100644
--- a/src/theme.js
+++ b/src/theme.js
@@ -40,6 +40,7 @@ const theme = {
},
lineHeights: {
body: 1.3,
+ body2: 1.5,
display: 1.1,
heading: 1.25,
},
@@ -68,6 +69,7 @@ const theme = {
fontFamily: "body2",
fontSize: 0,
fontWeight: "body",
+ lineHeight: "body2",
},
},
styles: {
@@ -185,6 +187,9 @@ const theme = {
opacity: 0.5,
},
},
+ textarea: {
+ fontFamily: "body2",
+ },
},
buttons: {
primary: {
diff --git a/src/workers/DatabaseWorker.js b/src/workers/DatabaseWorker.js
new file mode 100644
index 0000000..4c5fff3
--- /dev/null
+++ b/src/workers/DatabaseWorker.js
@@ -0,0 +1,18 @@
+import * as Comlink from "comlink";
+
+import { getDatabase } from "../database";
+
+// Worker to load large amounts of database data on a separate thread
+let obj = {
+ data: [],
+ async loadData(table) {
+ this.data = [];
+ try {
+ let db = getDatabase({});
+ // Use a cursor instead of toArray to prevent IPC max size error
+ await db.table(table).each((map) => this.data.push(map));
+ } catch {}
+ },
+};
+
+Comlink.expose(obj);
diff --git a/yarn.lock b/yarn.lock
index b0f6798..9639718 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2016,6 +2016,11 @@
dependencies:
"@babel/types" "^7.3.0"
+"@types/component-emitter@^1.2.10":
+ version "1.2.10"
+ resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea"
+ integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==
+
"@types/eslint@^7.2.4":
version "7.2.5"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.5.tgz#92172ecf490c2fce4b076739693d75f30376d610"
@@ -2534,11 +2539,6 @@ adjust-sourcemap-loader@3.0.0:
loader-utils "^2.0.0"
regex-parser "^2.2.11"
-after@0.8.2:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
- integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
-
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -2743,11 +2743,6 @@ array.prototype.flatmap@^1.2.3:
es-abstract "^1.18.0-next.1"
function-bind "^1.1.1"
-arraybuffer.slice@~0.0.7:
- version "0.0.7"
- resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
- integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
-
arrify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
@@ -3083,10 +3078,10 @@ base64-arraybuffer-es6@0.6.0:
resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.6.0.tgz#036f79f57588dca0018de7792ddf149299382007"
integrity sha512-57nLqKj4ShsDwFJWJsM4sZx6u60WbCge35rWRSevUwqxDtRwwxiKAO800zD2upPv4CfdWjQp//wSLar35nDKvA==
-base64-arraybuffer@0.1.5:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
- integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
+base64-arraybuffer@0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
+ integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=
base64-js@^1.0.2:
version "1.5.1"
@@ -3118,13 +3113,6 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
-better-assert@~1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
- integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=
- dependencies:
- callsite "1.0.0"
-
bfj@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2"
@@ -3157,11 +3145,6 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"
-blob@0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
- integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
-
bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@@ -3470,11 +3453,6 @@ caller-path@^2.0.0:
dependencies:
caller-callsite "^2.0.0"
-callsite@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
- integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
-
callsites@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
@@ -3823,6 +3801,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
+comlink@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/comlink/-/comlink-4.3.0.tgz#80b3366baccd87897dab3638ebfcfae28b2f87c7"
+ integrity sha512-mu4KKKNuW8TvkfpW/H88HBPeILubBS6T94BdD1VWBXNXfiyqVtwUCVNO1GeNOBTsIswzsMjWlycYr+77F5b84g==
+
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -3848,21 +3831,11 @@ component-bind@1.0.0:
resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=
-component-emitter@1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
- integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
-
component-emitter@^1.2.1, component-emitter@~1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
-component-inherit@0.0.3:
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
- integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
-
compose-function@3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f"
@@ -4414,13 +4387,6 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "2.1.2"
-debug@~3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
- integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
- dependencies:
- ms "2.0.0"
-
debug@~4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
@@ -4448,6 +4414,11 @@ dedent@^0.7.0:
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
+deep-diff@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-1.0.2.tgz#afd3d1f749115be965e89c63edc7abb1506b9c26"
+ integrity sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==
+
deep-equal@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
@@ -4859,33 +4830,26 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
-engine.io-client@~3.4.0:
- version "3.4.3"
- resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.3.tgz#192d09865403e3097e3575ebfeb3861c4d01a66c"
- integrity sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==
+engine.io-client@~4.0.0:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-4.0.4.tgz#973d312ab9c8dc41e64d2c323982f3b04a7f749d"
+ integrity sha512-and4JRvjv+BQ4WBLopYUFePxju3ms3aBRk0XjaLdh/t9TKv2LCKtKKWFRoRzIfUZsu3U38FcYqNLuXhfS16vqw==
dependencies:
+ base64-arraybuffer "0.1.4"
component-emitter "~1.3.0"
- component-inherit "0.0.3"
debug "~4.1.0"
- engine.io-parser "~2.2.0"
+ engine.io-parser "~4.0.1"
has-cors "1.1.0"
- indexof "0.0.1"
- parseqs "0.0.5"
- parseuri "0.0.5"
- ws "~6.1.0"
+ parseqs "0.0.6"
+ parseuri "0.0.6"
+ ws "~7.2.1"
xmlhttprequest-ssl "~1.5.4"
yeast "0.1.2"
-engine.io-parser@~2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed"
- integrity sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==
- dependencies:
- after "0.8.2"
- arraybuffer.slice "~0.0.7"
- base64-arraybuffer "0.1.5"
- blob "0.0.5"
- has-binary2 "~1.0.2"
+engine.io-parser@~4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.1.tgz#6444c3cf2523ba4fc3bbaedd4fe425e6bcb16479"
+ integrity sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg==
enhanced-resolve@^4.3.0:
version "4.3.0"
@@ -5846,9 +5810,9 @@ gensync@^1.0.0-beta.1:
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-browser-rtc@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz#bbcd40c8451a7ed4ef5c373b8169a409dd1d11d9"
- integrity sha1-u81AyEUaftTvXDc7gWmkCd0dEdk=
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz#d1494e299b00f33fc8e9d6d3343ba4ba99711a2c"
+ integrity sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
@@ -6019,13 +5983,6 @@ harmony-reflect@^1.4.6:
resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9"
integrity sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==
-has-binary2@~1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
- integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
- dependencies:
- isarray "2.0.1"
-
has-cors@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
@@ -6438,11 +6395,6 @@ indexes-of@^1.0.1:
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
-indexof@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
- integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
-
infer-owner@^1.0.3, infer-owner@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
@@ -6907,11 +6859,6 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-isarray@2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
- integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
-
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -8414,11 +8361,6 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-object-component@0.0.3:
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
- integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
-
object-copy@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
@@ -8765,19 +8707,15 @@ parse5@5.1.1:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
-parseqs@0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
- integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=
- dependencies:
- better-assert "~1.0.0"
+parseqs@0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
+ integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==
-parseuri@0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
- integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=
- dependencies:
- better-assert "~1.0.0"
+parseuri@0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a"
+ integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==
parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3"
@@ -9659,7 +9597,7 @@ prepend-http@^1.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
-pretty-bytes@^5.3.0:
+pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b"
integrity sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==
@@ -9835,9 +9773,9 @@ querystringify@^2.1.1:
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
queue-microtask@^1.1.0:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.1.4.tgz#40841ace4356b48b35b5ea61a2e1fe0a23c59ce1"
- integrity sha512-eY/4Obve9cE5FK8YvC1cJsm5cr7XvAurul8UtBDJ2PR1p5NmAwHtvAt5ftcLtwYRCUKNhxCneZZlxmUDFoSeKA==
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3"
+ integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==
raf-schd@^4.0.2:
version "4.0.2"
@@ -11008,6 +10946,7 @@ simple-peer@feross/simple-peer#694/head:
resolved "https://codeload.github.com/feross/simple-peer/tar.gz/0d08d07b83ff3b8c60401688d80642d24dfeffe2"
dependencies:
debug "^4.0.1"
+ err-code "^2.0.3"
get-browser-rtc "^1.0.0"
queue-microtask "^1.1.0"
randombytes "^2.0.3"
@@ -11094,34 +11033,27 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
-socket.io-client@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4"
- integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==
+socket.io-client@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-3.0.3.tgz#37890e24fc386e9de34358a1b7c4a2968f5d2630"
+ integrity sha512-kwCJAKb6JMqE9ZYXg78Dgt8rYLSwtJ/g/LJqpb/pOTFRZMSr1cKAsCaisHZ+IBwKHBY7DYOOkjtkHqseY3ZLpw==
dependencies:
+ "@types/component-emitter" "^1.2.10"
backo2 "1.0.2"
- base64-arraybuffer "0.1.5"
component-bind "1.0.0"
- component-emitter "1.2.1"
+ component-emitter "~1.3.0"
debug "~4.1.0"
- engine.io-client "~3.4.0"
- has-binary2 "~1.0.2"
- has-cors "1.1.0"
- indexof "0.0.1"
- object-component "0.0.3"
- parseqs "0.0.5"
- parseuri "0.0.5"
- socket.io-parser "~3.3.0"
- to-array "0.1.4"
+ engine.io-client "~4.0.0"
+ parseuri "0.0.6"
+ socket.io-parser "~4.0.1"
-socket.io-parser@~3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
- integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==
+socket.io-parser@~4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.1.tgz#b8ec84364c7369ad32ae0c16dd4d388db19b3a04"
+ integrity sha512-5JfNykYptCwU2lkOI0ieoePWm+6stEhkZ2UnLDjqnE1YEjUlXXLd1lpxPZ+g+h3rtaytwWkWrLQCaJULlGqjOg==
dependencies:
- component-emitter "1.2.1"
- debug "~3.1.0"
- isarray "2.0.1"
+ component-emitter "~1.3.0"
+ debug "~4.1.0"
sockjs-client@1.4.0:
version "1.4.0"
@@ -11828,11 +11760,6 @@ tmpl@1.0.x:
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
-to-array@0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
- integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA=
-
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@@ -12819,6 +12746,14 @@ worker-farm@^1.7.0:
dependencies:
errno "~0.1.7"
+worker-loader@^3.0.5:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.5.tgz#6e13a583c4120ba419eece8e4f2e098b014311bf"
+ integrity sha512-cOh4UqTtvT8eHpyuuTK2C66Fg/G5Pb7g11bwtKm7uyD0vj2hCGY1APlSzVD75V9ciYZt44VPbFPiSFTSLxkQ+w==
+ dependencies:
+ loader-utils "^2.0.0"
+ schema-utils "^3.0.0"
+
worker-rpc@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5"
@@ -12887,12 +12822,10 @@ ws@^7.2.3:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.0.tgz#a5dd76a24197940d4a8bb9e0e152bb4503764da7"
integrity sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==
-ws@~6.1.0:
- version "6.1.4"
- resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
- integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==
- dependencies:
- async-limiter "~1.0.0"
+ws@~7.2.1:
+ version "7.2.5"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.5.tgz#abb1370d4626a5a9cd79d8de404aa18b3465d10d"
+ integrity sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==
x-is-string@^0.1.0:
version "0.1.0"