Convert to use new prettier rules (#22)

This commit is contained in:
fj-msft 2020-08-09 21:09:40 -05:00 коммит произвёл GitHub
Родитель 90880f5cbd
Коммит 811860273e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
59 изменённых файлов: 22011 добавлений и 22813 удалений

Просмотреть файл

@ -1,19 +1,19 @@
/**@type {import('eslint').Linter.Config} */
// eslint-disable-next-line no-undef
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
env: {
browser: true,
node: true,
},
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
rules: {
semi: [2, "always"],
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-non-null-assertion": 0,
},
root: true,
parser: "@typescript-eslint/parser",
env: {
browser: true,
node: true
},
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
rules: {
semi: [2, "always"],
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-non-null-assertion": 0
}
};

24
.vscode/launch.json поставляемый
Просмотреть файл

@ -3,16 +3,16 @@
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "npm: watch"
}
]
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "npm: watch"
}
]
}

4
.vscode/settings.json поставляемый
Просмотреть файл

@ -16,7 +16,5 @@
"html.format.wrapAttributes": "preserve-aligned",
"editor.codeActionsOnSave": {
"source.fixAll": false
},
"importSorter.sortConfiguration.removeUnusedImports": true,
"importSorter.sortConfiguration.removeUnusedDefaultImports": true
}
}

32
.vscode/tasks.json поставляемый
Просмотреть файл

@ -1,20 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

28032
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -78,6 +78,7 @@
"compile-ext": "tsc -p tsconfig.ext.json",
"dev": "react-scripts start",
"lint": "eslint . --ext .ts,.tsx",
"format": "prettier --write \"./**/*.{js,jsx,json,ts,tsx}\" \"!./build\"",
"create-definitions": "tsc -p tsconfig.definitiongen.json && node ./definition-generator/tools/definition-generator/entry.js",
"refreshVSToken": "vsts-npm-auth -config .npmrc"
},
@ -96,13 +97,14 @@
"devDependencies": {
"@types/dagre": "^0.7.44",
"@types/lodash": "^4.14.155",
"@types/uuid": "^8.0.0",
"@types/node": "^12.12.0",
"@types/react": "^16.9.36",
"@types/react-dom": "^16.9.8",
"@types/uuid": "^8.0.0",
"@types/vscode": "^1.38.0",
"@typescript-eslint/eslint-plugin": "^3.0.2",
"@typescript-eslint/parser": "^3.0.2",
"prettier": "^2.0.5",
"typescript": "^3.9.4"
},
"browserslist": {

Просмотреть файл

@ -3,7 +3,7 @@ import { render } from "@testing-library/react";
import App from "./App";
test("renders learn react link", () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

Просмотреть файл

@ -1,73 +1,59 @@
import "./App.css";
import React, { useEffect } from "react";
import { ThemeProvider } from "office-ui-fabric-react/lib/Foundation";
import { ITheme } from "office-ui-fabric-react";
import ThemeHelpers from "./helpers/ThemeHelpers";
import IconSetupHelpers from "./helpers/IconSetupHelpers";
import { ThemeProvider } from "office-ui-fabric-react/lib/Foundation";
import React, { useEffect } from "react";
import { IZoomPanSettings } from "@vienna/react-dag-editor";
import { sampleTopology } from "./dev/sampleTopologies.js";
import { GraphTopology } from "./editor/components/GraphTopology";
import { GraphInstance } from "./editor/components/GraphInstance";
import { GraphInfo } from "./types/graphTypes";
import { GraphTopology } from "./editor/components/GraphTopology";
import Graph from "./graph/Graph";
import IconSetupHelpers from "./helpers/IconSetupHelpers";
import ThemeHelpers from "./helpers/ThemeHelpers";
import { GraphInfo } from "./types/graphTypes";
IconSetupHelpers.initializeIcons();
interface IProps {
graphData?: GraphInfo;
zoomPanSettings?: IZoomPanSettings;
vsCodeSetState: (state: any) => void;
graphData?: GraphInfo;
zoomPanSettings?: IZoomPanSettings;
vsCodeSetState: (state: any) => void;
}
export const App: React.FunctionComponent<IProps> = (props) => {
const [theme, setTheme] = React.useState<ITheme>(
ThemeHelpers.getAdaptedTheme()
);
const observer = ThemeHelpers.attachHtmlStyleAttrListener(() => {
setTheme(ThemeHelpers.getAdaptedTheme());
});
const [theme, setTheme] = React.useState<ITheme>(ThemeHelpers.getAdaptedTheme());
const observer = ThemeHelpers.attachHtmlStyleAttrListener(() => {
setTheme(ThemeHelpers.getAdaptedTheme());
});
// when unmounting, disconnect the observer to prevent leaked references
useEffect(() => {
return () => {
observer.disconnect();
};
});
// when unmounting, disconnect the observer to prevent leaked references
useEffect(() => {
return () => {
observer.disconnect();
};
});
const editingTopology = true;
const editingTopology = true;
const graph = new Graph();
const graph = new Graph();
if (props.graphData) {
graph.setGraphData(props.graphData);
} else {
graph.setTopology(sampleTopology);
}
if (props.graphData) {
graph.setGraphData(props.graphData);
} else {
graph.setTopology(sampleTopology);
}
// if there is no state to recover from (in props.graphData or zoomPanSettings), use default
// (load sampleTopology) and 1x zoom, no translate (stored in a transformation matrix)
// https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix
return (
<ThemeProvider theme={theme}>
{editingTopology ? (
<GraphTopology
graph={graph}
zoomPanSettings={
props.zoomPanSettings || { transformMatrix: [1, 0, 0, 1, 0, 0] }
}
vsCodeSetState={props.vsCodeSetState}
/>
) : (
<GraphInstance
graph={graph}
zoomPanSettings={
props.zoomPanSettings || { transformMatrix: [1, 0, 0, 1, 0, 0] }
}
vsCodeSetState={props.vsCodeSetState}
/>
)}
</ThemeProvider>
);
// if there is no state to recover from (in props.graphData or zoomPanSettings), use default
// (load sampleTopology) and 1x zoom, no translate (stored in a transformation matrix)
// https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix
return (
<ThemeProvider theme={theme}>
{editingTopology ? (
<GraphTopology graph={graph} zoomPanSettings={props.zoomPanSettings || { transformMatrix: [1, 0, 0, 1, 0, 0] }} vsCodeSetState={props.vsCodeSetState} />
) : (
<GraphInstance graph={graph} zoomPanSettings={props.zoomPanSettings || { transformMatrix: [1, 0, 0, 1, 0, 0] }} vsCodeSetState={props.vsCodeSetState} />
)}
</ThemeProvider>
);
};
export default App;

2
src/bootstrap.js поставляемый
Просмотреть файл

@ -5,5 +5,5 @@
__webpack_nonce__ = window.__webpack_nonce__;
if (window.__webpack_public_path__) {
__webpack_public_path__ = window.__webpack_public_path__;
__webpack_public_path__ = window.__webpack_public_path__;
}

Просмотреть файл

@ -1,42 +1,34 @@
import { ITreeNode } from "react-accessible-tree";
import { NodeDefinition, CanvasNodeProperties } from "../types/graphTypes";
import { CanvasNodeProperties, NodeDefinition } from "../types/graphTypes";
import * as storedNodes from "./v1.0/nodes.json";
const availableNodes: NodeDefinition[] = storedNodes.availableNodes as NodeDefinition[];
const itemPanelNodes: ITreeNode[] = storedNodes.itemPanelNodes;
export default class Definitions {
public static getNodeDefinition(
nodeProperties: CanvasNodeProperties
): NodeDefinition {
return availableNodes.filter(
(x) =>
nodeProperties["@type"] &&
x.name === nodeProperties["@type"].replace("#Microsoft.Media.", "")
)[0];
}
public static getCompatibleNodes(fullParentTypeRef: string) {
const compatibleNodes = [];
const parentType = fullParentTypeRef.replace("#/definitions/", "");
for (const candidateNode of availableNodes) {
const nodeInheritsFrom =
candidateNode.parsedAllOf &&
candidateNode.parsedAllOf.includes(fullParentTypeRef);
if (nodeInheritsFrom || candidateNode.name === parentType) {
compatibleNodes.push(candidateNode);
}
public static getNodeDefinition(nodeProperties: CanvasNodeProperties): NodeDefinition {
return availableNodes.filter((x) => nodeProperties["@type"] && x.name === nodeProperties["@type"].replace("#Microsoft.Media.", ""))[0];
}
return compatibleNodes;
}
public static getCompatibleNodes(fullParentTypeRef: string) {
const compatibleNodes = [];
const parentType = fullParentTypeRef.replace("#/definitions/", "");
public static getAllAvailableNodes() {
return availableNodes;
}
for (const candidateNode of availableNodes) {
const nodeInheritsFrom = candidateNode.parsedAllOf && candidateNode.parsedAllOf.includes(fullParentTypeRef);
if (nodeInheritsFrom || candidateNode.name === parentType) {
compatibleNodes.push(candidateNode);
}
}
public static getItemPanelNodes() {
return itemPanelNodes;
}
return compatibleNodes;
}
public static getAllAvailableNodes() {
return availableNodes;
}
public static getItemPanelNodes() {
return itemPanelNodes;
}
}

Просмотреть файл

@ -1,140 +1,140 @@
{
"MediaGraphInstance": "Represents a Media Graph instance.",
"MediaGraphInstanceProperties": "Properties of a Media Graph instance.",
"MediaGraphInstanceProperties.description": "An optional description for the instance.",
"MediaGraphInstanceProperties.topologyName": "The name of the graph topology that this instance will run. A topology with this name should already have been set in the Edge module.",
"MediaGraphInstanceProperties.parameters": "List of one or more graph instance parameters.",
"MediaGraphInstanceProperties.state": "Allowed states for a graph Instance.",
"MediaGraphInstanceProperties.state.Inactive": "Inactive state.",
"MediaGraphInstanceProperties.state.Activating": "Activating state.",
"MediaGraphInstanceProperties.state.Active": "Active state.",
"MediaGraphInstanceProperties.state.Deactivating": "Deactivating state.",
"MediaGraphParameterDefinition": "A key, value pair. The graph topology can be authored with certain values with parameters. Then, during graph instance creation, the value for that parameters can be specified. This allows the same graph topology to be used as a blueprint for multiple graph instances with different values for the parameters.",
"MediaGraphParameterDefinition.name": "Name of parameter as defined in the graph topology.",
"MediaGraphParameterDefinition.value": "Value of parameter.",
"MediaGraphInstanceCollection": "Collection of graph instances.",
"MediaGraphInstanceCollection.value": "Collection of graph instances.",
"MediaGraphInstanceCollection.@continuationToken": "Continuation token to use in subsequent calls to enumerate through the graph instance collection (when the collection contains too many results to return in one response).",
"MediaGraphTopologyCollection": "Collection of graph topologies.",
"MediaGraphTopologyCollection.value": "Collection of graph topologies.",
"MediaGraphTopologyCollection.@continuationToken": "Continuation token to use in subsequent calls to enumerate through the graph topologies collection (when the collection contains too many results to return in one response).",
"MediaGraphTopology": "Describes a graph topology.",
"MediaGraphTopologyProperties": "Describes the properties of a graph topology.",
"MediaGraphSystemData": "Graph system data.",
"MediaGraphSystemData.createdAt": "The timestamp of resource creation (UTC).",
"MediaGraphSystemData.lastModifiedAt": "The timestamp of resource last modification (UTC).",
"MediaGraphParameterDeclaration": "The declaration of a parameter in the graph topology. A graph topology can be authored with parameters. Then, during graph instance creation, the value for those parameters can be specified. This allows the same graph topology to be used as a blueprint for multiple graph instances with different values for the parameters.",
"MediaGraphParameterDeclaration.name": "The name of the parameter.",
"MediaGraphParameterDeclaration.type.String": "A string parameter value.",
"MediaGraphParameterDeclaration.type.SecretString": "A string to hold sensitive information as parameter value.",
"MediaGraphParameterDeclaration.type.Int": "A 32-bit signed integer as parameter value.",
"MediaGraphParameterDeclaration.type.Double": "A 64-bit double-precision floating point type as parameter value.",
"MediaGraphParameterDeclaration.type.Bool": "A boolean value that is either true or false.",
"MediaGraphParameterDeclaration.description": "Description of the parameter.",
"MediaGraphParameterDeclaration.default": "The default value for the parameter, to be used if the graph instance does not specify a value.",
"MediaGraphSource": "Media graph source.",
"MediaGraphSource.@type": "The type of the source node. The discriminator for derived types.",
"MediaGraphSource.name": "The name to be used for this source node.",
"MediaGraphRtspSource": "Enables a graph to capture media from a RTSP server.",
"MediaGraphRtspSource.transport": "Underlying RTSP transport. This is used to enable or disable HTTP tunneling.",
"MediaGraphRtspSource.transport.Http": "HTTP/HTTPS transport. This should be used when HTTP tunneling is desired.",
"MediaGraphRtspSource.transport.Tcp": "TCP transport. This should be used when HTTP tunneling is NOT desired.",
"MediaGraphRtspSource.endpoint": "RTSP endpoint of the stream that is being connected to.",
"MediaGraphIoTHubMessageSource": "Enables a graph to receive messages via routes declared in the IoT Edge deployment manifest.",
"MediaGraphIoTHubMessageSource.hubInputName": "Name of the input path where messages can be routed to (via routes declared in the IoT Edge deployment manifest).",
"MediaGraphIoTHubMessageSink": "Enables a graph to publish messages that can be delivered via routes declared in the IoT Edge deployment manifest.",
"MediaGraphIoTHubMessageSink.hubOutputName": "Name of the output path to which the graph will publish message. These messages can then be delivered to desired destinations by declaring routes referencing the output path in the IoT Edge deployment manifest.",
"MediaGraphEndpoint": "Base class for endpoints.",
"MediaGraphEndpoint.@type": "The discriminator for derived types.",
"MediaGraphEndpoint.credentials": "Polymorphic credentials to be presented to the endpoint.",
"MediaGraphEndpoint.url": "Url for the endpoint.",
"MediaGraphCredentials": "Credentials to present during authentication.",
"MediaGraphCredentials.@type": "The discriminator for derived types.",
"MediaGraphUsernamePasswordCredentials": "Username/password credential pair.",
"MediaGraphUsernamePasswordCredentials.username": "Username for a username/password pair.",
"MediaGraphUsernamePasswordCredentials.password": "Password for a username/password pair.",
"MediaGraphHttpHeaderCredentials": "Http header service credentials.",
"MediaGraphHttpHeaderCredentials.headerName": "HTTP header name.",
"MediaGraphHttpHeaderCredentials.headerValue": "HTTP header value.",
"MediaGraphUnsecuredEndpoint": "An endpoint that the media graph can connect to, with no encryption in transit.",
"MediaGraphTlsEndpoint": "An endpoint that the graph can connect to, which must be connected over TLS/SSL.",
"MediaGraphTlsEndpoint.trustedCertificates": "Trusted certificates when authenticating a TLS connection. Null designates that Azure Media Service's source of trust should be used.",
"MediaGraphTlsEndpoint.validationOptions": "Validation options to use when authenticating a TLS connection. By default, strict validation is used.",
"MediaGraphCertificateSource": "Base class for certificate sources.",
"MediaGraphCertificateSource.@type": "The discriminator for derived types.",
"MediaGraphTlsValidationOptions": "Options for controlling the authentication of TLS endpoints.",
"MediaGraphTlsValidationOptions.ignoreHostname": "Boolean value ignoring the host name (common name) during validation.",
"MediaGraphTlsValidationOptions.ignoreSignature": "Boolean value ignoring the integrity of the certificate chain at the current time.",
"MediaGraphPemCertificateList": "A list of PEM formatted certificates.",
"MediaGraphPemCertificateList.certificates": "PEM formatted public certificates one per entry.",
"MediaGraphSink": "Enables a media graph to write media data to a destination outside of the Live Video Analytics IoT Edge module.",
"MediaGraphSink.@type": "The discriminator for derived types.",
"MediaGraphSink.name": "Name to be used for the media graph sink.",
"MediaGraphSink.inputs": "An array of the names of the other nodes in the media graph, the outputs of which are used as input for this sink node.",
"MediaGraphNodeInput": "Represents the input to any node in a media graph.",
"MediaGraphNodeInput.nodeName": "The name of another node in the media graph, the output of which is used as input to this node.",
"MediaGraphNodeInput.outputSelectors": "Allows for the selection of particular streams from another node.",
"MediaGraphOutputSelector": "Allows for the selection of particular streams from another node.",
"MediaGraphOutputSelector.property": "The stream property to compare with.",
"MediaGraphOutputSelector.property.mediaType": "The stream's MIME type or subtype.",
"MediaGraphOutputSelector.operator": "The operator to compare streams by.",
"MediaGraphOutputSelector.operator.is": "A media type is the same type or a subtype.",
"MediaGraphOutputSelector.operator.isNot": "A media type is not the same type or a subtype.",
"MediaGraphOutputSelector.value": "Value to compare against.",
"MediaGraphFileSink": "Enables a media graph to write/store media (video and audio) to a file on the Edge device.",
"MediaGraphFileSink.filePathPattern": "Absolute file path pattern for creating new files on the Edge device.",
"MediaGraphAssetSink": "Enables a graph to record media to an Azure Media Services asset, for subsequent playback.",
"MediaGraphAssetSink.assetNamePattern": "A name pattern when creating new assets.",
"MediaGraphAssetSink.segmentLength": "When writing media to an asset, wait until at least this duration of media has been accumulated on the Edge. Expressed in increments of 30 seconds, with a minimum of 30 seconds and a recommended maximum of 5 minutes.",
"MediaGraphAssetSink.localMediaCachePath": "Path to a local file system directory for temporary caching of media, before writing to an Asset. Used when the Edge device is temporarily disconnected from Azure.",
"MediaGraphAssetSink.localMediaCacheMaximumSizeMiB": "Maximum amount of disk space that can be used for temporary caching of media.",
"MediaGraphProcessor": "A node that represents the desired processing of media in a graph. Takes media and/or events as inputs, and emits media and/or event as output.",
"MediaGraphProcessor.@type": "The discriminator for derived types.",
"MediaGraphProcessor.name": "The name for this processor node.",
"MediaGraphProcessor.inputs": "An array of the names of the other nodes in the media graph, the outputs of which are used as input for this processor node.",
"MediaGraphMotionDetectionProcessor": "A node that accepts raw video as input, and detects if there are moving objects present. If so, then it emits an event, and allows frames where motion was detected to pass through. Other frames are blocked/dropped.",
"MediaGraphMotionDetectionProcessor.sensitivity": "Enumeration that specifies the sensitivity of the motion detection processor.",
"MediaGraphMotionDetectionProcessor.sensitivity.Low": "Low Sensitivity.",
"MediaGraphMotionDetectionProcessor.sensitivity.Medium": "Medium Sensitivity.",
"MediaGraphMotionDetectionProcessor.sensitivity.High": "High Sensitivity.",
"MediaGraphMotionDetectionProcessor.outputMotionRegion": "Indicates whether the processor should detect and output the regions, within the video frame, where motion was detected. Default is true.",
"MediaGraphExtensionProcessorBase": "Processor that allows for extensions, outside of the Live Video Analytics Edge module, to be integrated into the graph. It is the base class for various different kinds of extension processor types.",
"MediaGraphExtensionProcessorBase.endpoint": "Endpoint to which this processor should connect.",
"MediaGraphExtensionProcessorBase.image": "Describes the parameters of the image that is sent as input to the endpoint.",
"MediaGraphHttpExtension": "A processor that allows the media graph to send video frames (mostly at low frame rates e.g. <5 fps) to external inference container by leveraging HTTP based RESTful API. It then retrieves the inference results and relays them downstream to the next node.",
"MediaGraphImage": "Describes the properties of an image frame.",
"MediaGraphImageScale": "The scaling mode for the image.",
"MediaGraphImageScale.mode": "Describes the modes for scaling an input video frame into an image, before it is sent to an inference engine.",
"MediaGraphImageScale.mode.PreserveAspectRatio": "Use the same aspect ratio as the input frame.",
"MediaGraphImageScale.mode.Pad": "Center pad the input frame to match the given dimensions.",
"MediaGraphImageScale.mode.Stretch": "Stretch input frame to match given dimensions.",
"MediaGraphImageScale.width": "The desired output width of the image.",
"MediaGraphImageScale.height": "The desired output height of the image.",
"MediaGraphImageFormat": "Encoding settings for an image.",
"MediaGraphImageFormat.@type": "The discriminator for derived types.",
"MediaGraphImageFormatRaw": "Encoding settings for raw images.",
"MediaGraphImageFormatRaw.pixelFormat.Yuv420p": "Planar YUV 4:2:0, 12bpp, (1 Cr and Cb sample per 2x2 Y samples).",
"MediaGraphImageFormatRaw.pixelFormat.Rgb565be": "Packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), big-endian.",
"MediaGraphImageFormatRaw.pixelFormat.Rgb565le": "Packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), little-endian.",
"MediaGraphImageFormatRaw.pixelFormat.Rgb555be": "Packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), big-endian , X=unused/undefined.",
"MediaGraphImageFormatRaw.pixelFormat.Rgb555le": "Packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), little-endian, X=unused/undefined.",
"MediaGraphImageFormatRaw.pixelFormat.Rgb24": "Packed RGB 8:8:8, 24bpp, RGBRGB.",
"MediaGraphImageFormatRaw.pixelFormat.Bgr24": "Packed RGB 8:8:8, 24bpp, BGRBGR.",
"MediaGraphImageFormatRaw.pixelFormat.Argb": "Packed ARGB 8:8:8:8, 32bpp, ARGBARGB.",
"MediaGraphImageFormatRaw.pixelFormat.Rgba": "Packed RGBA 8:8:8:8, 32bpp, RGBARGBA.",
"MediaGraphImageFormatRaw.pixelFormat.Abgr": "Packed ABGR 8:8:8:8, 32bpp, ABGRABGR.",
"MediaGraphImageFormatRaw.pixelFormat.Bgra": "Packed BGRA 8:8:8:8, 32bpp, BGRABGRA.",
"MediaGraphImageFormatEncoded": "Allowed formats for the image.",
"MediaGraphImageFormatEncoded.encoding": "The different encoding formats that can be used for the image.",
"MediaGraphImageFormatEncoded.encoding.Jpeg": "JPEG image format.",
"MediaGraphImageFormatEncoded.encoding.Bmp": "BMP image format.",
"MediaGraphImageFormatEncoded.encoding.Png": "PNG image format.",
"MediaGraphImageFormatEncoded.quality": "The image quality (used for JPEG only). Value must be between 0 to 100 (best quality).",
"MediaGraphSignalGateProcessor": "A signal gate determines when to block (gate) incoming media, and when to allow it through. It gathers input events over the activationEvaluationWindow, and determines whether to open or close the gate.",
"MediaGraphSignalGateProcessor.activationEvaluationWindow": "The period of time over which the gate gathers input events, before evaluating them.",
"MediaGraphSignalGateProcessor.activationSignalOffset": "Signal offset once the gate is activated (can be negative). It is an offset between the time the event is received, and the timestamp of the first media sample (eg. video frame) that is allowed through by the gate.",
"MediaGraphSignalGateProcessor.minimumActivationTime": "The minimum period for which the gate remains open, in the absence of subsequent triggers (events).",
"MediaGraphSignalGateProcessor.maximumActivationTime": "The maximum period for which the gate remains open, in the presence of subsequent events.",
"MediaGraphFrameRateFilterProcessor": "Limits the frame rate on the input video stream based on the maximumFps property.",
"MediaGraphFrameRateFilterProcessor.maximumFps": "Ensures that the frame rate of the video leaving this processor does not exceed this limit."
}
"MediaGraphInstance": "Represents a Media Graph instance.",
"MediaGraphInstanceProperties": "Properties of a Media Graph instance.",
"MediaGraphInstanceProperties.description": "An optional description for the instance.",
"MediaGraphInstanceProperties.topologyName": "The name of the graph topology that this instance will run. A topology with this name should already have been set in the Edge module.",
"MediaGraphInstanceProperties.parameters": "List of one or more graph instance parameters.",
"MediaGraphInstanceProperties.state": "Allowed states for a graph Instance.",
"MediaGraphInstanceProperties.state.Inactive": "Inactive state.",
"MediaGraphInstanceProperties.state.Activating": "Activating state.",
"MediaGraphInstanceProperties.state.Active": "Active state.",
"MediaGraphInstanceProperties.state.Deactivating": "Deactivating state.",
"MediaGraphParameterDefinition": "A key, value pair. The graph topology can be authored with certain values with parameters. Then, during graph instance creation, the value for that parameters can be specified. This allows the same graph topology to be used as a blueprint for multiple graph instances with different values for the parameters.",
"MediaGraphParameterDefinition.name": "Name of parameter as defined in the graph topology.",
"MediaGraphParameterDefinition.value": "Value of parameter.",
"MediaGraphInstanceCollection": "Collection of graph instances.",
"MediaGraphInstanceCollection.value": "Collection of graph instances.",
"MediaGraphInstanceCollection.@continuationToken": "Continuation token to use in subsequent calls to enumerate through the graph instance collection (when the collection contains too many results to return in one response).",
"MediaGraphTopologyCollection": "Collection of graph topologies.",
"MediaGraphTopologyCollection.value": "Collection of graph topologies.",
"MediaGraphTopologyCollection.@continuationToken": "Continuation token to use in subsequent calls to enumerate through the graph topologies collection (when the collection contains too many results to return in one response).",
"MediaGraphTopology": "Describes a graph topology.",
"MediaGraphTopologyProperties": "Describes the properties of a graph topology.",
"MediaGraphSystemData": "Graph system data.",
"MediaGraphSystemData.createdAt": "The timestamp of resource creation (UTC).",
"MediaGraphSystemData.lastModifiedAt": "The timestamp of resource last modification (UTC).",
"MediaGraphParameterDeclaration": "The declaration of a parameter in the graph topology. A graph topology can be authored with parameters. Then, during graph instance creation, the value for those parameters can be specified. This allows the same graph topology to be used as a blueprint for multiple graph instances with different values for the parameters.",
"MediaGraphParameterDeclaration.name": "The name of the parameter.",
"MediaGraphParameterDeclaration.type.String": "A string parameter value.",
"MediaGraphParameterDeclaration.type.SecretString": "A string to hold sensitive information as parameter value.",
"MediaGraphParameterDeclaration.type.Int": "A 32-bit signed integer as parameter value.",
"MediaGraphParameterDeclaration.type.Double": "A 64-bit double-precision floating point type as parameter value.",
"MediaGraphParameterDeclaration.type.Bool": "A boolean value that is either true or false.",
"MediaGraphParameterDeclaration.description": "Description of the parameter.",
"MediaGraphParameterDeclaration.default": "The default value for the parameter, to be used if the graph instance does not specify a value.",
"MediaGraphSource": "Media graph source.",
"MediaGraphSource.@type": "The type of the source node. The discriminator for derived types.",
"MediaGraphSource.name": "The name to be used for this source node.",
"MediaGraphRtspSource": "Enables a graph to capture media from a RTSP server.",
"MediaGraphRtspSource.transport": "Underlying RTSP transport. This is used to enable or disable HTTP tunneling.",
"MediaGraphRtspSource.transport.Http": "HTTP/HTTPS transport. This should be used when HTTP tunneling is desired.",
"MediaGraphRtspSource.transport.Tcp": "TCP transport. This should be used when HTTP tunneling is NOT desired.",
"MediaGraphRtspSource.endpoint": "RTSP endpoint of the stream that is being connected to.",
"MediaGraphIoTHubMessageSource": "Enables a graph to receive messages via routes declared in the IoT Edge deployment manifest.",
"MediaGraphIoTHubMessageSource.hubInputName": "Name of the input path where messages can be routed to (via routes declared in the IoT Edge deployment manifest).",
"MediaGraphIoTHubMessageSink": "Enables a graph to publish messages that can be delivered via routes declared in the IoT Edge deployment manifest.",
"MediaGraphIoTHubMessageSink.hubOutputName": "Name of the output path to which the graph will publish message. These messages can then be delivered to desired destinations by declaring routes referencing the output path in the IoT Edge deployment manifest.",
"MediaGraphEndpoint": "Base class for endpoints.",
"MediaGraphEndpoint.@type": "The discriminator for derived types.",
"MediaGraphEndpoint.credentials": "Polymorphic credentials to be presented to the endpoint.",
"MediaGraphEndpoint.url": "Url for the endpoint.",
"MediaGraphCredentials": "Credentials to present during authentication.",
"MediaGraphCredentials.@type": "The discriminator for derived types.",
"MediaGraphUsernamePasswordCredentials": "Username/password credential pair.",
"MediaGraphUsernamePasswordCredentials.username": "Username for a username/password pair.",
"MediaGraphUsernamePasswordCredentials.password": "Password for a username/password pair.",
"MediaGraphHttpHeaderCredentials": "Http header service credentials.",
"MediaGraphHttpHeaderCredentials.headerName": "HTTP header name.",
"MediaGraphHttpHeaderCredentials.headerValue": "HTTP header value.",
"MediaGraphUnsecuredEndpoint": "An endpoint that the media graph can connect to, with no encryption in transit.",
"MediaGraphTlsEndpoint": "An endpoint that the graph can connect to, which must be connected over TLS/SSL.",
"MediaGraphTlsEndpoint.trustedCertificates": "Trusted certificates when authenticating a TLS connection. Null designates that Azure Media Service's source of trust should be used.",
"MediaGraphTlsEndpoint.validationOptions": "Validation options to use when authenticating a TLS connection. By default, strict validation is used.",
"MediaGraphCertificateSource": "Base class for certificate sources.",
"MediaGraphCertificateSource.@type": "The discriminator for derived types.",
"MediaGraphTlsValidationOptions": "Options for controlling the authentication of TLS endpoints.",
"MediaGraphTlsValidationOptions.ignoreHostname": "Boolean value ignoring the host name (common name) during validation.",
"MediaGraphTlsValidationOptions.ignoreSignature": "Boolean value ignoring the integrity of the certificate chain at the current time.",
"MediaGraphPemCertificateList": "A list of PEM formatted certificates.",
"MediaGraphPemCertificateList.certificates": "PEM formatted public certificates one per entry.",
"MediaGraphSink": "Enables a media graph to write media data to a destination outside of the Live Video Analytics IoT Edge module.",
"MediaGraphSink.@type": "The discriminator for derived types.",
"MediaGraphSink.name": "Name to be used for the media graph sink.",
"MediaGraphSink.inputs": "An array of the names of the other nodes in the media graph, the outputs of which are used as input for this sink node.",
"MediaGraphNodeInput": "Represents the input to any node in a media graph.",
"MediaGraphNodeInput.nodeName": "The name of another node in the media graph, the output of which is used as input to this node.",
"MediaGraphNodeInput.outputSelectors": "Allows for the selection of particular streams from another node.",
"MediaGraphOutputSelector": "Allows for the selection of particular streams from another node.",
"MediaGraphOutputSelector.property": "The stream property to compare with.",
"MediaGraphOutputSelector.property.mediaType": "The stream's MIME type or subtype.",
"MediaGraphOutputSelector.operator": "The operator to compare streams by.",
"MediaGraphOutputSelector.operator.is": "A media type is the same type or a subtype.",
"MediaGraphOutputSelector.operator.isNot": "A media type is not the same type or a subtype.",
"MediaGraphOutputSelector.value": "Value to compare against.",
"MediaGraphFileSink": "Enables a media graph to write/store media (video and audio) to a file on the Edge device.",
"MediaGraphFileSink.filePathPattern": "Absolute file path pattern for creating new files on the Edge device.",
"MediaGraphAssetSink": "Enables a graph to record media to an Azure Media Services asset, for subsequent playback.",
"MediaGraphAssetSink.assetNamePattern": "A name pattern when creating new assets.",
"MediaGraphAssetSink.segmentLength": "When writing media to an asset, wait until at least this duration of media has been accumulated on the Edge. Expressed in increments of 30 seconds, with a minimum of 30 seconds and a recommended maximum of 5 minutes.",
"MediaGraphAssetSink.localMediaCachePath": "Path to a local file system directory for temporary caching of media, before writing to an Asset. Used when the Edge device is temporarily disconnected from Azure.",
"MediaGraphAssetSink.localMediaCacheMaximumSizeMiB": "Maximum amount of disk space that can be used for temporary caching of media.",
"MediaGraphProcessor": "A node that represents the desired processing of media in a graph. Takes media and/or events as inputs, and emits media and/or event as output.",
"MediaGraphProcessor.@type": "The discriminator for derived types.",
"MediaGraphProcessor.name": "The name for this processor node.",
"MediaGraphProcessor.inputs": "An array of the names of the other nodes in the media graph, the outputs of which are used as input for this processor node.",
"MediaGraphMotionDetectionProcessor": "A node that accepts raw video as input, and detects if there are moving objects present. If so, then it emits an event, and allows frames where motion was detected to pass through. Other frames are blocked/dropped.",
"MediaGraphMotionDetectionProcessor.sensitivity": "Enumeration that specifies the sensitivity of the motion detection processor.",
"MediaGraphMotionDetectionProcessor.sensitivity.Low": "Low Sensitivity.",
"MediaGraphMotionDetectionProcessor.sensitivity.Medium": "Medium Sensitivity.",
"MediaGraphMotionDetectionProcessor.sensitivity.High": "High Sensitivity.",
"MediaGraphMotionDetectionProcessor.outputMotionRegion": "Indicates whether the processor should detect and output the regions, within the video frame, where motion was detected. Default is true.",
"MediaGraphExtensionProcessorBase": "Processor that allows for extensions, outside of the Live Video Analytics Edge module, to be integrated into the graph. It is the base class for various different kinds of extension processor types.",
"MediaGraphExtensionProcessorBase.endpoint": "Endpoint to which this processor should connect.",
"MediaGraphExtensionProcessorBase.image": "Describes the parameters of the image that is sent as input to the endpoint.",
"MediaGraphHttpExtension": "A processor that allows the media graph to send video frames (mostly at low frame rates e.g. <5 fps) to external inference container by leveraging HTTP based RESTful API. It then retrieves the inference results and relays them downstream to the next node.",
"MediaGraphImage": "Describes the properties of an image frame.",
"MediaGraphImageScale": "The scaling mode for the image.",
"MediaGraphImageScale.mode": "Describes the modes for scaling an input video frame into an image, before it is sent to an inference engine.",
"MediaGraphImageScale.mode.PreserveAspectRatio": "Use the same aspect ratio as the input frame.",
"MediaGraphImageScale.mode.Pad": "Center pad the input frame to match the given dimensions.",
"MediaGraphImageScale.mode.Stretch": "Stretch input frame to match given dimensions.",
"MediaGraphImageScale.width": "The desired output width of the image.",
"MediaGraphImageScale.height": "The desired output height of the image.",
"MediaGraphImageFormat": "Encoding settings for an image.",
"MediaGraphImageFormat.@type": "The discriminator for derived types.",
"MediaGraphImageFormatRaw": "Encoding settings for raw images.",
"MediaGraphImageFormatRaw.pixelFormat.Yuv420p": "Planar YUV 4:2:0, 12bpp, (1 Cr and Cb sample per 2x2 Y samples).",
"MediaGraphImageFormatRaw.pixelFormat.Rgb565be": "Packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), big-endian.",
"MediaGraphImageFormatRaw.pixelFormat.Rgb565le": "Packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), little-endian.",
"MediaGraphImageFormatRaw.pixelFormat.Rgb555be": "Packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), big-endian , X=unused/undefined.",
"MediaGraphImageFormatRaw.pixelFormat.Rgb555le": "Packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), little-endian, X=unused/undefined.",
"MediaGraphImageFormatRaw.pixelFormat.Rgb24": "Packed RGB 8:8:8, 24bpp, RGBRGB.",
"MediaGraphImageFormatRaw.pixelFormat.Bgr24": "Packed RGB 8:8:8, 24bpp, BGRBGR.",
"MediaGraphImageFormatRaw.pixelFormat.Argb": "Packed ARGB 8:8:8:8, 32bpp, ARGBARGB.",
"MediaGraphImageFormatRaw.pixelFormat.Rgba": "Packed RGBA 8:8:8:8, 32bpp, RGBARGBA.",
"MediaGraphImageFormatRaw.pixelFormat.Abgr": "Packed ABGR 8:8:8:8, 32bpp, ABGRABGR.",
"MediaGraphImageFormatRaw.pixelFormat.Bgra": "Packed BGRA 8:8:8:8, 32bpp, BGRABGRA.",
"MediaGraphImageFormatEncoded": "Allowed formats for the image.",
"MediaGraphImageFormatEncoded.encoding": "The different encoding formats that can be used for the image.",
"MediaGraphImageFormatEncoded.encoding.Jpeg": "JPEG image format.",
"MediaGraphImageFormatEncoded.encoding.Bmp": "BMP image format.",
"MediaGraphImageFormatEncoded.encoding.Png": "PNG image format.",
"MediaGraphImageFormatEncoded.quality": "The image quality (used for JPEG only). Value must be between 0 to 100 (best quality).",
"MediaGraphSignalGateProcessor": "A signal gate determines when to block (gate) incoming media, and when to allow it through. It gathers input events over the activationEvaluationWindow, and determines whether to open or close the gate.",
"MediaGraphSignalGateProcessor.activationEvaluationWindow": "The period of time over which the gate gathers input events, before evaluating them.",
"MediaGraphSignalGateProcessor.activationSignalOffset": "Signal offset once the gate is activated (can be negative). It is an offset between the time the event is received, and the timestamp of the first media sample (eg. video frame) that is allowed through by the gate.",
"MediaGraphSignalGateProcessor.minimumActivationTime": "The minimum period for which the gate remains open, in the absence of subsequent triggers (events).",
"MediaGraphSignalGateProcessor.maximumActivationTime": "The maximum period for which the gate remains open, in the presence of subsequent events.",
"MediaGraphFrameRateFilterProcessor": "Limits the frame rate on the input video stream based on the maximumFps property.",
"MediaGraphFrameRateFilterProcessor.maximumFps": "Ensures that the frame rate of the video leaving this processor does not exceed this limit."
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,302 +1,338 @@
export const sampleTopology = {
"@apiVersion": "1.0",
name: "EVRtoAssetsOnMotionDetecion",
properties: {
description: "Event-based video recording to Assets based on motion events",
parameters: [{
name: "rtspUserName",
type: "String",
description: "rtsp source user name.",
default: "dummyUserName",
},
{
name: "rtspPassword",
type: "String",
description: "rtsp source password.",
default: "dummyPassword",
},
{
name: "rtspUrl",
type: "String",
description: "rtsp Url",
},
{
name: "motionSensitivity",
type: "String",
description: "motion detection sensitivity",
default: "medium",
},
{
name: "hubSinkOutputName",
type: "String",
description: "hub sink output name",
default: "iothubsinkoutput",
},
],
sources: [{
"@type": "#Microsoft.Media.MediaGraphRtspSource",
name: "rtspSource",
endpoint: {
"@type": "#Microsoft.Media.MediaGraphUnsecuredEndpoint",
url: "${rtspUrl}",
credentials: {
"@type": "#Microsoft.Media.MediaGraphUsernamePasswordCredentials",
username: "${rtspUserName}",
password: "${rtspPassword}",
},
},
}, ],
processors: [{
"@type": "#Microsoft.Media.MediaGraphMotionDetectionProcessor",
name: "motionDetection",
sensitivity: "${motionSensitivity}",
inputs: [{
nodeName: "rtspSource",
}, ],
},
{
"@type": "#Microsoft.Media.MediaGraphSignalGateProcessor",
name: "signalGateProcessor",
inputs: [{
nodeName: "motionDetection",
},
{
nodeName: "rtspSource",
},
"@apiVersion": "1.0",
name: "EVRtoAssetsOnMotionDetecion",
properties: {
description: "Event-based video recording to Assets based on motion events",
parameters: [
{
name: "rtspUserName",
type: "String",
description: "rtsp source user name.",
default: "dummyUserName"
},
{
name: "rtspPassword",
type: "String",
description: "rtsp source password.",
default: "dummyPassword"
},
{
name: "rtspUrl",
type: "String",
description: "rtsp Url"
},
{
name: "motionSensitivity",
type: "String",
description: "motion detection sensitivity",
default: "medium"
},
{
name: "hubSinkOutputName",
type: "String",
description: "hub sink output name",
default: "iothubsinkoutput"
}
],
activationEvaluationWindow: "PT1S",
activationSignalOffset: "PT0S",
minimumActivationTime: "PT30S",
maximumActivationTime: "PT30S",
},
],
sinks: [{
"@type": "#Microsoft.Media.MediaGraphAssetSink",
name: "assetSink",
assetNamePattern: "sampleAssetFromEVR-LVAEdge-${System.DateTime}",
segmentLength: "PT0M30S",
localMediaCacheMaximumSizeMiB: "2048",
localMediaCachePath: "/var/lib/azuremediaservices/tmp/",
inputs: [{
nodeName: "signalGateProcessor",
}, ],
},
{
"@type": "#Microsoft.Media.MediaGraphIoTHubMessageSink",
name: "hubSink",
hubOutputName: "${hubSinkOutputName}",
inputs: [{
nodeName: "motionDetection",
}, ],
},
],
},
sources: [
{
"@type": "#Microsoft.Media.MediaGraphRtspSource",
name: "rtspSource",
endpoint: {
"@type": "#Microsoft.Media.MediaGraphUnsecuredEndpoint",
url: "${rtspUrl}",
credentials: {
"@type": "#Microsoft.Media.MediaGraphUsernamePasswordCredentials",
username: "${rtspUserName}",
password: "${rtspPassword}"
}
}
}
],
processors: [
{
"@type": "#Microsoft.Media.MediaGraphMotionDetectionProcessor",
name: "motionDetection",
sensitivity: "${motionSensitivity}",
inputs: [
{
nodeName: "rtspSource"
}
]
},
{
"@type": "#Microsoft.Media.MediaGraphSignalGateProcessor",
name: "signalGateProcessor",
inputs: [
{
nodeName: "motionDetection"
},
{
nodeName: "rtspSource"
}
],
activationEvaluationWindow: "PT1S",
activationSignalOffset: "PT0S",
minimumActivationTime: "PT30S",
maximumActivationTime: "PT30S"
}
],
sinks: [
{
"@type": "#Microsoft.Media.MediaGraphAssetSink",
name: "assetSink",
assetNamePattern: "sampleAssetFromEVR-LVAEdge-${System.DateTime}",
segmentLength: "PT0M30S",
localMediaCacheMaximumSizeMiB: "2048",
localMediaCachePath: "/var/lib/azuremediaservices/tmp/",
inputs: [
{
nodeName: "signalGateProcessor"
}
]
},
{
"@type": "#Microsoft.Media.MediaGraphIoTHubMessageSink",
name: "hubSink",
hubOutputName: "${hubSinkOutputName}",
inputs: [
{
nodeName: "motionDetection"
}
]
}
]
}
};
export const sampleTopology2 = {
"@apiVersion": "1.0",
name: "MotionDetection",
properties: {
description: "Analyzing live video to detect motion and emit events",
parameters: [{
name: "rtspUserName",
type: "String",
description: "rtsp source user name.",
default: "dummyUserName",
},
{
name: "rtspPassword",
type: "String",
description: "rtsp source password.",
default: "dummyPassword",
},
{
name: "rtspUrl",
type: "String",
description: "rtsp Url",
},
],
sources: [{
"@type": "#Microsoft.Media.MediaGraphRtspSource",
name: "rtspSource",
endpoint: {
"@type": "#Microsoft.Media.MediaGraphUnsecuredEndpoint",
url: "${rtspUrl}",
credentials: {
"@type": "#Microsoft.Media.MediaGraphUsernamePasswordCredentials",
username: "${rtspUserName}",
password: "${rtspPassword}",
},
},
}, ],
processors: [{
"@type": "#Microsoft.Media.MediaGraphMotionDetectionProcessor",
name: "motionDetection",
sensitivity: "medium",
inputs: [{
nodeName: "rtspSource",
}, ],
}, ],
sinks: [{
"@type": "#Microsoft.Media.MediaGraphIoTHubMessageSink",
name: "hubSink",
hubOutputName: "inferenceOutput",
inputs: [{
nodeName: "motionDetection",
}, ],
}, ],
},
"@apiVersion": "1.0",
name: "MotionDetection",
properties: {
description: "Analyzing live video to detect motion and emit events",
parameters: [
{
name: "rtspUserName",
type: "String",
description: "rtsp source user name.",
default: "dummyUserName"
},
{
name: "rtspPassword",
type: "String",
description: "rtsp source password.",
default: "dummyPassword"
},
{
name: "rtspUrl",
type: "String",
description: "rtsp Url"
}
],
sources: [
{
"@type": "#Microsoft.Media.MediaGraphRtspSource",
name: "rtspSource",
endpoint: {
"@type": "#Microsoft.Media.MediaGraphUnsecuredEndpoint",
url: "${rtspUrl}",
credentials: {
"@type": "#Microsoft.Media.MediaGraphUsernamePasswordCredentials",
username: "${rtspUserName}",
password: "${rtspPassword}"
}
}
}
],
processors: [
{
"@type": "#Microsoft.Media.MediaGraphMotionDetectionProcessor",
name: "motionDetection",
sensitivity: "medium",
inputs: [
{
nodeName: "rtspSource"
}
]
}
],
sinks: [
{
"@type": "#Microsoft.Media.MediaGraphIoTHubMessageSink",
name: "hubSink",
hubOutputName: "inferenceOutput",
inputs: [
{
nodeName: "motionDetection"
}
]
}
]
}
};
export const evrHubMessageAssets = {
"@apiVersion": "1.0",
"name": "EVRtoAssetsOnObjDetect",
"properties": {
"description": "Event-based video recording to Assets based on specific objects being detected by external inference engine",
"parameters": [{
"name": "rtspUserName",
"type": "String",
"description": "rtsp source user name.",
"default": "dummyUserName"
},
{
"name": "rtspPassword",
"type": "String",
"description": "rtsp source password.",
"default": "dummyPassword"
},
{
"name": "rtspUrl",
"type": "String",
"description": "rtsp Url"
},
{
"name": "hubSourceInput",
"type": "String",
"description": "input name for hub source",
"default": "recordingTrigger"
},
{
"name": "inferencingUrl",
"type": "String",
"description": "inferencing Url",
"default": "http://yolov3/score"
},
{
"name": "inferencingUserName",
"type": "String",
"description": "inferencing endpoint user name.",
"default": "dummyUserName"
},
{
"name": "inferencingPassword",
"type": "String",
"description": "inferencing endpoint password.",
"default": "dummyPassword"
},
{
"name": "imageEncoding",
"type": "String",
"description": "image encoding for frames",
"default": "bmp"
},
{
"name": "hubSinkOutputName",
"type": "String",
"description": "hub sink output name",
"default": "detectedObjects"
}
],
"sources": [{
"@type": "#Microsoft.Media.MediaGraphRtspSource",
"name": "rtspSource",
"endpoint": {
"@type": "#Microsoft.Media.MediaGraphUnsecuredEndpoint",
"url": "${rtspUrl}",
"credentials": {
"@type": "#Microsoft.Media.MediaGraphUsernamePasswordCredentials",
"username": "${rtspUserName}",
"password": "${rtspPassword}"
}
}
},
{
"@type": "#Microsoft.Media.MediaGraphIoTHubMessageSource",
"name": "iotMessageSource",
"hubInputName": "${hubSourceInput}"
}
],
"processors": [{
"@type": "#Microsoft.Media.MediaGraphSignalGateProcessor",
"name": "signalGateProcessor",
"inputs": [{
"nodeName": "iotMessageSource"
},
{
"nodeName": "rtspSource"
}
"@apiVersion": "1.0",
name: "EVRtoAssetsOnObjDetect",
properties: {
description: "Event-based video recording to Assets based on specific objects being detected by external inference engine",
parameters: [
{
name: "rtspUserName",
type: "String",
description: "rtsp source user name.",
default: "dummyUserName"
},
{
name: "rtspPassword",
type: "String",
description: "rtsp source password.",
default: "dummyPassword"
},
{
name: "rtspUrl",
type: "String",
description: "rtsp Url"
},
{
name: "hubSourceInput",
type: "String",
description: "input name for hub source",
default: "recordingTrigger"
},
{
name: "inferencingUrl",
type: "String",
description: "inferencing Url",
default: "http://yolov3/score"
},
{
name: "inferencingUserName",
type: "String",
description: "inferencing endpoint user name.",
default: "dummyUserName"
},
{
name: "inferencingPassword",
type: "String",
description: "inferencing endpoint password.",
default: "dummyPassword"
},
{
name: "imageEncoding",
type: "String",
description: "image encoding for frames",
default: "bmp"
},
{
name: "hubSinkOutputName",
type: "String",
description: "hub sink output name",
default: "detectedObjects"
}
],
"activationEvaluationWindow": "PT1S",
"activationSignalOffset": "-PT5S",
"minimumActivationTime": "PT30S",
"maximumActivationTime": "PT30S"
},
{
"@type": "#Microsoft.Media.MediaGraphFrameRateFilterProcessor",
"name": "frameRateFilter",
"maximumFps": "1.0",
"inputs": [{
"nodeName": "rtspSource"
}]
},
{
"@type": "#Microsoft.Media.MediaGraphHttpExtension",
"name": "inferenceClient",
"endpoint": {
"@type": "#Microsoft.Media.MediaGraphUnsecuredEndpoint",
"url": "${inferencingUrl}",
"credentials": {
"@type": "#Microsoft.Media.MediaGraphUsernamePasswordCredentials",
"username": "${inferencingUserName}",
"password": "${inferencingPassword}"
}
},
"image": {
"scale": {
"mode": "preserveAspectRatio",
"width": "416",
"height": "416"
},
"format": {
"@type": "#Microsoft.Media.MediaGraphImageFormatEncoded",
"encoding": "${imageEncoding}"
}
},
"inputs": [{
"nodeName": "frameRateFilter"
}]
}
],
"sinks": [{
"@type": "#Microsoft.Media.MediaGraphIoTHubMessageSink",
"name": "hubSink",
"hubOutputName": "${hubSinkOutputName}",
"inputs": [{
"nodeName": "inferenceClient"
}]
},
{
"@type": "#Microsoft.Media.MediaGraphAssetSink",
"name": "assetSink",
"assetNamePattern": "sampleAssetFromEVR-LVAEdge-${System.DateTime}",
"segmentLength": "PT30S",
"LocalMediaCacheMaximumSizeMiB": "2048",
"localMediaCachePath": "/var/lib/azuremediaservices/tmp/",
"inputs": [{
"nodeName": "signalGateProcessor"
}]
}
]
}
};
sources: [
{
"@type": "#Microsoft.Media.MediaGraphRtspSource",
name: "rtspSource",
endpoint: {
"@type": "#Microsoft.Media.MediaGraphUnsecuredEndpoint",
url: "${rtspUrl}",
credentials: {
"@type": "#Microsoft.Media.MediaGraphUsernamePasswordCredentials",
username: "${rtspUserName}",
password: "${rtspPassword}"
}
}
},
{
"@type": "#Microsoft.Media.MediaGraphIoTHubMessageSource",
name: "iotMessageSource",
hubInputName: "${hubSourceInput}"
}
],
processors: [
{
"@type": "#Microsoft.Media.MediaGraphSignalGateProcessor",
name: "signalGateProcessor",
inputs: [
{
nodeName: "iotMessageSource"
},
{
nodeName: "rtspSource"
}
],
activationEvaluationWindow: "PT1S",
activationSignalOffset: "-PT5S",
minimumActivationTime: "PT30S",
maximumActivationTime: "PT30S"
},
{
"@type": "#Microsoft.Media.MediaGraphFrameRateFilterProcessor",
name: "frameRateFilter",
maximumFps: "1.0",
inputs: [
{
nodeName: "rtspSource"
}
]
},
{
"@type": "#Microsoft.Media.MediaGraphHttpExtension",
name: "inferenceClient",
endpoint: {
"@type": "#Microsoft.Media.MediaGraphUnsecuredEndpoint",
url: "${inferencingUrl}",
credentials: {
"@type": "#Microsoft.Media.MediaGraphUsernamePasswordCredentials",
username: "${inferencingUserName}",
password: "${inferencingPassword}"
}
},
image: {
scale: {
mode: "preserveAspectRatio",
width: "416",
height: "416"
},
format: {
"@type": "#Microsoft.Media.MediaGraphImageFormatEncoded",
encoding: "${imageEncoding}"
}
},
inputs: [
{
nodeName: "frameRateFilter"
}
]
}
],
sinks: [
{
"@type": "#Microsoft.Media.MediaGraphIoTHubMessageSink",
name: "hubSink",
hubOutputName: "${hubSinkOutputName}",
inputs: [
{
nodeName: "inferenceClient"
}
]
},
{
"@type": "#Microsoft.Media.MediaGraphAssetSink",
name: "assetSink",
assetNamePattern: "sampleAssetFromEVR-LVAEdge-${System.DateTime}",
segmentLength: "PT30S",
LocalMediaCacheMaximumSizeMiB: "2048",
localMediaCachePath: "/var/lib/azuremediaservices/tmp/",
inputs: [
{
nodeName: "signalGateProcessor"
}
]
}
]
}
};

Просмотреть файл

@ -1,55 +1,55 @@
import * as React from "react";
import {
CanvasMenu,
ContextMenu as $ContextMenu,
EdgeMenu,
MultiMenu,
NodeMenu,
usePropsAPI,
CanvasMenu,
ContextMenu as $ContextMenu,
EdgeMenu,
MultiMenu,
NodeMenu,
usePropsAPI
} from "@vienna/react-dag-editor";
import Localizer from "../../localization/Localizer";
export const ContextMenu: React.FunctionComponent = () => {
const propsAPI = usePropsAPI();
const propsAPI = usePropsAPI();
const onCopyClick = () => {
propsAPI.copy();
};
const onPasteClick = (evt: React.MouseEvent) => {
propsAPI.paste(evt.clientX, evt.clientY);
};
const onDeleteClick = () => {
propsAPI.delete();
};
const onCopyClick = () => {
propsAPI.copy();
};
const onPasteClick = (evt: React.MouseEvent) => {
propsAPI.paste(evt.clientX, evt.clientY);
};
const onDeleteClick = () => {
propsAPI.delete();
};
return (
<$ContextMenu className="context-menu">
<NodeMenu>
<div onClick={onCopyClick} role="button">
{Localizer.l("copy")}
</div>
<div onClick={onDeleteClick} role="button">
{Localizer.l("delete")}
</div>
</NodeMenu>
<EdgeMenu>
<div onClick={onDeleteClick} role="button">
{Localizer.l("delete")}
</div>
</EdgeMenu>
<MultiMenu>
<div onClick={onCopyClick} role="button">
{Localizer.l("copySelected")}
</div>
<div onClick={onDeleteClick} role="button">
{Localizer.l("deleteSelected")}
</div>
</MultiMenu>
<CanvasMenu>
<div onClick={onPasteClick} role="button">
{Localizer.l("paste")}
</div>
</CanvasMenu>
</$ContextMenu>
);
return (
<$ContextMenu className="context-menu">
<NodeMenu>
<div onClick={onCopyClick} role="button">
{Localizer.l("copy")}
</div>
<div onClick={onDeleteClick} role="button">
{Localizer.l("delete")}
</div>
</NodeMenu>
<EdgeMenu>
<div onClick={onDeleteClick} role="button">
{Localizer.l("delete")}
</div>
</EdgeMenu>
<MultiMenu>
<div onClick={onCopyClick} role="button">
{Localizer.l("copySelected")}
</div>
<div onClick={onDeleteClick} role="button">
{Localizer.l("deleteSelected")}
</div>
</MultiMenu>
<CanvasMenu>
<div onClick={onPasteClick} role="button">
{Localizer.l("paste")}
</div>
</CanvasMenu>
</$ContextMenu>
);
};

Просмотреть файл

@ -1,70 +1,58 @@
import * as React from "react";
import {
getCurvePathD,
GraphEdgeState,
hasState,
ICanvasEdge,
IEdgeConfig,
IEdgeDrawArgs,
IPropsAPI,
ITheme,
getCurvePathD,
GraphEdgeState,
hasState,
ICanvasEdge,
IEdgeConfig,
IEdgeDrawArgs,
IPropsAPI,
ITheme
} from "@vienna/react-dag-editor";
export class CustomEdgeConfig implements IEdgeConfig {
private readonly propsAPI: IPropsAPI;
private readonly propsAPI: IPropsAPI;
constructor(propsAPI: IPropsAPI) {
this.propsAPI = propsAPI;
}
constructor(propsAPI: IPropsAPI) {
this.propsAPI = propsAPI;
}
private isSelected(edge: ICanvasEdge) {
return hasState(
GraphEdgeState.selected |
GraphEdgeState.activated |
GraphEdgeState.connectedToSelected
)(edge.state);
}
private isSelected(edge: ICanvasEdge) {
return hasState(GraphEdgeState.selected | GraphEdgeState.activated | GraphEdgeState.connectedToSelected)(edge.state);
}
private getColor(edge: ICanvasEdge, theme: ITheme) {
return this.isSelected(edge) ? theme.edgeColorSelected : theme.edgeColor;
}
private getColor(edge: ICanvasEdge, theme: ITheme) {
return this.isSelected(edge) ? theme.edgeColorSelected : theme.edgeColor;
}
public getStyle(edge: ICanvasEdge, theme: ITheme): React.CSSProperties {
return {
cursor: "pointer",
stroke: this.getColor(edge, theme),
strokeWidth: this.isSelected(edge) ? 3 : 2,
};
}
public getStyle(edge: ICanvasEdge, theme: ITheme): React.CSSProperties {
return {
cursor: "pointer",
stroke: this.getColor(edge, theme),
strokeWidth: this.isSelected(edge) ? 3 : 2
};
}
public render(args: IEdgeDrawArgs): React.ReactNode {
const { theme, model: edge, x1, x2, y1, y2 } = args;
const style = this.getStyle ? this.getStyle(edge, args.theme) : {};
public render(args: IEdgeDrawArgs): React.ReactNode {
const { theme, model: edge, x1, x2, y1, y2 } = args;
const style = this.getStyle ? this.getStyle(edge, args.theme) : {};
const fixedY2 = y2 - 12;
const triangleHeadPoints = `${x2 - 3} ${fixedY2}, ${
x2 + 3
} ${fixedY2}, ${x2} ${fixedY2 + 6}`;
const color = this.getColor(edge, theme);
const fixedY2 = y2 - 12;
const triangleHeadPoints = `${x2 - 3} ${fixedY2}, ${x2 + 3} ${fixedY2}, ${x2} ${fixedY2 + 6}`;
const color = this.getColor(edge, theme);
return (
<>
<path
key={edge.id}
d={getCurvePathD(x2, x1, fixedY2, y1)}
fill="none"
style={style}
id={`edge${edge.id}`}
/>
<polygon
points={triangleHeadPoints}
style={{
stroke: color,
fill: color,
strokeWidth: this.isSelected(edge) ? 3 : 2,
}}
/>
</>
);
}
return (
<>
<path key={edge.id} d={getCurvePathD(x2, x1, fixedY2, y1)} fill="none" style={style} id={`edge${edge.id}`} />
<polygon
points={triangleHeadPoints}
style={{
stroke: color,
fill: color,
strokeWidth: this.isSelected(edge) ? 3 : 2
}}
/>
</>
);
}
}

Просмотреть файл

@ -1,136 +1,125 @@
import { Stack, TextField } from "office-ui-fabric-react";
import * as React from "react";
import {
CanvasMouseMode,
ICanvasData,
isSupported,
IZoomPanSettings,
ReactDagEditor,
RegisterNode,
RegisterPort,
withDefaultPortsPosition,
CanvasMouseMode,
ICanvasData,
isSupported,
IZoomPanSettings,
ReactDagEditor,
RegisterNode,
RegisterPort,
withDefaultPortsPosition
} from "@vienna/react-dag-editor";
import Graph from "../../graph/Graph";
import Localizer from "../../localization/Localizer";
import { graphTheme as theme } from "../editorTheme";
import { ContextMenu } from "./ContextMenu";
import { GraphPanel } from "./GraphPanel";
import { InnerGraph } from "./InnerGraph";
import { NodeBase } from "./NodeBase";
import { modulePort } from "./Port";
import Localizer from "../../localization/Localizer";
import Graph from "../../graph/Graph";
import { Toolbar } from "./Toolbar";
interface IGraphInstanceProps {
graph: Graph;
zoomPanSettings: IZoomPanSettings;
vsCodeSetState: (state: any) => void;
graph: Graph;
zoomPanSettings: IZoomPanSettings;
vsCodeSetState: (state: any) => void;
}
export const GraphInstance: React.FunctionComponent<IGraphInstanceProps> = (
props
) => {
const graph = props.graph;
const [data, setData] = React.useState<ICanvasData>(graph.getICanvasData());
const [zoomPanSettings, setZoomPanSettings] = React.useState<
IZoomPanSettings
>(props.zoomPanSettings);
const [graphInstanceName, setGraphInstanceName] = React.useState<string>(
graph.getName()
);
const [graphDescription, setGraphDescription] = React.useState<string>(
graph.getDescription() || ""
);
export const GraphInstance: React.FunctionComponent<IGraphInstanceProps> = (props) => {
const graph = props.graph;
const [data, setData] = React.useState<ICanvasData>(graph.getICanvasData());
const [zoomPanSettings, setZoomPanSettings] = React.useState<IZoomPanSettings>(props.zoomPanSettings);
const [graphInstanceName, setGraphInstanceName] = React.useState<string>(graph.getName());
const [graphDescription, setGraphDescription] = React.useState<string>(graph.getDescription() || "");
// save state in VS Code when data or zoomPanSettings change
React.useEffect(() => {
props.vsCodeSetState({
graphData: { ...data, meta: graph.getTopology() },
zoomPanSettings,
});
}, [data, zoomPanSettings]);
// save state in VS Code when data or zoomPanSettings change
React.useEffect(() => {
props.vsCodeSetState({
graphData: { ...data, meta: graph.getTopology() },
zoomPanSettings
});
}, [data, zoomPanSettings]);
if (!isSupported()) {
return <h1>{Localizer.l("browserNotSupported")}</h1>;
}
const exportGraph = () => {
graph.setName(graphInstanceName);
graph.setDescription(graphDescription);
graph.setGraphDataFromICanvasData(data);
const topology = graph.getTopology();
console.log(topology);
};
const onNameChange = (event: React.FormEvent, newValue?: string) => {
if (newValue) {
setGraphInstanceName(newValue);
if (!isSupported()) {
return <h1>{Localizer.l("browserNotSupported")}</h1>;
}
};
const onDescriptionChange = (event: React.FormEvent, newValue?: string) => {
if (typeof newValue !== "undefined") {
setGraphDescription(newValue);
}
};
const exportGraph = () => {
graph.setName(graphInstanceName);
graph.setDescription(graphDescription);
graph.setGraphDataFromICanvasData(data);
const topology = graph.getTopology();
console.log(topology);
};
const panelStyles = {
root: {
boxSizing: "border-box" as const,
padding: 10,
overflowY: "auto" as const,
willChange: "transform",
height: "100vh",
width: 300,
background: "var(--vscode-editorWidget-background)",
borderRight: "1px solid var(--vscode-editorWidget-border)",
},
};
const onNameChange = (event: React.FormEvent, newValue?: string) => {
if (newValue) {
setGraphInstanceName(newValue);
}
};
return (
<ReactDagEditor theme={theme}>
<RegisterNode
name="module"
config={withDefaultPortsPosition(new NodeBase())}
/>
<RegisterPort name="modulePort" config={modulePort} />
<Stack horizontal>
<Stack.Item styles={panelStyles}>
<TextField
label={Localizer.l("sidebarGraphInstanceNameLabel")}
required
defaultValue={graphInstanceName}
placeholder={Localizer.l("sidebarGraphInstanceNamePlaceholder")}
onChange={onNameChange}
/>
<TextField
label={Localizer.l("sidebarGraphDescriptionLabel")}
defaultValue={graphDescription}
placeholder={Localizer.l("sidebarGraphDescriptionPlaceholder")}
onChange={onDescriptionChange}
/>
<GraphPanel data={graph.getTopology()} exportGraph={exportGraph} />
</Stack.Item>
<Stack.Item grow>
<Toolbar
name={graphInstanceName}
exportGraph={exportGraph}
closeEditor={() => {
alert("TODO: Close editor");
}}
/>
<Stack.Item grow>
<InnerGraph
data={data}
setData={setData}
zoomPanSettings={zoomPanSettings}
setZoomPanSettings={setZoomPanSettings}
canvasMouseMode={CanvasMouseMode.pan}
readOnly
/>
</Stack.Item>
</Stack.Item>
</Stack>
<ContextMenu />
</ReactDagEditor>
);
const onDescriptionChange = (event: React.FormEvent, newValue?: string) => {
if (typeof newValue !== "undefined") {
setGraphDescription(newValue);
}
};
const panelStyles = {
root: {
boxSizing: "border-box" as const,
padding: 10,
overflowY: "auto" as const,
willChange: "transform",
height: "100vh",
width: 300,
background: "var(--vscode-editorWidget-background)",
borderRight: "1px solid var(--vscode-editorWidget-border)"
}
};
return (
<ReactDagEditor theme={theme}>
<RegisterNode name="module" config={withDefaultPortsPosition(new NodeBase())} />
<RegisterPort name="modulePort" config={modulePort} />
<Stack horizontal>
<Stack.Item styles={panelStyles}>
<TextField
label={Localizer.l("sidebarGraphInstanceNameLabel")}
required
defaultValue={graphInstanceName}
placeholder={Localizer.l("sidebarGraphInstanceNamePlaceholder")}
onChange={onNameChange}
/>
<TextField
label={Localizer.l("sidebarGraphDescriptionLabel")}
defaultValue={graphDescription}
placeholder={Localizer.l("sidebarGraphDescriptionPlaceholder")}
onChange={onDescriptionChange}
/>
<GraphPanel data={graph.getTopology()} exportGraph={exportGraph} />
</Stack.Item>
<Stack.Item grow>
<Toolbar
name={graphInstanceName}
exportGraph={exportGraph}
closeEditor={() => {
alert("TODO: Close editor");
}}
/>
<Stack.Item grow>
<InnerGraph
data={data}
setData={setData}
zoomPanSettings={zoomPanSettings}
setZoomPanSettings={setZoomPanSettings}
canvasMouseMode={CanvasMouseMode.pan}
readOnly
/>
</Stack.Item>
</Stack.Item>
</Stack>
<ContextMenu />
</ReactDagEditor>
);
};

Просмотреть файл

@ -1,93 +1,79 @@
import * as React from "react";
import Localizer from "../../localization/Localizer";
import {
MediaGraphParameterDeclaration,
MediaGraphParameterType,
MediaGraphTopology,
MediaGraphParameterDeclaration,
MediaGraphParameterType,
MediaGraphTopology
} from "../../lva-sdk/lvaSDKtypes";
export interface IGraphPanelProps {
exportGraph: () => void;
data: MediaGraphTopology;
exportGraph: () => void;
data: MediaGraphTopology;
}
export const GraphPanel: React.FunctionComponent<IGraphPanelProps> = (
props
) => {
const [data, setData] = React.useState<MediaGraphTopology>(props.data);
export const GraphPanel: React.FunctionComponent<IGraphPanelProps> = (props) => {
const [data, setData] = React.useState<MediaGraphTopology>(props.data);
const { properties } = data;
const { parameters } = properties || {};
const { properties } = data;
const { parameters } = properties || {};
return (
<>
<h2>{Localizer.l("sidebarHeadingParameters")}</h2>
{parameters &&
parameters.map((parameter) => {
const key = "parameter-" + parameter.name;
return (
<div
key={key}
style={{
marginTop: 20,
}}
>
<label htmlFor={key}>
<strong>{parameter.name}</strong>
</label>
<p>
{parameter.description && Localizer.l(parameter.description)}
</p>
<GraphPanelEditField parameter={parameter} keyName={key} />
</div>
);
})}
</>
);
return (
<>
<h2>{Localizer.l("sidebarHeadingParameters")}</h2>
{parameters &&
parameters.map((parameter) => {
const key = "parameter-" + parameter.name;
return (
<div
key={key}
style={{
marginTop: 20
}}
>
<label htmlFor={key}>
<strong>{parameter.name}</strong>
</label>
<p>{parameter.description && Localizer.l(parameter.description)}</p>
<GraphPanelEditField parameter={parameter} keyName={key} />
</div>
);
})}
</>
);
};
interface IGraphPanelEditFieldProps {
parameter: MediaGraphParameterDeclaration;
keyName: string;
parameter: MediaGraphParameterDeclaration;
keyName: string;
}
const GraphPanelEditField: React.FunctionComponent<IGraphPanelEditFieldProps> = (
props
) => {
const { keyName, parameter } = props;
const [defaultValue, setDefaultValue] = React.useState<string>(
parameter.default || ""
);
const [type, setType] = React.useState<string>(parameter.type);
const GraphPanelEditField: React.FunctionComponent<IGraphPanelEditFieldProps> = (props) => {
const { keyName, parameter } = props;
const [defaultValue, setDefaultValue] = React.useState<string>(parameter.default || "");
const [type, setType] = React.useState<string>(parameter.type);
function handleChange(e: React.FormEvent) {
parameter.default = (e.target as any).value;
setDefaultValue(parameter.default || "");
if (!parameter.default) {
delete parameter.default;
function handleChange(e: React.FormEvent) {
parameter.default = (e.target as any).value;
setDefaultValue(parameter.default || "");
if (!parameter.default) {
delete parameter.default;
}
}
}
function handleTypeChange(e: React.FormEvent) {
parameter.type = (e.target as any).value;
setType(parameter.type);
}
function handleTypeChange(e: React.FormEvent) {
parameter.type = (e.target as any).value;
setType(parameter.type);
}
return (
<>
<select value={type} onChange={handleTypeChange}>
<option key="undefined">undefined</option>
{Object.keys(MediaGraphParameterType).map((value: string) => (
<option key={value}>{value}</option>
))}
</select>
<input
type="text"
id={keyName}
value={defaultValue}
placeholder={Localizer.l("optionalDefaultValue")}
onChange={handleChange}
/>
</>
);
return (
<>
<select value={type} onChange={handleTypeChange}>
<option key="undefined">undefined</option>
{Object.keys(MediaGraphParameterType).map((value: string) => (
<option key={value}>{value}</option>
))}
</select>
<input type="text" id={keyName} value={defaultValue} placeholder={Localizer.l("optionalDefaultValue")} onChange={handleChange} />
</>
);
};

Просмотреть файл

@ -1,189 +1,175 @@
import { Stack, TextField } from "office-ui-fabric-react";
import * as React from "react";
import {
CanvasMouseMode,
ICanvasData,
ICanvasNode,
isSupported,
IZoomPanSettings,
ReactDagEditor,
RegisterNode,
RegisterPort,
withDefaultPortsPosition,
CanvasMouseMode,
ICanvasData,
ICanvasNode,
isSupported,
IZoomPanSettings,
ReactDagEditor,
RegisterNode,
RegisterPort,
withDefaultPortsPosition
} from "@vienna/react-dag-editor";
import Graph from "../../graph/Graph";
import Localizer from "../../localization/Localizer";
import { graphTheme as theme } from "../editorTheme";
import { ContextMenu } from "./ContextMenu";
import { Toolbar } from "./Toolbar";
import { InnerGraph } from "./InnerGraph";
import { ItemPanel } from "./ItemPanel";
import { NodeBase } from "./NodeBase";
import { modulePort } from "./Port";
import Localizer from "../../localization/Localizer";
import Graph from "../../graph/Graph";
import { SampleSelectorTrigger } from "./SampleSelector/SampleSelectorTrigger";
import { Toolbar } from "./Toolbar";
interface IGraphTopologyProps {
graph: Graph;
zoomPanSettings: IZoomPanSettings;
vsCodeSetState: (state: any) => void;
graph: Graph;
zoomPanSettings: IZoomPanSettings;
vsCodeSetState: (state: any) => void;
}
export const GraphTopology: React.FunctionComponent<IGraphTopologyProps> = (
props
) => {
const { graph, vsCodeSetState } = props;
const [data, setData] = React.useState<ICanvasData>(graph.getICanvasData());
const [dirty, setDirty] = React.useState<boolean>(false);
const [zoomPanSettings, setZoomPanSettings] = React.useState<
IZoomPanSettings
>(props.zoomPanSettings);
const [graphTopologyName, setGraphTopologyName] = React.useState<string>(
graph.getName()
);
const [graphDescription, setGraphDescription] = React.useState<string>(
graph.getDescription() || ""
);
export const GraphTopology: React.FunctionComponent<IGraphTopologyProps> = (props) => {
const { graph, vsCodeSetState } = props;
const [data, setData] = React.useState<ICanvasData>(graph.getICanvasData());
const [dirty, setDirty] = React.useState<boolean>(false);
const [zoomPanSettings, setZoomPanSettings] = React.useState<IZoomPanSettings>(props.zoomPanSettings);
const [graphTopologyName, setGraphTopologyName] = React.useState<string>(graph.getName());
const [graphDescription, setGraphDescription] = React.useState<string>(graph.getDescription() || "");
// save state in VS Code when data or zoomPanSettings change
React.useEffect(() => {
vsCodeSetState({
graphData: { ...data, meta: graph.getTopology() },
zoomPanSettings,
// save state in VS Code when data or zoomPanSettings change
React.useEffect(() => {
vsCodeSetState({
graphData: { ...data, meta: graph.getTopology() },
zoomPanSettings
});
}, [data, zoomPanSettings]);
if (!isSupported()) {
return <h1>{Localizer.l("browserNotSupported")}</h1>;
}
function setTopology(topology: any) {
graph.setTopology(topology);
setData(graph.getICanvasData());
setDirty(false);
}
function onChange() {
setDirty(true);
}
// nodeNames maps an ID to a name, is updated on node add/remove
const nodeNames: Record<string, string> = {};
data.nodes.forEach((node) => {
nodeNames[node.id] = node.name || "";
});
}, [data, zoomPanSettings]);
const nodeAdded = (node: ICanvasNode) => {
nodeNames[node.id] = node.name || "";
};
const nodesRemoved = (nodes: Set<string>) => {
nodes.forEach((nodeId) => delete nodeNames[nodeId]);
};
const hasNodeWithName = (name: string) => {
for (const nodeId in nodeNames) {
if (nodeNames[nodeId] === name) {
return true;
}
}
return false;
};
if (!isSupported()) {
return <h1>{Localizer.l("browserNotSupported")}</h1>;
}
const exportGraph = () => {
graph.setName(graphTopologyName);
graph.setDescription(graphDescription);
graph.setGraphDataFromICanvasData(data);
const topology = graph.getTopology();
console.log(topology);
};
function setTopology(topology: any) {
graph.setTopology(topology);
setData(graph.getICanvasData());
setDirty(false);
}
const onNameChange = (event: React.FormEvent, newValue?: string) => {
if (newValue) {
setGraphTopologyName(newValue);
}
};
function onChange() {
setDirty(true);
}
const onDescriptionChange = (event: React.FormEvent, newValue?: string) => {
if (typeof newValue !== "undefined") {
setGraphDescription(newValue);
}
};
// nodeNames maps an ID to a name, is updated on node add/remove
const nodeNames: Record<string, string> = {};
data.nodes.forEach((node) => {
nodeNames[node.id] = node.name || "";
});
const nodeAdded = (node: ICanvasNode) => {
nodeNames[node.id] = node.name || "";
};
const nodesRemoved = (nodes: Set<string>) => {
nodes.forEach((nodeId) => delete nodeNames[nodeId]);
};
const hasNodeWithName = (name: string) => {
for (const nodeId in nodeNames) {
if (nodeNames[nodeId] === name) {
return true;
}
}
return false;
};
const panelStyles = {
root: {
boxSizing: "border-box" as const,
overflowY: "auto" as const,
willChange: "transform",
height: "100vh",
width: 300,
background: "var(--vscode-editorWidget-background)",
borderRight: "1px solid var(--vscode-editorWidget-border)"
}
};
const exportGraph = () => {
graph.setName(graphTopologyName);
graph.setDescription(graphDescription);
graph.setGraphDataFromICanvasData(data);
const topology = graph.getTopology();
console.log(topology);
};
const panelItemStyles = {
padding: 10
};
const onNameChange = (event: React.FormEvent, newValue?: string) => {
if (newValue) {
setGraphTopologyName(newValue);
}
};
const topSidebarStyles = {
padding: 10,
borderBottom: "1px solid var(--vscode-editorWidget-border)",
paddingBottom: 20,
marginBottom: 10
};
const onDescriptionChange = (event: React.FormEvent, newValue?: string) => {
if (typeof newValue !== "undefined") {
setGraphDescription(newValue);
}
};
const panelStyles = {
root: {
boxSizing: "border-box" as const,
overflowY: "auto" as const,
willChange: "transform",
height: "100vh",
width: 300,
background: "var(--vscode-editorWidget-background)",
borderRight: "1px solid var(--vscode-editorWidget-border)",
},
};
const panelItemStyles = {
padding: 10,
};
const topSidebarStyles = {
padding: 10,
borderBottom: "1px solid var(--vscode-editorWidget-border)",
paddingBottom: 20,
marginBottom: 10,
};
return (
<ReactDagEditor theme={theme}>
<RegisterNode
name="module"
config={withDefaultPortsPosition(new NodeBase())}
/>
<RegisterPort name="modulePort" config={modulePort} />
<Stack horizontal>
<Stack.Item styles={panelStyles}>
<div style={topSidebarStyles}>
<TextField
label={Localizer.l("sidebarGraphTopologyNameLabel")}
required
defaultValue={graphTopologyName}
placeholder={Localizer.l("sidebarGraphTopologyNamePlaceholder")}
onChange={onNameChange}
/>
<TextField
label={Localizer.l("sidebarGraphDescriptionLabel")}
defaultValue={graphDescription}
placeholder={Localizer.l("sidebarGraphDescriptionPlaceholder")}
onChange={onDescriptionChange}
/>
</div>
<div style={panelItemStyles}>
<SampleSelectorTrigger
setTopology={setTopology}
hasUnsavedChanges={dirty}
/>
<ItemPanel hasNodeWithName={hasNodeWithName} />
</div>
</Stack.Item>
<Stack.Item grow>
<Toolbar
name={graphTopologyName}
exportGraph={exportGraph}
closeEditor={() => {
alert("TODO: Close editor");
}}
/>
<Stack.Item grow>
<InnerGraph
data={data}
setData={setData}
zoomPanSettings={zoomPanSettings}
setZoomPanSettings={setZoomPanSettings}
canvasMouseMode={CanvasMouseMode.pan}
onNodeAdded={nodeAdded}
onNodeRemoved={nodesRemoved}
onChange={onChange}
/>
</Stack.Item>
</Stack.Item>
</Stack>
<ContextMenu />
</ReactDagEditor>
);
return (
<ReactDagEditor theme={theme}>
<RegisterNode name="module" config={withDefaultPortsPosition(new NodeBase())} />
<RegisterPort name="modulePort" config={modulePort} />
<Stack horizontal>
<Stack.Item styles={panelStyles}>
<div style={topSidebarStyles}>
<TextField
label={Localizer.l("sidebarGraphTopologyNameLabel")}
required
defaultValue={graphTopologyName}
placeholder={Localizer.l("sidebarGraphTopologyNamePlaceholder")}
onChange={onNameChange}
/>
<TextField
label={Localizer.l("sidebarGraphDescriptionLabel")}
defaultValue={graphDescription}
placeholder={Localizer.l("sidebarGraphDescriptionPlaceholder")}
onChange={onDescriptionChange}
/>
</div>
<div style={panelItemStyles}>
<SampleSelectorTrigger setTopology={setTopology} hasUnsavedChanges={dirty} />
<ItemPanel hasNodeWithName={hasNodeWithName} />
</div>
</Stack.Item>
<Stack.Item grow>
<Toolbar
name={graphTopologyName}
exportGraph={exportGraph}
closeEditor={() => {
alert("TODO: Close editor");
}}
/>
<Stack.Item grow>
<InnerGraph
data={data}
setData={setData}
zoomPanSettings={zoomPanSettings}
setZoomPanSettings={setZoomPanSettings}
canvasMouseMode={CanvasMouseMode.pan}
onNodeAdded={nodeAdded}
onNodeRemoved={nodesRemoved}
onChange={onChange}
/>
</Stack.Item>
</Stack.Item>
</Stack>
<ContextMenu />
</ReactDagEditor>
);
};

Просмотреть файл

@ -1,134 +1,118 @@
import * as React from "react";
import {
CanvasMouseMode,
Graph,
GraphDataChangeType,
GraphNodeState,
ICanvasData,
ICanvasNode,
IGraphDataChangeEvent,
IGraphStyles,
IPropsAPI,
IZoomPanSettings,
RegisterEdge,
RegisterPanel,
TSetData,
TSetZoomPanSettings,
usePropsAPI,
GraphFeatures,
CanvasMouseMode,
Graph,
GraphDataChangeType,
GraphFeatures,
GraphNodeState,
ICanvasData,
ICanvasNode,
IGraphDataChangeEvent,
IGraphStyles,
IPropsAPI,
IZoomPanSettings,
RegisterEdge,
RegisterPanel,
TSetData,
TSetZoomPanSettings,
usePropsAPI
} from "@vienna/react-dag-editor";
import LocalizerHelpers from "../../helpers/LocalizerHelpers";
import { CustomEdgeConfig } from "./CustomEdgeConfig";
import { NodePropertiesPanel } from "./NodePropertiesPanel";
import LocalizerHelpers from "../../helpers/LocalizerHelpers";
export interface IInnerGraphProps {
data: ICanvasData;
setData: TSetData;
zoomPanSettings: IZoomPanSettings;
setZoomPanSettings: TSetZoomPanSettings;
canvasMouseMode: CanvasMouseMode;
isHorizontal?: boolean;
onNodeAdded?: (node: ICanvasNode) => void;
onNodeRemoved?: (nodes: Set<string>) => void;
onChange?: (evt: IGraphDataChangeEvent) => void;
readOnly?: boolean;
data: ICanvasData;
setData: TSetData;
zoomPanSettings: IZoomPanSettings;
setZoomPanSettings: TSetZoomPanSettings;
canvasMouseMode: CanvasMouseMode;
isHorizontal?: boolean;
onNodeAdded?: (node: ICanvasNode) => void;
onNodeRemoved?: (nodes: Set<string>) => void;
onChange?: (evt: IGraphDataChangeEvent) => void;
readOnly?: boolean;
}
export const InnerGraph: React.FunctionComponent<IInnerGraphProps> = (
props
) => {
const propsApiRef = React.useRef<IPropsAPI>(null);
const propsApi = usePropsAPI();
const svgRef = React.useRef<SVGSVGElement>(null);
export const InnerGraph: React.FunctionComponent<IInnerGraphProps> = (props) => {
const propsApiRef = React.useRef<IPropsAPI>(null);
const propsApi = usePropsAPI();
const svgRef = React.useRef<SVGSVGElement>(null);
// open node inspector panel when recovering state, a node is clicked, or a node is added
const inspectNode = (node?: ICanvasNode) => {
if (node && propsApiRef.current) {
propsApiRef.current.openSidePanel("node", node);
}
};
props.data.nodes.forEach((node) => {
if (node.state === GraphNodeState.selected) {
inspectNode(node);
}
});
const onNodeClick = (_e: React.MouseEvent, node: ICanvasNode) => {
inspectNode(node);
};
const onAddNode = (node?: ICanvasNode) => {
inspectNode(node);
};
// open node inspector panel when recovering state, a node is clicked, or a node is added
const inspectNode = (node?: ICanvasNode) => {
if (node && propsApiRef.current) {
propsApiRef.current.openSidePanel("node", node);
}
};
props.data.nodes.forEach((node) => {
if (node.state === GraphNodeState.selected) {
inspectNode(node);
}
});
const onNodeClick = (_e: React.MouseEvent, node: ICanvasNode) => {
inspectNode(node);
};
const onAddNode = (node?: ICanvasNode) => {
inspectNode(node);
};
const dismissSidePanel = () => {
if (propsApiRef.current) {
propsApiRef.current.dismissSidePanel();
}
};
const dismissSidePanel = () => {
if (propsApiRef.current) {
propsApiRef.current.dismissSidePanel();
}
};
const onChange = (
evt: IGraphDataChangeEvent,
ref: React.RefObject<SVGSVGElement>
) => {
if (props.onChange) {
props.onChange(evt);
}
switch (evt.type) {
case GraphDataChangeType.addNode:
onAddNode(evt.payload);
if (props.onNodeAdded && evt.payload) props.onNodeAdded(evt.payload);
break;
case GraphDataChangeType.deleteNode: // in case just a node is removed
case GraphDataChangeType.deleteMultiple: // in case nodes + attached edges are removed
dismissSidePanel();
if (props.onNodeRemoved && evt.payload && evt.payload.selectedNodeIds)
props.onNodeRemoved(evt.payload.selectedNodeIds);
break;
default:
}
};
const onChange = (evt: IGraphDataChangeEvent, ref: React.RefObject<SVGSVGElement>) => {
if (props.onChange) {
props.onChange(evt);
}
switch (evt.type) {
case GraphDataChangeType.addNode:
onAddNode(evt.payload);
if (props.onNodeAdded && evt.payload) props.onNodeAdded(evt.payload);
break;
case GraphDataChangeType.deleteNode: // in case just a node is removed
case GraphDataChangeType.deleteMultiple: // in case nodes + attached edges are removed
dismissSidePanel();
if (props.onNodeRemoved && evt.payload && evt.payload.selectedNodeIds) props.onNodeRemoved(evt.payload.selectedNodeIds);
break;
default:
}
};
const graphStyles: IGraphStyles = {
root: {
height: "100vh",
width: "100%",
},
};
const graphStyles: IGraphStyles = {
root: {
height: "100vh",
width: "100%"
}
};
const readOnlyFeatures = new Set([
"a11yFeatures",
"canvasScrollable",
"panCanvas",
"clickNodeToSelect",
"sidePanel",
"editNode",
]) as Set<GraphFeatures>;
const readOnlyFeatures = new Set(["a11yFeatures", "canvasScrollable", "panCanvas", "clickNodeToSelect", "sidePanel", "editNode"]) as Set<GraphFeatures>;
return (
<>
<RegisterPanel name={"node"} config={new NodePropertiesPanel(propsApi)} />
<RegisterEdge
name={"customEdge"}
config={new CustomEdgeConfig(propsApi)}
/>
<Graph
svgRef={svgRef}
propsAPIRef={propsApiRef}
data={props.data}
setData={props.setData}
styles={graphStyles}
onCanvasClick={dismissSidePanel}
zoomPanSettings={props.zoomPanSettings}
setPanZoomPanSettings={props.setZoomPanSettings}
onNodeClick={onNodeClick}
onChange={onChange}
defaultNodeShape="module"
defaultPortShape="modulePort"
defaultEdgeShape="customEdge"
canvasMouseMode={props.canvasMouseMode}
getNodeAriaLabel={LocalizerHelpers.getNodeAriaLabel}
getPortAriaLabel={LocalizerHelpers.getPortAriaLabel}
features={props.readOnly ? readOnlyFeatures : undefined}
/>
</>
);
return (
<>
<RegisterPanel name={"node"} config={new NodePropertiesPanel(propsApi)} />
<RegisterEdge name={"customEdge"} config={new CustomEdgeConfig(propsApi)} />
<Graph
svgRef={svgRef}
propsAPIRef={propsApiRef}
data={props.data}
setData={props.setData}
styles={graphStyles}
onCanvasClick={dismissSidePanel}
zoomPanSettings={props.zoomPanSettings}
setPanZoomPanSettings={props.setZoomPanSettings}
onNodeClick={onNodeClick}
onChange={onChange}
defaultNodeShape="module"
defaultPortShape="modulePort"
defaultEdgeShape="customEdge"
canvasMouseMode={props.canvasMouseMode}
getNodeAriaLabel={LocalizerHelpers.getNodeAriaLabel}
getPortAriaLabel={LocalizerHelpers.getPortAriaLabel}
features={props.readOnly ? readOnlyFeatures : undefined}
/>
</>
);
};

Просмотреть файл

@ -1,159 +1,130 @@
import { FontIcon, Stack, Text } from "office-ui-fabric-react";
import * as React from "react";
import {
IAccessibleTreeStyles,
ITreeNode,
ReactAccessibleTree,
IAccessibleTreeStyles,
ITreeNode,
ReactAccessibleTree
} from "react-accessible-tree";
import { v4 as uuid } from "uuid";
import { ICanvasNode, Item, usePropsAPI } from "@vienna/react-dag-editor";
import { FontIcon, Text, Stack } from "office-ui-fabric-react";
import Definitions from "../../definitions/Definitions";
import NodeHelpers from "../../helpers/NodeHelpers";
import Localizer from "../../localization/Localizer";
import { NodeContainer } from "./NodeContainer";
import NodeHelpers from "../../helpers/NodeHelpers";
interface IProps {
hasNodeWithName: (name: string) => boolean;
hasNodeWithName: (name: string) => boolean;
}
export const ItemPanel: React.FunctionComponent<IProps> = (props) => {
const [treeData, setTreeData] = React.useState<ITreeNode[]>([]);
const propsAPI = usePropsAPI();
const [treeData, setTreeData] = React.useState<ITreeNode[]>([]);
const propsAPI = usePropsAPI();
const nodeWillAdd = (node: ICanvasNode): ICanvasNode => {
// make sure this name hasn't already been used, append number if it has
let nodeName = node.name || "";
let duplicateCounter = 1;
while (props.hasNodeWithName(nodeName)) {
nodeName = (node.name || "node") + duplicateCounter;
duplicateCounter++;
}
if (node.data) {
node.data.nodeProperties.name = nodeName;
}
return {
...node,
id: uuid(),
name: nodeName,
};
};
const nodeDidAdd = (node: ICanvasNode) => {
// show the side panel when adding a new node
propsAPI.selectNodeById(node.id);
};
const generateAccordionTitle = (node: ITreeNode) => {
const nodeTypeStringKey = node.searchKeys[0] as string;
const styles = NodeHelpers.getNodeAppearance(
NodeHelpers.getNodeTypeFromString(nodeTypeStringKey)
);
return (
<Stack
horizontal
verticalAlign="center"
tokens={{ childrenGap: "s1" }}
style={{ cursor: "pointer" }}
>
<FontIcon
iconName={node.expanded ? "CaretSolidDown" : "CaretSolidRight"}
/>
<FontIcon iconName={styles.iconName} />
<Text variant="medium">
{Localizer.l(nodeTypeStringKey)} ({node.children.length})
</Text>
</Stack>
);
};
if (treeData.length === 0) {
const treeNodes: ITreeNode[] = Definitions.getItemPanelNodes().map(
(category, index) => {
const children = category.children.map((node) => {
const internalNode = node.extra as ICanvasNode;
const description = Localizer.l(
internalNode.data!.nodeProperties.name
);
const styles = NodeHelpers.getNodeAppearance(
internalNode.data!.nodeType
);
return {
title: (
<Item
key={node.title as string}
model={internalNode}
nodeWillAdd={nodeWillAdd}
nodeDidAdd={nodeDidAdd}
>
<NodeContainer
heading={node.title as string}
iconName={styles.iconName!}
title={description}
background="var(--vscode-editor-background)"
>
<Text
variant="small"
style={{
overflow: "hidden",
display: "-webkit-box",
// these properties aren't recognized
["-webkit-line-clamp" as any]: "2",
["-webkit-box-orient" as any]: "vertical",
}}
>
{description}
</Text>
</NodeContainer>
</Item>
),
id: uuid(),
searchKeys: [node.title as string],
children: [],
};
});
// collapse all except first by default
category.expanded = index === 0;
category.title = generateAccordionTitle(category);
const nodeWillAdd = (node: ICanvasNode): ICanvasNode => {
// make sure this name hasn't already been used, append number if it has
let nodeName = node.name || "";
let duplicateCounter = 1;
while (props.hasNodeWithName(nodeName)) {
nodeName = (node.name || "node") + duplicateCounter;
duplicateCounter++;
}
if (node.data) {
node.data.nodeProperties.name = nodeName;
}
return {
...category,
children,
...node,
id: uuid(),
name: nodeName
};
}
};
const nodeDidAdd = (node: ICanvasNode) => {
// show the side panel when adding a new node
propsAPI.selectNodeById(node.id);
};
const generateAccordionTitle = (node: ITreeNode) => {
const nodeTypeStringKey = node.searchKeys[0] as string;
const styles = NodeHelpers.getNodeAppearance(NodeHelpers.getNodeTypeFromString(nodeTypeStringKey));
return (
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: "s1" }} style={{ cursor: "pointer" }}>
<FontIcon iconName={node.expanded ? "CaretSolidDown" : "CaretSolidRight"} />
<FontIcon iconName={styles.iconName} />
<Text variant="medium">
{Localizer.l(nodeTypeStringKey)} ({node.children.length})
</Text>
</Stack>
);
};
if (treeData.length === 0) {
const treeNodes: ITreeNode[] = Definitions.getItemPanelNodes().map((category, index) => {
const children = category.children.map((node) => {
const internalNode = node.extra as ICanvasNode;
const description = Localizer.l(internalNode.data!.nodeProperties.name);
const styles = NodeHelpers.getNodeAppearance(internalNode.data!.nodeType);
return {
title: (
<Item key={node.title as string} model={internalNode} nodeWillAdd={nodeWillAdd} nodeDidAdd={nodeDidAdd}>
<NodeContainer heading={node.title as string} iconName={styles.iconName!} title={description} background="var(--vscode-editor-background)">
<Text
variant="small"
style={{
overflow: "hidden",
display: "-webkit-box",
// these properties aren't recognized
["-webkit-line-clamp" as any]: "2",
["-webkit-box-orient" as any]: "vertical"
}}
>
{description}
</Text>
</NodeContainer>
</Item>
),
id: uuid(),
searchKeys: [node.title as string],
children: []
};
});
// collapse all except first by default
category.expanded = index === 0;
category.title = generateAccordionTitle(category);
return {
...category,
children
};
});
setTreeData(treeNodes);
}
const onChange = (nextData: ITreeNode[]) => {
nextData.forEach((category) => {
category.title = generateAccordionTitle(category);
});
setTreeData(nextData);
};
const treeViewStyles: IAccessibleTreeStyles = {
root: {
padding: 0,
margin: 0
},
group: {
paddingTop: 5,
paddingLeft: 0
},
item: {
listStyle: "none",
padding: "5px 0"
}
};
return (
<>
<h2>{Localizer.l("sidebarTopologyComponentTitle")}</h2>
<p>{Localizer.l("sidebarTopologyComponentText")}</p>
<ReactAccessibleTree treeData={treeData} onChange={onChange} styles={treeViewStyles} />
</>
);
setTreeData(treeNodes);
}
const onChange = (nextData: ITreeNode[]) => {
nextData.forEach((category) => {
category.title = generateAccordionTitle(category);
});
setTreeData(nextData);
};
const treeViewStyles: IAccessibleTreeStyles = {
root: {
padding: 0,
margin: 0,
},
group: {
paddingTop: 5,
paddingLeft: 0,
},
item: {
listStyle: "none",
padding: "5px 0",
},
};
return (
<>
<h2>{Localizer.l("sidebarTopologyComponentTitle")}</h2>
<p>{Localizer.l("sidebarTopologyComponentText")}</p>
<ReactAccessibleTree
treeData={treeData}
onChange={onChange}
styles={treeViewStyles}
/>
</>
);
};

Просмотреть файл

@ -1,80 +1,67 @@
import { IStackStyles } from "office-ui-fabric-react";
import * as React from "react";
import {
getRectHeight,
getRectWidth,
GraphNodeState,
hasState,
ICanvasNode,
IItemConfigArgs,
IRectConfig,
ITheme,
getRectHeight,
getRectWidth,
GraphNodeState,
hasState,
ICanvasNode,
IItemConfigArgs,
IRectConfig,
ITheme
} from "@vienna/react-dag-editor";
import { IStackStyles } from "office-ui-fabric-react";
import { NodeContainer } from "./NodeContainer";
import Localizer from "../../localization/Localizer";
import { NodeContainer } from "./NodeContainer";
export class NodeBase implements IRectConfig<ICanvasNode> {
public getMinHeight = (curNode: ICanvasNode): number => {
return 50;
};
public getMinWidth = (): number => {
return 280;
};
public render = (args: IItemConfigArgs<ICanvasNode>): React.ReactNode => {
const node = args.model;
const iconName = node.data && node.data.iconName;
const nodeType = node.data && node.data.nodeProperties["@type"];
const description = Localizer.l(nodeType.split(".").pop());
const rectHeight = getRectHeight<ICanvasNode>(this, node);
const rectWidth = getRectWidth<ICanvasNode>(this, node);
const opacity = hasState(GraphNodeState.unconnectedToSelected)(node.state)
? "60%"
: "100%";
return (
<foreignObject
transform={`translate(${node.x}, ${node.y})`}
height={rectHeight}
width={rectWidth}
opacity={opacity}
overflow="visible"
>
<NodeContainer
heading={node.name as string}
iconName={iconName}
title={description}
selected={hasState(GraphNodeState.selected)(node.state)}
hovered={hasState(GraphNodeState.activated)(node.state)}
></NodeContainer>
</foreignObject>
);
};
protected readonly getNodeStyle = (
node: ICanvasNode,
theme: ITheme
): IStackStyles => {
let borderColor = node.data ? node.data.color : theme.defaultBorderColor;
if (
hasState(GraphNodeState.activated | GraphNodeState.selected)(node.state)
) {
borderColor = node.data ? node.data.colorAlt : theme.nodeActivateStroke;
}
return {
root: {
height: getRectHeight<ICanvasNode>(this, node),
padding: 8,
backgroundColor: theme.nodeFill,
borderRadius: 4,
cursor: "move",
border: `2px solid ${borderColor}`,
},
public getMinHeight = (curNode: ICanvasNode): number => {
return 50;
};
public getMinWidth = (): number => {
return 280;
};
public render = (args: IItemConfigArgs<ICanvasNode>): React.ReactNode => {
const node = args.model;
const iconName = node.data && node.data.iconName;
const nodeType = node.data && node.data.nodeProperties["@type"];
const description = Localizer.l(nodeType.split(".").pop());
const rectHeight = getRectHeight<ICanvasNode>(this, node);
const rectWidth = getRectWidth<ICanvasNode>(this, node);
const opacity = hasState(GraphNodeState.unconnectedToSelected)(node.state) ? "60%" : "100%";
return (
<foreignObject transform={`translate(${node.x}, ${node.y})`} height={rectHeight} width={rectWidth} opacity={opacity} overflow="visible">
<NodeContainer
heading={node.name as string}
iconName={iconName}
title={description}
selected={hasState(GraphNodeState.selected)(node.state)}
hovered={hasState(GraphNodeState.activated)(node.state)}
></NodeContainer>
</foreignObject>
);
};
protected readonly getNodeStyle = (node: ICanvasNode, theme: ITheme): IStackStyles => {
let borderColor = node.data ? node.data.color : theme.defaultBorderColor;
if (hasState(GraphNodeState.activated | GraphNodeState.selected)(node.state)) {
borderColor = node.data ? node.data.colorAlt : theme.nodeActivateStroke;
}
return {
root: {
height: getRectHeight<ICanvasNode>(this, node),
padding: 8,
backgroundColor: theme.nodeFill,
borderRadius: 4,
cursor: "move",
border: `2px solid ${borderColor}`
}
};
};
};
}

Просмотреть файл

@ -1,71 +1,51 @@
import { FontIcon, Stack, Text } from "office-ui-fabric-react";
import * as React from "react";
import { Stack, FontIcon, Text } from "office-ui-fabric-react";
interface INodeContainerProps {
heading: string;
title?: string;
iconName: string;
children?: React.ReactElement[] | React.ReactElement | string;
selected?: boolean;
hovered?: boolean;
background?: string;
heading: string;
title?: string;
iconName: string;
children?: React.ReactElement[] | React.ReactElement | string;
selected?: boolean;
hovered?: boolean;
background?: string;
}
export const NodeContainer: React.FunctionComponent<INodeContainerProps> = (
props
) => {
const {
iconName,
heading,
title,
children = [],
selected = false,
hovered = false,
} = props;
export const NodeContainer: React.FunctionComponent<INodeContainerProps> = (props) => {
const { iconName, heading, title, children = [], selected = false, hovered = false } = props;
const background =
props.background ||
(selected
? "var(--vscode-editor-selectionBackground)"
: "var(--vscode-editorWidget-background)");
const background = props.background || (selected ? "var(--vscode-editor-selectionBackground)" : "var(--vscode-editorWidget-background)");
const cardStyle = {
userSelect: "none" as const,
boxSizing: "border-box" as const,
cursor: "default",
transition: "all 0.2s ease-in-out",
background,
border: "1px solid",
borderColor: selected
? "var(--vscode-editor-foreground)"
: "var(--vscode-editorWidget-border)",
color: selected
? "var(--vscode-editor-foreground)"
: "car(--vscode-editorWidget-foreground)",
boxShadow:
hovered || selected
? "0px 4px 4px rgba(0, 0, 0, 0.25)"
: "0px 4px 6px rgba(0, 0, 0, 0.1)",
borderRadius: 2,
padding: 4,
paddingBottom: 8,
minHeight: 50,
};
const cardStyle = {
userSelect: "none" as const,
boxSizing: "border-box" as const,
cursor: "default",
transition: "all 0.2s ease-in-out",
background,
border: "1px solid",
borderColor: selected ? "var(--vscode-editor-foreground)" : "var(--vscode-editorWidget-border)",
color: selected ? "var(--vscode-editor-foreground)" : "car(--vscode-editorWidget-foreground)",
boxShadow: hovered || selected ? "0px 4px 4px rgba(0, 0, 0, 0.25)" : "0px 4px 6px rgba(0, 0, 0, 0.1)",
borderRadius: 2,
padding: 4,
paddingBottom: 8,
minHeight: 50
};
const iconStyle = {
padding: 4,
paddingRight: 0,
};
const iconStyle = {
padding: 4,
paddingRight: 0
};
return (
<>
<Stack horizontal style={cardStyle} tokens={{ childrenGap: "s1" }}>
<FontIcon iconName={iconName} style={iconStyle} />
<Stack title={title} tokens={{ childrenGap: "s2" }}>
<Text variant="medium">{heading}</Text>
<div>{children}</div>
</Stack>
</Stack>
</>
);
return (
<>
<Stack horizontal style={cardStyle} tokens={{ childrenGap: "s1" }}>
<FontIcon iconName={iconName} style={iconStyle} />
<Stack title={title} tokens={{ childrenGap: "s2" }}>
<Text variant="medium">{heading}</Text>
<div>{children}</div>
</Stack>
</Stack>
</>
);
};

Просмотреть файл

@ -1,59 +1,55 @@
import { Stack, IconButton } from "office-ui-fabric-react";
import { IconButton, Stack } from "office-ui-fabric-react";
import * as React from "react";
import { IPanelConfig, IPropsAPI } from "@vienna/react-dag-editor";
import Localizer from "../../localization/Localizer";
import Definitions from "../../definitions/Definitions";
import Localizer from "../../localization/Localizer";
import { PropertyEditor } from "./PropertyEditor/PropertyEditor";
export class NodePropertiesPanel implements IPanelConfig {
private readonly _propsAPI: IPropsAPI;
private readonly _propsAPI: IPropsAPI;
constructor(propsAPI: IPropsAPI) {
this._propsAPI = propsAPI;
}
constructor(propsAPI: IPropsAPI) {
this._propsAPI = propsAPI;
}
public render(data: any): React.ReactNode {
const panelStyle: React.CSSProperties = {
position: "absolute",
right: 0,
top: 0,
background: "var(--vscode-editorWidget-background)",
height: "100%",
borderLeft: "1px solid var(--vscode-editorWidget-border)",
width: 460,
zIndex: 1000,
padding: 10,
overflowY: "auto",
public render(data: any): React.ReactNode {
const panelStyle: React.CSSProperties = {
position: "absolute",
right: 0,
top: 0,
background: "var(--vscode-editorWidget-background)",
height: "100%",
borderLeft: "1px solid var(--vscode-editorWidget-border)",
width: 460,
zIndex: 1000,
padding: 10,
overflowY: "auto"
};
const nodeProperties = data.data.nodeProperties as any;
const definition = Definitions.getNodeDefinition(nodeProperties);
return (
<div style={panelStyle}>
<Stack horizontal horizontalAlign="space-between" tokens={{ childrenGap: "s1" }}>
<h2 style={{ margin: 0 }}>{data.name}</h2>
<IconButton
iconProps={{
iconName: "Clear"
}}
title={Localizer.l("closeButtonText")}
ariaLabel={Localizer.l("propertyEditorCloseButtonAriaLabel")}
onClick={this._dismissPanel}
/>
</Stack>
{definition.description && <p>{Localizer.l(definition.description)}</p>}
<PropertyEditor nodeProperties={nodeProperties} />
</div>
);
}
private readonly _dismissPanel = () => {
this._propsAPI.dismissSidePanel();
this._propsAPI.selectNodeById([]);
};
const nodeProperties = data.data.nodeProperties as any;
const definition = Definitions.getNodeDefinition(nodeProperties);
return (
<div style={panelStyle}>
<Stack
horizontal
horizontalAlign="space-between"
tokens={{ childrenGap: "s1" }}
>
<h2 style={{ margin: 0 }}>{data.name}</h2>
<IconButton
iconProps={{
iconName: "Clear",
}}
title={Localizer.l("closeButtonText")}
ariaLabel={Localizer.l("propertyEditorCloseButtonAriaLabel")}
onClick={this._dismissPanel}
/>
</Stack>
{definition.description && <p>{Localizer.l(definition.description)}</p>}
<PropertyEditor nodeProperties={nodeProperties} />
</div>
);
}
private readonly _dismissPanel = () => {
this._propsAPI.dismissSidePanel();
this._propsAPI.selectNodeById([]);
};
}

Просмотреть файл

@ -1,104 +1,85 @@
import * as React from "react";
import {
GraphPortState,
hasState,
ICanvasData,
ICanvasNode,
ICanvasPort,
IPortConfig,
GraphPortState,
hasState,
ICanvasData,
ICanvasNode,
ICanvasPort,
IPortConfig
} from "@vienna/react-dag-editor";
import NodeHelpers from "../../helpers/NodeHelpers";
import { PortInner } from "./PortInner";
export interface ICanvasPortCustomized extends ICanvasPort {
label: string;
label: string;
}
export const modulePort: IPortConfig = {
getStyle(portOriginal, parentNode, data): Partial<React.CSSProperties> {
const port: ICanvasPortCustomized = portOriginal as ICanvasPortCustomized;
getStyle(portOriginal, parentNode, data): Partial<React.CSSProperties> {
const port: ICanvasPortCustomized = portOriginal as ICanvasPortCustomized;
const strokeWidth = 1;
const stroke = "var(--vscode-editorWidget-border)";
let fill = "var(--vscode-editorWidget-background)";
const strokeWidth = 1;
const stroke = "var(--vscode-editorWidget-border)";
let fill = "var(--vscode-editorWidget-background)";
if (
hasState(
GraphPortState.activated |
GraphPortState.selected |
GraphPortState.connecting
)(port.state)
) {
fill = "var(--vscode-editor-selectionBackground)";
}
if (hasState(GraphPortState.activated | GraphPortState.selected | GraphPortState.connecting)(port.state)) {
fill = "var(--vscode-editor-selectionBackground)";
}
return {
stroke,
strokeWidth,
fill,
};
},
getIsConnectable(
port: ICanvasPortCustomized,
parentNode: ICanvasNode,
data: ICanvasData
): boolean | undefined {
const { nodes = [] } = data;
return {
stroke,
strokeWidth,
fill
};
},
getIsConnectable(port: ICanvasPortCustomized, parentNode: ICanvasNode, data: ICanvasData): boolean | undefined {
const { nodes = [] } = data;
// edge cannot be made from input port (violates edge direction)
if (port.isInputDisabled) {
return false;
}
// edge cannot be made from input port (violates edge direction)
if (port.isInputDisabled) {
return false;
}
// has to connect input <-> output
let isOutputPort;
nodes.forEach((node) => {
if (node.ports) {
node.ports.forEach((p) => {
// find the port being connected
if (hasState(GraphPortState.connecting)(p.state)) {
// check if we are connecting downstream
if (!NodeHelpers.nodeCanConnectToNode(node, parentNode)) {
return false;
// has to connect input <-> output
let isOutputPort;
nodes.forEach((node) => {
if (node.ports) {
node.ports.forEach((p) => {
// find the port being connected
if (hasState(GraphPortState.connecting)(p.state)) {
// check if we are connecting downstream
if (!NodeHelpers.nodeCanConnectToNode(node, parentNode)) {
return false;
}
// check if connecting to self
if (node.id === parentNode.id) {
return false;
}
// check input/output connectivity
if (!p.isOutputDisabled) {
isOutputPort = true;
}
if (!p.isInputDisabled) {
isOutputPort = false;
}
}
});
}
// check if connecting to self
if (node.id === parentNode.id) {
return false;
}
// check input/output connectivity
if (!p.isOutputDisabled) {
isOutputPort = true;
}
if (!p.isInputDisabled) {
isOutputPort = false;
}
}
});
}
});
if (isOutputPort === undefined) {
return undefined;
}
if (isOutputPort) {
return !port.isInputDisabled;
} else {
return !port.isOutputDisabled;
}
},
render(args): React.ReactNode {
const { model, data, x, y, parentNode } = args;
const port: ICanvasPortCustomized = model as ICanvasPortCustomized;
if (isOutputPort === undefined) {
return undefined;
}
if (isOutputPort) {
return !port.isInputDisabled;
} else {
return !port.isOutputDisabled;
}
},
render(args): React.ReactNode {
const { model, data, x, y, parentNode } = args;
const port: ICanvasPortCustomized = model as ICanvasPortCustomized;
return (
<PortInner
data={data}
port={port}
parentNode={parentNode}
modulePort={modulePort}
x={x}
y={y}
/>
);
},
return <PortInner data={data} port={port} parentNode={parentNode} modulePort={modulePort} x={x} y={y} />;
}
};

Просмотреть файл

@ -1,60 +1,50 @@
import * as React from "react";
import {
GraphPortConnectState,
GraphPortState,
hasState,
ICanvasData,
ICanvasNode,
IPortConfig,
useTheme
GraphPortConnectState,
GraphPortState,
hasState,
ICanvasData,
ICanvasNode,
IPortConfig,
useTheme
} from "@vienna/react-dag-editor";
import { ICanvasPortCustomized } from "./Port";
interface IProps {
data: ICanvasData;
port: ICanvasPortCustomized;
parentNode: ICanvasNode;
modulePort: IPortConfig;
x: number;
y: number;
data: ICanvasData;
port: ICanvasPortCustomized;
parentNode: ICanvasNode;
modulePort: IPortConfig;
x: number;
y: number;
}
export const PortInner: React.FunctionComponent<IProps> = (props) => {
const { port, data, modulePort, x, y, parentNode } = props;
const { theme } = useTheme();
const { port, data, modulePort, x, y, parentNode } = props;
const { theme } = useTheme();
const style = modulePort.getStyle
? modulePort.getStyle(port, parentNode, data, theme)
: {};
const style = modulePort.getStyle ? modulePort.getStyle(port, parentNode, data, theme) : {};
const isConnectable = modulePort.getIsConnectable(port, parentNode, data);
const isConnectable = modulePort.getIsConnectable(port, parentNode, data);
const renderCircle = (
r: number,
circleStyle: Partial<React.CSSProperties>
): React.ReactNode => {
return <circle r={r} cx={x} cy={y} style={circleStyle} />;
};
const renderCircle = (r: number, circleStyle: Partial<React.CSSProperties>): React.ReactNode => {
return <circle r={r} cx={x} cy={y} style={circleStyle} />;
};
return (
<g>
<circle r="10" fill="transparent" cx={x} cy={y} />
return (
<g>
<circle r="10" fill="transparent" cx={x} cy={y} />
{isConnectable === undefined ? ( // isConnectable === undefined is when the graph is not in connecting state
<>
{hasState(GraphPortState.activated)(port.state)
? renderCircle(7, style)
: renderCircle(5, style)}
</>
) : port.connectState === GraphPortConnectState.connectingAsTarget ? (
renderCircle(7, style)
) : (
<>
{isConnectable &&
renderCircle(18, { fill: theme.primaryColor, opacity: 0.2 })}
{renderCircle(5, style)}
</>
)}
</g>
);
{isConnectable === undefined ? ( // isConnectable === undefined is when the graph is not in connecting state
<>{hasState(GraphPortState.activated)(port.state) ? renderCircle(7, style) : renderCircle(5, style)}</>
) : port.connectState === GraphPortConnectState.connectingAsTarget ? (
renderCircle(7, style)
) : (
<>
{isConnectable && renderCircle(18, { fill: theme.primaryColor, opacity: 0.2 })}
{renderCircle(5, style)}
</>
)}
</g>
);
};

Просмотреть файл

@ -1,79 +1,75 @@
import * as React from "react";
import Localizer from "../../../localization/Localizer";
import {
IconButton,
Stack,
IStackTokens,
Callout,
Label,
IStackStyles,
IButtonStyles,
Callout,
IButtonStyles,
IconButton,
IStackStyles,
IStackTokens,
Label,
Stack
} from "office-ui-fabric-react";
import * as React from "react";
import { useBoolean, useId } from "@uifabric/react-hooks";
import Localizer from "../../../localization/Localizer";
interface IPropertyDescriptionProps {
name: string;
required: boolean;
property: any;
labelId: string;
name: string;
required: boolean;
property: any;
labelId: string;
}
export const PropertyDescription: React.FunctionComponent<IPropertyDescriptionProps> = (
props
) => {
const { name, required, property, labelId } = props;
export const PropertyDescription: React.FunctionComponent<IPropertyDescriptionProps> = (props) => {
const { name, required, property, labelId } = props;
const [isCalloutVisible, { toggle: toggleIsCalloutVisible }] = useBoolean(
false
);
const descriptionId: string = useId("description");
const iconButtonId: string = useId("iconButton");
const [isCalloutVisible, { toggle: toggleIsCalloutVisible }] = useBoolean(false);
const descriptionId: string = useId("description");
const iconButtonId: string = useId("iconButton");
const stackTokens: IStackTokens = {
childrenGap: 4,
};
const labelCalloutStackStyles: Partial<IStackStyles> = {
root: { maxWidth: 300, padding: 10 },
};
const iconButtonStyles: Partial<IButtonStyles> = {
root: { marginBottom: -3 },
};
const stackTokens: IStackTokens = {
childrenGap: 4
};
const labelCalloutStackStyles: Partial<IStackStyles> = {
root: { maxWidth: 300, padding: 10 }
};
const iconButtonStyles: Partial<IButtonStyles> = {
root: { marginBottom: -3 }
};
return (
<>
<Stack horizontal verticalAlign="center" tokens={stackTokens}>
<Label
required={required}
id={labelId}
style={{
// Fabric adds a 12px padding to the required *
marginRight: required ? -12 : 0,
}}
>
{name}
</Label>
<IconButton
id={iconButtonId}
iconProps={{ iconName: "Info" }}
title={Localizer.l("propertyEditorInfoButtonTitle")}
ariaLabel={Localizer.l("propertyEditorInfoButtonAriaLabel")}
onClick={toggleIsCalloutVisible}
styles={iconButtonStyles}
/>
</Stack>
{isCalloutVisible && (
<Callout
target={"#" + iconButtonId}
setInitialFocus
onDismiss={toggleIsCalloutVisible}
ariaDescribedBy={descriptionId}
role="alertdialog"
styles={labelCalloutStackStyles}
id={descriptionId}
>
{Localizer.l(property.description)}
</Callout>
)}
</>
);
return (
<>
<Stack horizontal verticalAlign="center" tokens={stackTokens}>
<Label
required={required}
id={labelId}
style={{
// Fabric adds a 12px padding to the required *
marginRight: required ? -12 : 0
}}
>
{name}
</Label>
<IconButton
id={iconButtonId}
iconProps={{ iconName: "Info" }}
title={Localizer.l("propertyEditorInfoButtonTitle")}
ariaLabel={Localizer.l("propertyEditorInfoButtonAriaLabel")}
onClick={toggleIsCalloutVisible}
styles={iconButtonStyles}
/>
</Stack>
{isCalloutVisible && (
<Callout
target={"#" + iconButtonId}
setInitialFocus
onDismiss={toggleIsCalloutVisible}
ariaDescribedBy={descriptionId}
role="alertdialog"
styles={labelCalloutStackStyles}
id={descriptionId}
>
{Localizer.l(property.description)}
</Callout>
)}
</>
);
};

Просмотреть файл

@ -1,240 +1,218 @@
import {
Text,
TextField,
Dropdown,
IDropdownOption,
ChoiceGroup,
IChoiceGroupOption,
getTheme,
ChoiceGroup,
Dropdown,
getTheme,
IChoiceGroupOption,
IDropdownOption,
Text,
TextField
} from "office-ui-fabric-react";
import { useId } from "@uifabric/react-hooks";
import * as React from "react";
import { useId } from "@uifabric/react-hooks";
import Localizer from "../../../localization/Localizer";
import { PropertyNestedObject } from "./PropertyNestedObject";
import { PropertyDescription } from "./PropertyDescription";
import { PropertyNestedObject } from "./PropertyNestedObject";
interface IPropertyEditFieldProps {
name: string;
property: any;
nodeProperties: any;
required: boolean;
name: string;
property: any;
nodeProperties: any;
required: boolean;
}
export const PropertyEditField: React.FunctionComponent<IPropertyEditFieldProps> = (
props
) => {
const { name, property, nodeProperties, required } = props;
export const PropertyEditField: React.FunctionComponent<IPropertyEditFieldProps> = (props) => {
const { name, property, nodeProperties, required } = props;
let initValue = nodeProperties[name];
if (property.type !== "boolean" && property.type !== "string") {
initValue = JSON.stringify(initValue);
}
const [value, setValue] = React.useState<string>(initValue);
const [errorMessage, setErrorMessage] = React.useState<string>("");
function handleDropdownChange(e: React.FormEvent, item?: IDropdownOption) {
if (item) {
const value = item.key as string;
setNewValue(value);
setErrorMessage(validateInput(value));
let initValue = nodeProperties[name];
if (property.type !== "boolean" && property.type !== "string") {
initValue = JSON.stringify(initValue);
}
}
const [value, setValue] = React.useState<string>(initValue);
const [errorMessage, setErrorMessage] = React.useState<string>("");
function handleTextFieldChange(e: React.FormEvent, newValue?: string) {
if (newValue !== undefined) {
setNewValue(newValue);
}
}
function handleChoiceGroupChange(
e?: React.FormEvent,
option?: IChoiceGroupOption
) {
if (option !== undefined) {
const value = option.key as string;
setNewValue(value);
setErrorMessage(validateInput(value));
}
}
function setNewValue(newValue: string) {
setValue(newValue);
switch (property.type) {
case "boolean":
if (newValue === "true") {
nodeProperties[name] = true;
} else if (newValue === "false") {
nodeProperties[name] = false;
} else {
delete nodeProperties[name];
function handleDropdownChange(e: React.FormEvent, item?: IDropdownOption) {
if (item) {
const value = item.key as string;
setNewValue(value);
setErrorMessage(validateInput(value));
}
break;
case "string":
if (newValue) {
nodeProperties[name] = newValue;
} else {
delete nodeProperties[name];
}
break;
default:
if (newValue) {
try {
nodeProperties[name] = JSON.parse(newValue);
} catch (e) {
// no change in value
}
} else {
delete nodeProperties[name];
}
break;
}
}
function validateInput(value: string) {
if (required) {
switch (property.type) {
case "boolean":
if (value === "") {
return Localizer.l("propertyEditorValidationUndefined");
}
break;
case "string":
if (!value) {
return Localizer.l("propertyEditorValidationUndefinedOrEmpty");
}
break;
default:
if (value) {
try {
JSON.parse(value);
} catch (e) {
return Localizer.l("propertyEditorValidationInvalidJSON");
function handleTextFieldChange(e: React.FormEvent, newValue?: string) {
if (newValue !== undefined) {
setNewValue(newValue);
}
}
function handleChoiceGroupChange(e?: React.FormEvent, option?: IChoiceGroupOption) {
if (option !== undefined) {
const value = option.key as string;
setNewValue(value);
setErrorMessage(validateInput(value));
}
}
function setNewValue(newValue: string) {
setValue(newValue);
switch (property.type) {
case "boolean":
if (newValue === "true") {
nodeProperties[name] = true;
} else if (newValue === "false") {
nodeProperties[name] = false;
} else {
delete nodeProperties[name];
}
break;
case "string":
if (newValue) {
nodeProperties[name] = newValue;
} else {
delete nodeProperties[name];
}
break;
default:
if (newValue) {
try {
nodeProperties[name] = JSON.parse(newValue);
} catch (e) {
// no change in value
}
} else {
delete nodeProperties[name];
}
break;
}
}
function validateInput(value: string) {
if (required) {
switch (property.type) {
case "boolean":
if (value === "") {
return Localizer.l("propertyEditorValidationUndefined");
}
break;
case "string":
if (!value) {
return Localizer.l("propertyEditorValidationUndefinedOrEmpty");
}
break;
default:
if (value) {
try {
JSON.parse(value);
} catch (e) {
return Localizer.l("propertyEditorValidationInvalidJSON");
}
} else {
return Localizer.l("propertyEditorValidationEmpty");
}
break;
}
} else {
return Localizer.l("propertyEditorValidationEmpty");
}
break;
}
}
return "";
}
return "";
}
const labelId: string = useId("label");
const labelId: string = useId("label");
function onRenderLabel() {
return (
<PropertyDescription
name={name}
required={required}
property={property}
labelId={labelId}
/>
);
}
if (property.type === "string" && property.enum) {
const options: IDropdownOption[] = [
{
key: "",
text: Localizer.l("propertyEditorNoneValueLabel"),
},
...property.enum.map((value: string) => ({
key: value,
text: value,
})),
];
return (
<Dropdown
label={name}
options={options}
defaultSelectedKey={value || ""}
onChange={handleDropdownChange}
required={required}
onRenderLabel={onRenderLabel}
aria-labelledby={labelId}
errorMessage={errorMessage}
/>
);
} else if (property.type === "string") {
return (
<TextField
label={name}
type="text"
id={name}
value={value}
placeholder={property.example}
onChange={handleTextFieldChange}
required={required}
onRenderLabel={onRenderLabel}
aria-labelledby={labelId}
onGetErrorMessage={validateInput}
/>
);
} else if (property.type === "boolean") {
const options: IChoiceGroupOption[] = [
{
key: "",
text: Localizer.l("propertyEditorNoneValueLabel"),
},
{
key: "true",
text: Localizer.l("propertyEditorBooleanTrueLabel"),
},
{
key: "false",
text: Localizer.l("propertyEditorBooleanFalseLabel"),
},
];
return (
<>
{/* ChoiceGroup does not support onRenderLabel or errorMessage */}
{onRenderLabel()}
<ChoiceGroup
defaultSelectedKey={value === undefined ? "" : value + ""}
options={options}
onChange={handleChoiceGroupChange}
required={required}
aria-labelledby={labelId}
/>
{errorMessage && (
<Text
variant="small"
style={{ color: getTheme().semanticColors.errorText }}
>
{errorMessage}
</Text>
)}
</>
);
} else if (property.type === "object") {
const isPropertyValueSet = name in nodeProperties;
if (!isPropertyValueSet) {
nodeProperties[name] = {};
function onRenderLabel() {
return <PropertyDescription name={name} required={required} property={property} labelId={labelId} />;
}
if (property.type === "string" && property.enum) {
const options: IDropdownOption[] = [
{
key: "",
text: Localizer.l("propertyEditorNoneValueLabel")
},
...property.enum.map((value: string) => ({
key: value,
text: value
}))
];
return (
<Dropdown
label={name}
options={options}
defaultSelectedKey={value || ""}
onChange={handleDropdownChange}
required={required}
onRenderLabel={onRenderLabel}
aria-labelledby={labelId}
errorMessage={errorMessage}
/>
);
} else if (property.type === "string") {
return (
<TextField
label={name}
type="text"
id={name}
value={value}
placeholder={property.example}
onChange={handleTextFieldChange}
required={required}
onRenderLabel={onRenderLabel}
aria-labelledby={labelId}
onGetErrorMessage={validateInput}
/>
);
} else if (property.type === "boolean") {
const options: IChoiceGroupOption[] = [
{
key: "",
text: Localizer.l("propertyEditorNoneValueLabel")
},
{
key: "true",
text: Localizer.l("propertyEditorBooleanTrueLabel")
},
{
key: "false",
text: Localizer.l("propertyEditorBooleanFalseLabel")
}
];
return (
<>
{/* ChoiceGroup does not support onRenderLabel or errorMessage */}
{onRenderLabel()}
<ChoiceGroup
defaultSelectedKey={value === undefined ? "" : value + ""}
options={options}
onChange={handleChoiceGroupChange}
required={required}
aria-labelledby={labelId}
/>
{errorMessage && (
<Text variant="small" style={{ color: getTheme().semanticColors.errorText }}>
{errorMessage}
</Text>
)}
</>
);
} else if (property.type === "object") {
const isPropertyValueSet = name in nodeProperties;
if (!isPropertyValueSet) {
nodeProperties[name] = {};
}
return <PropertyNestedObject name={name} property={property} nodeProperties={nodeProperties[name]} required={required} />;
} else {
return (
<TextField
label={name}
multiline
autoAdjustHeight
defaultValue={value}
placeholder={property.example}
onChange={handleTextFieldChange}
required={required}
onRenderLabel={onRenderLabel}
aria-labelledby={labelId}
onGetErrorMessage={validateInput}
/>
);
}
return (
<PropertyNestedObject
name={name}
property={property}
nodeProperties={nodeProperties[name]}
required={required}
/>
);
} else {
return (
<TextField
label={name}
multiline
autoAdjustHeight
defaultValue={value}
placeholder={property.example}
onChange={handleTextFieldChange}
required={required}
onRenderLabel={onRenderLabel}
aria-labelledby={labelId}
onGetErrorMessage={validateInput}
/>
);
}
};

Просмотреть файл

@ -3,49 +3,44 @@ import Definitions from "../../../definitions/Definitions";
import { PropertyEditField } from "./PropertyEditField";
interface IPropertyEditorProps {
nodeProperties: any;
nodeProperties: any;
}
export const PropertyEditor: React.FunctionComponent<IPropertyEditorProps> = (
props
) => {
const { nodeProperties } = props;
const definition = Definitions.getNodeDefinition(nodeProperties);
export const PropertyEditor: React.FunctionComponent<IPropertyEditorProps> = (props) => {
const { nodeProperties } = props;
const definition = Definitions.getNodeDefinition(nodeProperties);
if (!definition) {
return null;
}
if (!definition) {
return null;
}
const propertyFields = [];
const propertyFields = [];
for (const name in definition.properties) {
const property = definition.properties[name];
for (const name in definition.properties) {
const property = definition.properties[name];
if (!property) continue;
if (!property) continue;
// skip the type field (already shown as dropdown by PropertyNestedObject)
if (name === "@type") continue;
// skip the type field (already shown as dropdown by PropertyNestedObject)
if (name === "@type") continue;
const key = "property-" + name;
propertyFields.push(
<div
key={key}
style={{
marginTop: 20,
}}
>
<PropertyEditField
name={name}
property={property}
nodeProperties={nodeProperties}
required={
(definition.required &&
definition.required.includes(name)) as boolean
}
/>
</div>
);
}
const key = "property-" + name;
propertyFields.push(
<div
key={key}
style={{
marginTop: 20
}}
>
<PropertyEditField
name={name}
property={property}
nodeProperties={nodeProperties}
required={(definition.required && definition.required.includes(name)) as boolean}
/>
</div>
);
}
return <>{propertyFields}</>;
return <>{propertyFields}</>;
};

Просмотреть файл

@ -1,93 +1,78 @@
import * as React from "react";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react";
import Definitions from "../../../definitions/Definitions";
import { PropertyEditor } from "./PropertyEditor";
import { PropertyDescription } from "./PropertyDescription";
import * as React from "react";
import { useId } from "@uifabric/react-hooks";
import Definitions from "../../../definitions/Definitions";
import Localizer from "../../../localization/Localizer";
import { PropertyDescription } from "./PropertyDescription";
import { PropertyEditor } from "./PropertyEditor";
interface IPropertyNestedObjectProps {
name: string;
property: any;
nodeProperties: any;
required: boolean;
name: string;
property: any;
nodeProperties: any;
required: boolean;
}
export const PropertyNestedObject: React.FunctionComponent<IPropertyNestedObjectProps> = (
props
) => {
const { name, property, nodeProperties, required } = props;
const initType =
nodeProperties["@type"] &&
nodeProperties["@type"].replace("#Microsoft.Media.", "");
const [type, setType] = React.useState<string>(initType);
const [errorMessage, setErrorMessage] = React.useState<string>("");
export const PropertyNestedObject: React.FunctionComponent<IPropertyNestedObjectProps> = (props) => {
const { name, property, nodeProperties, required } = props;
const initType = nodeProperties["@type"] && nodeProperties["@type"].replace("#Microsoft.Media.", "");
const [type, setType] = React.useState<string>(initType);
const [errorMessage, setErrorMessage] = React.useState<string>("");
function handleTypeChange(e: React.FormEvent, item?: IDropdownOption) {
if (item) {
const selectedType = item.key as string;
if (selectedType) {
nodeProperties["@type"] = `#Microsoft.Media.${selectedType}`;
} else {
nodeProperties["@type"] = "";
}
setType(selectedType);
if (required) {
setErrorMessage(
selectedType === ""
? Localizer.l("propertyEditorValidationUndefined")
: ""
);
}
function handleTypeChange(e: React.FormEvent, item?: IDropdownOption) {
if (item) {
const selectedType = item.key as string;
if (selectedType) {
nodeProperties["@type"] = `#Microsoft.Media.${selectedType}`;
} else {
nodeProperties["@type"] = "";
}
setType(selectedType);
if (required) {
setErrorMessage(selectedType === "" ? Localizer.l("propertyEditorValidationUndefined") : "");
}
}
}
}
const options: IDropdownOption[] = [
{
key: "",
text: Localizer.l("propertyEditorNoneValueLabel"),
},
...Definitions.getCompatibleNodes(property.parsedRef).map((node) => ({
key: node.name,
text: node.name,
})),
];
const options: IDropdownOption[] = [
{
key: "",
text: Localizer.l("propertyEditorNoneValueLabel")
},
...Definitions.getCompatibleNodes(property.parsedRef).map((node) => ({
key: node.name,
text: node.name
}))
];
const labelId: string = useId("label");
const labelId: string = useId("label");
function onRenderLabel() {
return <PropertyDescription name={name} required={required} property={property} labelId={labelId} />;
}
function onRenderLabel() {
return (
<PropertyDescription
name={name}
required={required}
property={property}
labelId={labelId}
/>
<>
<Dropdown
label={name}
options={options}
defaultSelectedKey={type || ""}
onChange={handleTypeChange}
required={required}
onRenderLabel={onRenderLabel}
aria-labelledby={labelId}
errorMessage={errorMessage}
/>
{type && (
<div
style={{
borderLeft: "1px solid",
paddingLeft: 10
}}
>
{nodeProperties && <PropertyEditor nodeProperties={nodeProperties} />}
</div>
)}
</>
);
}
return (
<>
<Dropdown
label={name}
options={options}
defaultSelectedKey={type || ""}
onChange={handleTypeChange}
required={required}
onRenderLabel={onRenderLabel}
aria-labelledby={labelId}
errorMessage={errorMessage}
/>
{type && (
<div
style={{
borderLeft: "1px solid",
paddingLeft: 10,
}}
>
{nodeProperties && <PropertyEditor nodeProperties={nodeProperties} />}
</div>
)}
</>
);
};

Просмотреть файл

@ -1,46 +1,34 @@
import * as React from "react";
import {
Dialog,
DialogType,
DialogFooter,
PrimaryButton,
DefaultButton,
DefaultButton,
Dialog,
DialogFooter,
DialogType,
PrimaryButton
} from "office-ui-fabric-react";
import * as React from "react";
import Localizer from "../../../localization/Localizer";
interface IOverwriteConfirmationProps {
shown: boolean;
confirm: () => void;
dismiss: () => void;
shown: boolean;
confirm: () => void;
dismiss: () => void;
}
export const OverwriteConfirmation: React.FunctionComponent<IOverwriteConfirmationProps> = (
props: IOverwriteConfirmationProps
) => {
const dialogContentProps = {
type: DialogType.normal,
title: Localizer.l("sampleSelectorOverwriteConfirmationTitle"),
subText: Localizer.l("sampleSelectorOverwriteConfirmationText"),
};
export const OverwriteConfirmation: React.FunctionComponent<IOverwriteConfirmationProps> = (props: IOverwriteConfirmationProps) => {
const dialogContentProps = {
type: DialogType.normal,
title: Localizer.l("sampleSelectorOverwriteConfirmationTitle"),
subText: Localizer.l("sampleSelectorOverwriteConfirmationText")
};
return (
<>
<Dialog
hidden={!props.shown}
onDismiss={props.dismiss}
dialogContentProps={dialogContentProps}
>
<DialogFooter>
<PrimaryButton
onClick={props.confirm}
text={Localizer.l("sampleSelectorOverwriteConfirmButtonText")}
/>
<DefaultButton
onClick={props.dismiss}
text={Localizer.l("sampleSelectorOverwriteKeepButtonText")}
/>
</DialogFooter>
</Dialog>
</>
);
return (
<>
<Dialog hidden={!props.shown} onDismiss={props.dismiss} dialogContentProps={dialogContentProps}>
<DialogFooter>
<PrimaryButton onClick={props.confirm} text={Localizer.l("sampleSelectorOverwriteConfirmButtonText")} />
<DefaultButton onClick={props.dismiss} text={Localizer.l("sampleSelectorOverwriteKeepButtonText")} />
</DialogFooter>
</Dialog>
</>
);
};

Просмотреть файл

@ -1,128 +1,103 @@
import * as React from "react";
import {
Dialog,
DialogType,
DialogFooter,
PrimaryButton,
DefaultButton,
Dropdown,
IDropdownOption,
SpinnerSize,
Spinner,
DefaultButton,
Dialog,
DialogFooter,
DialogType,
Dropdown,
IDropdownOption,
PrimaryButton,
Spinner,
SpinnerSize
} from "office-ui-fabric-react";
import { sampleOptionsList } from "./sampleList";
import { OverwriteConfirmation } from "./OverwriteConfirmation";
import * as React from "react";
import Localizer from "../../../localization/Localizer";
import { OverwriteConfirmation } from "./OverwriteConfirmation";
import { sampleOptionsList } from "./sampleList";
import { Status } from "./statusEnum";
interface ISampleSelectorProps {
status: Status;
setStatus: React.Dispatch<React.SetStateAction<Status>>;
loadTopology: (topology: any) => void;
status: Status;
setStatus: React.Dispatch<React.SetStateAction<Status>>;
loadTopology: (topology: any) => void;
}
export const SampleSelector: React.FunctionComponent<ISampleSelectorProps> = (
props
) => {
const { status, setStatus, loadTopology } = props;
export const SampleSelector: React.FunctionComponent<ISampleSelectorProps> = (props) => {
const { status, setStatus, loadTopology } = props;
const dialogContentProps = {
type: DialogType.close,
closeButtonAriaLabel: Localizer.l("closeButtonText"),
title: Localizer.l("sampleSelectorTitle"),
subText: Localizer.l("sampleSelectorText"),
};
const dialogContentProps = {
type: DialogType.close,
closeButtonAriaLabel: Localizer.l("closeButtonText"),
title: Localizer.l("sampleSelectorTitle"),
subText: Localizer.l("sampleSelectorText")
};
const modalProps = {
isBlocking: false,
};
const modalProps = {
isBlocking: false
};
let selectedSampleName = "";
let selectedSampleName = "";
const onChange = (
event: React.FormEvent<HTMLDivElement>,
option?: IDropdownOption
) => {
if (option) {
selectedSampleName = option.key as string;
}
};
const confirmSelection = () => {
setStatus(Status.WaitingOnSampleLoad);
fetch(
"https://api.github.com/repos/Azure/live-video-analytics/git/trees/master?recursive=1"
)
.then((response) => response.json() as any)
.then(
(data) =>
data.tree.filter((entry: any) => entry.path === selectedSampleName)[0]
.url
)
.then((apiUrl) => fetch(apiUrl))
.then((response) => response.json() as any)
.then((data) => atob(data.content))
.then((topology) => {
loadTopology(JSON.parse(topology));
dismissSelector();
});
};
const dismissSelector = () => {
setStatus(Status.NoDisplay);
};
const confirmedOverwrite = () => {
setStatus(Status.SelectSample);
};
const localizedOptionList = sampleOptionsList.map((entry) => ({
text: Localizer.l(entry.text),
key: entry.key,
}));
return (
<>
<Dialog
hidden={
status === Status.NoDisplay || status === Status.ConfirmOverwrite
const onChange = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption) => {
if (option) {
selectedSampleName = option.key as string;
}
onDismiss={dismissSelector}
dialogContentProps={dialogContentProps}
modalProps={modalProps}
>
{status === Status.WaitingOnSampleLoad ? (
<Spinner size={SpinnerSize.large} />
) : (
<Dropdown
placeholder={Localizer.l("sampleSelectorDropdownPlaceholderText")}
label={Localizer.l("sampleSelectorDropdownLabel")}
options={localizedOptionList}
onChange={onChange}
dropdownWidth={700}
/>
)}
};
<DialogFooter>
<PrimaryButton
disabled={status === Status.WaitingOnSampleLoad}
onClick={confirmSelection}
text={Localizer.l("sampleSelectorLoadSampleButtonText")}
/>
<DefaultButton
disabled={status === Status.WaitingOnSampleLoad}
onClick={dismissSelector}
text={Localizer.l("cancelButtonText")}
/>
</DialogFooter>
</Dialog>
const confirmSelection = () => {
setStatus(Status.WaitingOnSampleLoad);
<OverwriteConfirmation
shown={status === Status.ConfirmOverwrite}
confirm={confirmedOverwrite}
dismiss={dismissSelector}
/>
</>
);
fetch("https://api.github.com/repos/Azure/live-video-analytics/git/trees/master?recursive=1")
.then((response) => response.json() as any)
.then((data) => data.tree.filter((entry: any) => entry.path === selectedSampleName)[0].url)
.then((apiUrl) => fetch(apiUrl))
.then((response) => response.json() as any)
.then((data) => atob(data.content))
.then((topology) => {
loadTopology(JSON.parse(topology));
dismissSelector();
});
};
const dismissSelector = () => {
setStatus(Status.NoDisplay);
};
const confirmedOverwrite = () => {
setStatus(Status.SelectSample);
};
const localizedOptionList = sampleOptionsList.map((entry) => ({
text: Localizer.l(entry.text),
key: entry.key
}));
return (
<>
<Dialog
hidden={status === Status.NoDisplay || status === Status.ConfirmOverwrite}
onDismiss={dismissSelector}
dialogContentProps={dialogContentProps}
modalProps={modalProps}
>
{status === Status.WaitingOnSampleLoad ? (
<Spinner size={SpinnerSize.large} />
) : (
<Dropdown
placeholder={Localizer.l("sampleSelectorDropdownPlaceholderText")}
label={Localizer.l("sampleSelectorDropdownLabel")}
options={localizedOptionList}
onChange={onChange}
dropdownWidth={700}
/>
)}
<DialogFooter>
<PrimaryButton disabled={status === Status.WaitingOnSampleLoad} onClick={confirmSelection} text={Localizer.l("sampleSelectorLoadSampleButtonText")} />
<DefaultButton disabled={status === Status.WaitingOnSampleLoad} onClick={dismissSelector} text={Localizer.l("cancelButtonText")} />
</DialogFooter>
</Dialog>
<OverwriteConfirmation shown={status === Status.ConfirmOverwrite} confirm={confirmedOverwrite} dismiss={dismissSelector} />
</>
);
};

Просмотреть файл

@ -1,42 +1,31 @@
import * as React from "react";
import { DefaultButton } from "office-ui-fabric-react/lib/Button";
import { SampleSelector } from "./SampleSelector";
import * as React from "react";
import Localizer from "../../../localization/Localizer";
import { SampleSelector } from "./SampleSelector";
import { Status } from "./statusEnum";
interface ISampleSelectorTriggerProps {
setTopology: (topology: any) => void;
hasUnsavedChanges: boolean;
setTopology: (topology: any) => void;
hasUnsavedChanges: boolean;
}
export const SampleSelectorTrigger: React.FunctionComponent<ISampleSelectorTriggerProps> = (
props
) => {
const [status, setStatus] = React.useState<Status>(Status.NoDisplay);
export const SampleSelectorTrigger: React.FunctionComponent<ISampleSelectorTriggerProps> = (props) => {
const [status, setStatus] = React.useState<Status>(Status.NoDisplay);
const loadTopology = (topology: any) => {
setStatus(Status.NoDisplay);
props.setTopology(topology);
};
const loadTopology = (topology: any) => {
setStatus(Status.NoDisplay);
props.setTopology(topology);
};
const openSelector = () => {
setStatus(
props.hasUnsavedChanges ? Status.ConfirmOverwrite : Status.SelectSample
const openSelector = () => {
setStatus(props.hasUnsavedChanges ? Status.ConfirmOverwrite : Status.SelectSample);
};
return (
<>
<DefaultButton text={Localizer.l("sampleSelectorButtonText")} onClick={openSelector} />
<SampleSelector status={status} setStatus={setStatus} loadTopology={loadTopology} />
</>
);
};
return (
<>
<DefaultButton
text={Localizer.l("sampleSelectorButtonText")}
onClick={openSelector}
/>
<SampleSelector
status={status}
setStatus={setStatus}
loadTopology={loadTopology}
/>
</>
);
};

Просмотреть файл

@ -1,52 +1,52 @@
import { IDropdownOption } from "office-ui-fabric-react";
export const sampleOptionsList: IDropdownOption[] = [
{
text: "sampleSelectorDropdownEntryCvrAssetTitle",
key: "MediaGraph/topologies/cvr-asset/topology.json",
},
{
text: "sampleSelectorDropdownEntryCvrWithHttpExtensionTitle",
key: "MediaGraph/topologies/cvr-with-httpExtension/topology.json",
},
{
text: "sampleSelectorDropdownEntryCvrWithMotionTitle",
key: "MediaGraph/topologies/cvr-with-motion/topology.json",
},
{
text: "sampleSelectorDropdownEntryEvrHttpExtensionAssetsTitle",
key: "MediaGraph/topologies/evr-httpExtension-assets/topology.json",
},
{
text: "sampleSelectorDropdownEntryEvrHubMessageAssetsTitle",
key: "MediaGraph/topologies/evr-hubMessage-assets/topology.json",
},
{
text: "sampleSelectorDropdownEntryEvrHubMessageFilesTitle",
key: "MediaGraph/topologies/evr-hubMessage-files/topology.json",
},
{
text: "sampleSelectorDropdownEntryEvrMotionAssetsFilesTitle",
key: "MediaGraph/topologies/evr-motion-assets-files/topology.json",
},
{
text: "sampleSelectorDropdownEntryEvrMotionAssetsTitle",
key: "MediaGraph/topologies/evr-motion-assets/topology.json",
},
{
text: "sampleSelectorDropdownEntryEvrMotionFilesTitle",
key: "MediaGraph/topologies/evr-motion-files/topology.json",
},
{
text: "sampleSelectorDropdownEntryHttpExtensionTitle",
key: "MediaGraph/topologies/httpExtension/topology.json",
},
{
text: "sampleSelectorDropdownEntryMotionDetectionTitle",
key: "MediaGraph/topologies/motion-detection/topology.json",
},
{
text: "sampleSelectorDropdownEntryMotionWithHttpExtensionTitle",
key: "MediaGraph/topologies/motion-with-httpExtension/topology.json",
},
{
text: "sampleSelectorDropdownEntryCvrAssetTitle",
key: "MediaGraph/topologies/cvr-asset/topology.json"
},
{
text: "sampleSelectorDropdownEntryCvrWithHttpExtensionTitle",
key: "MediaGraph/topologies/cvr-with-httpExtension/topology.json"
},
{
text: "sampleSelectorDropdownEntryCvrWithMotionTitle",
key: "MediaGraph/topologies/cvr-with-motion/topology.json"
},
{
text: "sampleSelectorDropdownEntryEvrHttpExtensionAssetsTitle",
key: "MediaGraph/topologies/evr-httpExtension-assets/topology.json"
},
{
text: "sampleSelectorDropdownEntryEvrHubMessageAssetsTitle",
key: "MediaGraph/topologies/evr-hubMessage-assets/topology.json"
},
{
text: "sampleSelectorDropdownEntryEvrHubMessageFilesTitle",
key: "MediaGraph/topologies/evr-hubMessage-files/topology.json"
},
{
text: "sampleSelectorDropdownEntryEvrMotionAssetsFilesTitle",
key: "MediaGraph/topologies/evr-motion-assets-files/topology.json"
},
{
text: "sampleSelectorDropdownEntryEvrMotionAssetsTitle",
key: "MediaGraph/topologies/evr-motion-assets/topology.json"
},
{
text: "sampleSelectorDropdownEntryEvrMotionFilesTitle",
key: "MediaGraph/topologies/evr-motion-files/topology.json"
},
{
text: "sampleSelectorDropdownEntryHttpExtensionTitle",
key: "MediaGraph/topologies/httpExtension/topology.json"
},
{
text: "sampleSelectorDropdownEntryMotionDetectionTitle",
key: "MediaGraph/topologies/motion-detection/topology.json"
},
{
text: "sampleSelectorDropdownEntryMotionWithHttpExtensionTitle",
key: "MediaGraph/topologies/motion-with-httpExtension/topology.json"
}
];

Просмотреть файл

@ -1,6 +1,6 @@
export enum Status {
NoDisplay,
SelectSample,
ConfirmOverwrite,
WaitingOnSampleLoad,
NoDisplay,
SelectSample,
ConfirmOverwrite,
WaitingOnSampleLoad
}

Просмотреть файл

@ -1,39 +1,27 @@
import { DefaultButton, PrimaryButton, Stack } from "office-ui-fabric-react";
import * as React from "react";
import Localizer from "../../localization/Localizer";
import { PrimaryButton, Stack, DefaultButton } from "office-ui-fabric-react";
export interface IGraphPanelProps {
name: string;
closeEditor: () => void;
exportGraph: () => void;
name: string;
closeEditor: () => void;
exportGraph: () => void;
}
export const Toolbar: React.FunctionComponent<IGraphPanelProps> = (props) => {
const toolbarStyles = {
padding: 10,
background: "var(--vscode-editorWidget-background)",
borderBottom: "1px solid var(--vscode-editorWidget-border)",
};
const toolbarStyles = {
padding: 10,
background: "var(--vscode-editorWidget-background)",
borderBottom: "1px solid var(--vscode-editorWidget-border)"
};
return (
<Stack
horizontal
horizontalAlign="space-between"
verticalAlign="center"
tokens={{ childrenGap: "s1" }}
style={toolbarStyles}
>
<div>{props.name}</div>
<Stack horizontal horizontalAlign="end" tokens={{ childrenGap: "s1" }}>
<DefaultButton
text={Localizer.l("cancelButtonText")}
onClick={props.closeEditor}
/>
<PrimaryButton
text={Localizer.l("saveButtonText")}
onClick={props.exportGraph}
/>
</Stack>
</Stack>
);
return (
<Stack horizontal horizontalAlign="space-between" verticalAlign="center" tokens={{ childrenGap: "s1" }} style={toolbarStyles}>
<div>{props.name}</div>
<Stack horizontal horizontalAlign="end" tokens={{ childrenGap: "s1" }}>
<DefaultButton text={Localizer.l("cancelButtonText")} onClick={props.closeEditor} />
<PrimaryButton text={Localizer.l("saveButtonText")} onClick={props.exportGraph} />
</Stack>
</Stack>
);
};

Просмотреть файл

@ -1,26 +1,26 @@
export const graphTheme = {
primaryColor: "var(--vscode-foreground)",
defaultColor: "var(--vscode-foreground)",
borderColor: "var(--vscode-input-border)",
defaultBorderColor: "var(--vscode-input-border)",
defaultBackgroundColor: "var(--vscode-editor-background)",
annotationBgColor: "var(--vscode-peekViewEditor-background)",
connectedPortColor: "var(--vscode-editor-selectionBackground)",
nodeActivateFill: "var(--vscode-editor-inactiveSelectionBackground)",
nodeActivateStroke: "var(--vscode-editor-selectionBackground)",
nodeFill: "var(--vscode-editorWidget-background)",
nodeStroke: "var(--vscode-editorWidget-border)",
contextMenuBackground: "var(--vscode-menu-background)",
contextMenuBorder: "var(--vscode-menu-separatorBackground)",
contextMenuHoverBackground: "var(--vscode-menu-selectionBackground)",
fontColor: "var(--vscode-foreground)",
canvasBackground: "var(--vscode-editor-background)",
edgeColor: "var(--vscode-breadcrumb-foreground)",
edgeColorSelected: "var(--vscode-pickerGroup-foreground)",
minimapShadow: "var(--vscode-widget-shadow)",
outlineStyle: "none",
focusOutlineColor: "var(--vscode-focusBorder)",
dummyNodeStroke: "var(--vscode-foreground)",
inputFocusBorderAlt: "var(--vscode-foreground)",
buttonBorder: "var(--vscode-input-border)",
primaryColor: "var(--vscode-foreground)",
defaultColor: "var(--vscode-foreground)",
borderColor: "var(--vscode-input-border)",
defaultBorderColor: "var(--vscode-input-border)",
defaultBackgroundColor: "var(--vscode-editor-background)",
annotationBgColor: "var(--vscode-peekViewEditor-background)",
connectedPortColor: "var(--vscode-editor-selectionBackground)",
nodeActivateFill: "var(--vscode-editor-inactiveSelectionBackground)",
nodeActivateStroke: "var(--vscode-editor-selectionBackground)",
nodeFill: "var(--vscode-editorWidget-background)",
nodeStroke: "var(--vscode-editorWidget-border)",
contextMenuBackground: "var(--vscode-menu-background)",
contextMenuBorder: "var(--vscode-menu-separatorBackground)",
contextMenuHoverBackground: "var(--vscode-menu-selectionBackground)",
fontColor: "var(--vscode-foreground)",
canvasBackground: "var(--vscode-editor-background)",
edgeColor: "var(--vscode-breadcrumb-foreground)",
edgeColorSelected: "var(--vscode-pickerGroup-foreground)",
minimapShadow: "var(--vscode-widget-shadow)",
outlineStyle: "none",
focusOutlineColor: "var(--vscode-focusBorder)",
dummyNodeStroke: "var(--vscode-foreground)",
inputFocusBorderAlt: "var(--vscode-foreground)",
buttonBorder: "var(--vscode-input-border)"
};

Просмотреть файл

@ -5,32 +5,30 @@ import { InitializationParameters } from "../types/vscodeDelegationTypes";
declare const acquireVsCodeApi: any;
export async function initalizeEnvironment(language: string) {
await Localizer.loadUserLanguage(language);
await Localizer.loadUserLanguage(language);
return new Promise(
(resolve: (params: InitializationParameters) => void, reject) => {
// Check if this is running in VS Code (might be developing in React)
if (typeof acquireVsCodeApi === "function") {
(function () {
const vscode = acquireVsCodeApi();
const oldState = vscode.getState() || {};
return new Promise((resolve: (params: InitializationParameters) => void, reject) => {
// Check if this is running in VS Code (might be developing in React)
if (typeof acquireVsCodeApi === "function") {
(function () {
const vscode = acquireVsCodeApi();
const oldState = vscode.getState() || {};
// Handle messages sent from the extension to the webview
window.addEventListener("message", (event) => {
const message = event.data;
// use message.command
});
// Handle messages sent from the extension to the webview
window.addEventListener("message", (event) => {
const message = event.data;
// use message.command
});
resolve({
state: oldState,
vsCodeSetState: vscode.setState,
});
})();
} else {
// We won't save/restore state in browser, use noop function
// eslint-disable-next-line @typescript-eslint/no-empty-function
resolve({ state: {}, vsCodeSetState: () => {} });
}
}
);
resolve({
state: oldState,
vsCodeSetState: vscode.setState
});
})();
} else {
// We won't save/restore state in browser, use noop function
// eslint-disable-next-line @typescript-eslint/no-empty-function
resolve({ state: {}, vsCodeSetState: () => {} });
}
});
}

Просмотреть файл

@ -1,390 +1,362 @@
import dagre from "dagre";
import { v4 as uuid } from "uuid";
import {
ICanvasEdge,
ICanvasNode,
ICanvasData,
ICanvasData,
ICanvasEdge,
ICanvasNode
} from "@vienna/react-dag-editor";
import Definitions from "../definitions/Definitions";
import Helpers from "../helpers/Helpers";
import LocalizerHelpers from "../helpers/LocalizerHelpers";
import NodeHelpers from "../helpers/NodeHelpers";
import Localizer from "../localization/Localizer";
import {
MediaGraphProcessorUnion,
MediaGraphSinkUnion,
MediaGraphSourceUnion,
MediaGraphTopology,
MediaGraphNodeInput,
MediaGraphNodeInput,
MediaGraphProcessorUnion,
MediaGraphSinkUnion,
MediaGraphSourceUnion,
MediaGraphTopology
} from "../lva-sdk/lvaSDKtypes";
import {
GraphInfo,
MediaGraphNodeType,
CanvasNodeData,
CanvasNodeProperties,
CanvasNodeData,
CanvasNodeProperties,
GraphInfo,
MediaGraphNodeType
} from "../types/graphTypes";
import Localizer from "../localization/Localizer";
import LocalizerHelpers from "../helpers/LocalizerHelpers";
export default class Graph {
private static readonly nodeTypeList = [
MediaGraphNodeType.Source,
MediaGraphNodeType.Processor,
MediaGraphNodeType.Sink,
];
private static readonly nodeTypeList = [MediaGraphNodeType.Source, MediaGraphNodeType.Processor, MediaGraphNodeType.Sink];
// what we initialized with (contains name, description, etc.)
private graphInformation: MediaGraphTopology = { name: "" };
// what we initialized with (contains name, description, etc.)
private graphInformation: MediaGraphTopology = { name: "" };
// the target format expects dividing up the nodes into these three types
private sources: MediaGraphSourceUnion[] = [];
private processors: MediaGraphProcessorUnion[] = [];
private sinks: MediaGraphSinkUnion[] = [];
// the target format expects dividing up the nodes into these three types
private sources: MediaGraphSourceUnion[] = [];
private processors: MediaGraphProcessorUnion[] = [];
private sinks: MediaGraphSinkUnion[] = [];
// output is a list of nodes, edges, and some metadata
private nodes: ICanvasNode<CanvasNodeData>[] = [];
private edges: ICanvasEdge[] = [];
// output is a list of nodes, edges, and some metadata
private nodes: ICanvasNode<CanvasNodeData>[] = [];
private edges: ICanvasEdge[] = [];
public setGraphData(graphInfo: GraphInfo) {
this.graphInformation = graphInfo.meta;
this.nodes = graphInfo.nodes;
this.edges = graphInfo.edges;
}
public setGraphDataFromICanvasData(canvasData: ICanvasData) {
this.nodes = [...canvasData.nodes];
this.edges = [...canvasData.edges];
}
// converts internal representation to topology that can be sent using a direct method call
public setTopology(topology: any) {
this.graphInformation = topology;
this.nodes = [];
this.edges = [];
// go through all the sources, processors, and sinks we are given and flatten them into nodes
for (const nodeType of Graph.nodeTypeList) {
const nodesForType =
topology.properties[NodeHelpers.getNodeTypeKey(nodeType)];
if (!nodesForType) {
// no nodes for this type
continue;
}
for (const node of nodesForType) {
const ports = NodeHelpers.getPorts(node, nodeType).map((port) => {
return {
...port,
name: LocalizerHelpers.getPortName(node, port),
ariaLabel: LocalizerHelpers.getPortAriaLabel(
this.getICanvasData(),
node,
port
),
};
});
this.nodes.push({
id: uuid(),
name: node.name,
ariaLabel: Localizer.l("ariaNodeLabelNodeDescription").format(
node.name
),
data: {
...NodeHelpers.getNodeAppearance(nodeType),
nodeProperties: node,
nodeType: nodeType,
} as CanvasNodeData,
ports: ports,
x: 0,
y: 0,
});
}
public setGraphData(graphInfo: GraphInfo) {
this.graphInformation = graphInfo.meta;
this.nodes = graphInfo.nodes;
this.edges = graphInfo.edges;
}
this.forEachNodeInput(
(node: CanvasNodeProperties, input: MediaGraphNodeInput) => {
const sourceNode = this.getNode(input.nodeName || "");
const sourcePort = this.getPort(input.nodeName || "", false);
const targetNode = this.getNode(node.name);
const targetPort = this.getPort(node.name, true);
// since we know all of the inputs for node, we can form edges (input, node)
if (sourceNode && sourcePort && targetNode && targetPort) {
this.edges.push({
source: sourceNode.id,
target: targetNode.id,
sourcePortId: sourcePort.id,
targetPortId: targetPort.id,
id: uuid(),
});
}
}
);
this.layoutGraph();
}
public setName(name: string) {
this.graphInformation.name = name;
}
public setDescription(description: string) {
if (!this.graphInformation.properties) {
this.graphInformation.properties = {};
}
if (description === "") {
delete this.graphInformation.properties.description;
} else {
this.graphInformation.properties.description = description;
}
}
public getName(): string {
return this.graphInformation.name;
}
public getDescription(): string | undefined {
if (this.graphInformation.properties) {
return this.graphInformation.properties.description;
} else {
return undefined;
}
}
public getTopology() {
this.sources = [];
this.processors = [];
this.sinks = [];
for (const node of this.nodes) {
const nodeData = node.data;
if (nodeData) {
// only save used node properties i.e. those that match the selected types
const properties = this.getTrimmedNodeProperties(
nodeData.nodeProperties as CanvasNodeProperties
);
properties.name = nodeData.nodeProperties.name;
// get nodes pointing to this node
properties.inputs = this.getNodeInputs(node.id);
if (properties.inputs.length === 0) {
delete properties.inputs;
}
// filter into three categories
switch (nodeData.nodeType) {
case MediaGraphNodeType.Source:
this.sources.push(properties as any);
break;
case MediaGraphNodeType.Processor:
this.processors.push(properties as any);
break;
case MediaGraphNodeType.Sink:
this.sinks.push(properties as any);
break;
}
}
public setGraphDataFromICanvasData(canvasData: ICanvasData) {
this.nodes = [...canvasData.nodes];
this.edges = [...canvasData.edges];
}
const topology: MediaGraphTopology = {
name: this.graphInformation.name,
properties: {
...this.graphInformation.properties,
sources: this.sources,
processors: this.processors,
sinks: this.sinks,
},
};
if (this.graphInformation.systemData) {
topology.systemData = this.graphInformation.systemData;
}
if (this.graphInformation.apiVersion) {
// AutoRest changes @apiVersion to apiVersion, here it is changed back
(topology as any)["@apiVersion"] = this.graphInformation.apiVersion;
}
// converts internal representation to topology that can be sent using a direct method call
public setTopology(topology: any) {
this.graphInformation = topology;
this.nodes = [];
this.edges = [];
return topology;
}
public getICanvasData(): ICanvasData {
return {
nodes: this.nodes,
edges: this.edges,
};
}
public getGraphInfo(): GraphInfo {
const graphInfo = {
meta: this.getTopology(),
nodes: this.nodes,
edges: this.edges,
};
if (this.graphInformation.properties) {
const props = graphInfo.meta.properties as any;
props.description = this.graphInformation.properties.description;
props.parameters = this.graphInformation.properties.parameters;
}
return graphInfo;
}
// Internal functions
private layoutGraph() {
const g = new dagre.graphlib.Graph();
g.setGraph({
marginx: 30,
marginy: 30,
});
g.setDefaultEdgeLabel(function () {
return {};
});
const width = 350;
const height = 70;
for (const node of this.nodes) {
g.setNode(node.id, {
width: width,
height: height,
});
}
for (const edge of this.edges) {
g.setEdge((edge.source as unknown) as dagre.Edge, edge.target);
}
dagre.layout(g);
this.nodes = this.nodes.map((node) => ({
...node,
x: g.node(node.id).x - width / 2,
y: g.node(node.id).y - height / 2,
}));
}
// helper that gets a node object from its string
private getNode(nodeName: string) {
for (const node of this.nodes) {
if (node.name === nodeName) {
return node;
}
}
return null;
}
// get the input or output port for a node named nodeName
private getPort(nodeName: string, input: boolean) {
const node = this.getNode(nodeName);
if (node && node.ports) {
for (const port of node.ports) {
if (port.isOutputDisabled === input) {
return port;
}
}
}
return null;
}
// helper to loop through all inputs for all nodes
private forEachNodeInput(
callback: (node: CanvasNodeProperties, input: MediaGraphNodeInput) => void
) {
if (this.graphInformation && this.graphInformation.properties) {
for (const nodeType of Graph.nodeTypeList) {
const nodesForType = (this.graphInformation.properties as Record<
string,
CanvasNodeProperties[]
>)[NodeHelpers.getNodeTypeKey(nodeType)];
if (!nodesForType) {
// no nodes for this type
continue;
}
for (const node of nodesForType) {
if (node.inputs) {
for (const input of node.inputs) {
callback(node, input);
// go through all the sources, processors, and sinks we are given and flatten them into nodes
for (const nodeType of Graph.nodeTypeList) {
const nodesForType = topology.properties[NodeHelpers.getNodeTypeKey(nodeType)];
if (!nodesForType) {
// no nodes for this type
continue;
}
}
}
}
}
}
/* To be able to switch between multiple different types of properties without
for (const node of nodesForType) {
const ports = NodeHelpers.getPorts(node, nodeType).map((port) => {
return {
...port,
name: LocalizerHelpers.getPortName(node, port),
ariaLabel: LocalizerHelpers.getPortAriaLabel(this.getICanvasData(), node, port)
};
});
this.nodes.push({
id: uuid(),
name: node.name,
ariaLabel: Localizer.l("ariaNodeLabelNodeDescription").format(node.name),
data: {
...NodeHelpers.getNodeAppearance(nodeType),
nodeProperties: node,
nodeType: nodeType
} as CanvasNodeData,
ports: ports,
x: 0,
y: 0
});
}
}
this.forEachNodeInput((node: CanvasNodeProperties, input: MediaGraphNodeInput) => {
const sourceNode = this.getNode(input.nodeName || "");
const sourcePort = this.getPort(input.nodeName || "", false);
const targetNode = this.getNode(node.name);
const targetPort = this.getPort(node.name, true);
// since we know all of the inputs for node, we can form edges (input, node)
if (sourceNode && sourcePort && targetNode && targetPort) {
this.edges.push({
source: sourceNode.id,
target: targetNode.id,
sourcePortId: sourcePort.id,
targetPortId: targetPort.id,
id: uuid()
});
}
});
this.layoutGraph();
}
public setName(name: string) {
this.graphInformation.name = name;
}
public setDescription(description: string) {
if (!this.graphInformation.properties) {
this.graphInformation.properties = {};
}
if (description === "") {
delete this.graphInformation.properties.description;
} else {
this.graphInformation.properties.description = description;
}
}
public getName(): string {
return this.graphInformation.name;
}
public getDescription(): string | undefined {
if (this.graphInformation.properties) {
return this.graphInformation.properties.description;
} else {
return undefined;
}
}
public getTopology() {
this.sources = [];
this.processors = [];
this.sinks = [];
for (const node of this.nodes) {
const nodeData = node.data;
if (nodeData) {
// only save used node properties i.e. those that match the selected types
const properties = this.getTrimmedNodeProperties(nodeData.nodeProperties as CanvasNodeProperties);
properties.name = nodeData.nodeProperties.name;
// get nodes pointing to this node
properties.inputs = this.getNodeInputs(node.id);
if (properties.inputs.length === 0) {
delete properties.inputs;
}
// filter into three categories
switch (nodeData.nodeType) {
case MediaGraphNodeType.Source:
this.sources.push(properties as any);
break;
case MediaGraphNodeType.Processor:
this.processors.push(properties as any);
break;
case MediaGraphNodeType.Sink:
this.sinks.push(properties as any);
break;
}
}
}
const topology: MediaGraphTopology = {
name: this.graphInformation.name,
properties: {
...this.graphInformation.properties,
sources: this.sources,
processors: this.processors,
sinks: this.sinks
}
};
if (this.graphInformation.systemData) {
topology.systemData = this.graphInformation.systemData;
}
if (this.graphInformation.apiVersion) {
// AutoRest changes @apiVersion to apiVersion, here it is changed back
(topology as any)["@apiVersion"] = this.graphInformation.apiVersion;
}
return topology;
}
public getICanvasData(): ICanvasData {
return {
nodes: this.nodes,
edges: this.edges
};
}
public getGraphInfo(): GraphInfo {
const graphInfo = {
meta: this.getTopology(),
nodes: this.nodes,
edges: this.edges
};
if (this.graphInformation.properties) {
const props = graphInfo.meta.properties as any;
props.description = this.graphInformation.properties.description;
props.parameters = this.graphInformation.properties.parameters;
}
return graphInfo;
}
// Internal functions
private layoutGraph() {
const g = new dagre.graphlib.Graph();
g.setGraph({
marginx: 30,
marginy: 30
});
g.setDefaultEdgeLabel(function () {
return {};
});
const width = 350;
const height = 70;
for (const node of this.nodes) {
g.setNode(node.id, {
width: width,
height: height
});
}
for (const edge of this.edges) {
g.setEdge((edge.source as unknown) as dagre.Edge, edge.target);
}
dagre.layout(g);
this.nodes = this.nodes.map((node) => ({
...node,
x: g.node(node.id).x - width / 2,
y: g.node(node.id).y - height / 2
}));
}
// helper that gets a node object from its string
private getNode(nodeName: string) {
for (const node of this.nodes) {
if (node.name === nodeName) {
return node;
}
}
return null;
}
// get the input or output port for a node named nodeName
private getPort(nodeName: string, input: boolean) {
const node = this.getNode(nodeName);
if (node && node.ports) {
for (const port of node.ports) {
if (port.isOutputDisabled === input) {
return port;
}
}
}
return null;
}
// helper to loop through all inputs for all nodes
private forEachNodeInput(callback: (node: CanvasNodeProperties, input: MediaGraphNodeInput) => void) {
if (this.graphInformation && this.graphInformation.properties) {
for (const nodeType of Graph.nodeTypeList) {
const nodesForType = (this.graphInformation.properties as Record<string, CanvasNodeProperties[]>)[NodeHelpers.getNodeTypeKey(nodeType)];
if (!nodesForType) {
// no nodes for this type
continue;
}
for (const node of nodesForType) {
if (node.inputs) {
for (const input of node.inputs) {
callback(node, input);
}
}
}
}
}
}
/* To be able to switch between multiple different types of properties without
loosing the values or properties not needed for the selected type, properties
that might not be needed are retained. We can remove these when exporting. */
private getTrimmedNodeProperties(
nodeProperties: CanvasNodeProperties
): CanvasNodeProperties {
const definition = Definitions.getNodeDefinition(nodeProperties);
const neededProperties: any = {};
private getTrimmedNodeProperties(nodeProperties: CanvasNodeProperties): CanvasNodeProperties {
const definition = Definitions.getNodeDefinition(nodeProperties);
const neededProperties: any = {};
if (!definition) {
return {
"@type": nodeProperties["@type"],
name: nodeProperties.name,
};
}
// copy over only properties as needed (determined by definition)
for (const name in definition.properties) {
const property = definition.properties[name];
const nestedProperties = (nodeProperties as any)[name];
if (nestedProperties) {
if (property && property.type === "object") {
if (
!Helpers.isEmptyObject(nestedProperties) &&
nestedProperties["@type"]
) {
neededProperties[name] = this.getTrimmedNodeProperties(
nestedProperties
);
}
} else {
neededProperties[name] = nestedProperties;
if (!definition) {
return {
"@type": nodeProperties["@type"],
name: nodeProperties.name
};
}
}
// copy over only properties as needed (determined by definition)
for (const name in definition.properties) {
const property = definition.properties[name];
const nestedProperties = (nodeProperties as any)[name];
if (nestedProperties) {
if (property && property.type === "object") {
if (!Helpers.isEmptyObject(nestedProperties) && nestedProperties["@type"]) {
neededProperties[name] = this.getTrimmedNodeProperties(nestedProperties);
}
} else {
neededProperties[name] = nestedProperties;
}
}
}
// validate if any required properties are missing
for (const name in definition.properties) {
const isRequiredProperty = definition.required?.includes(name);
const usedProperties = neededProperties[name];
const propertyIsMissing = !usedProperties || Helpers.isEmptyObject(usedProperties);
if (isRequiredProperty && propertyIsMissing) {
// TODO bubble up and show with validation errors in interface
console.log("Expected to see property", name);
}
}
return {
"@type": nodeProperties["@type"],
...neededProperties
};
}
// validate if any required properties are missing
for (const name in definition.properties) {
const isRequiredProperty = definition.required?.includes(name);
const usedProperties = neededProperties[name];
const propertyIsMissing =
!usedProperties || Helpers.isEmptyObject(usedProperties);
if (isRequiredProperty && propertyIsMissing) {
// TODO bubble up and show with validation errors in interface
console.log("Expected to see property", name);
}
// helper method that converts a node ID -> name
private getNodeName(nodeId: string) {
for (const node of this.nodes) {
if (node.id === nodeId) {
return node.name;
}
}
return null;
}
return {
"@type": nodeProperties["@type"],
...neededProperties,
};
}
// helper method that converts a node ID -> name
private getNodeName(nodeId: string) {
for (const node of this.nodes) {
if (node.id === nodeId) {
return node.name;
}
// converts from edges (u, v) to an array of nodes [u] pointing to v
private getNodeInputs(nodeId: string) {
const inboundEdges = this.edges.filter((edge) => edge.target === nodeId);
return inboundEdges.map(
(edge) =>
({
nodeName: this.getNodeName(edge.source)
} as MediaGraphNodeInput)
);
}
return null;
}
// converts from edges (u, v) to an array of nodes [u] pointing to v
private getNodeInputs(nodeId: string) {
const inboundEdges = this.edges.filter((edge) => edge.target === nodeId);
return inboundEdges.map(
(edge) =>
({
nodeName: this.getNodeName(edge.source),
} as MediaGraphNodeInput)
);
}
}

Просмотреть файл

@ -1,13 +1,13 @@
export default class Helpers {
// checks if an object is {}
static isEmptyObject(object: any) {
return Object.keys(object).length === 0;
}
// checks if an object is {}
static isEmptyObject(object: any) {
return Object.keys(object).length === 0;
}
// lowercase first letter
static lowercaseFirstCharacter(name: string): string {
name = name.replace("MediaGraph", "");
name = name.charAt(0).toLowerCase() + name.slice(1);
return name;
}
// lowercase first letter
static lowercaseFirstCharacter(name: string): string {
name = name.replace("MediaGraph", "");
name = name.charAt(0).toLowerCase() + name.slice(1);
return name;
}
}

Просмотреть файл

@ -3,26 +3,21 @@ import { initializeIcons } from "@uifabric/icons";
import { registerIcons } from "office-ui-fabric-react";
export default class IconSetupHelpers {
static initializeIcons() {
initializeIcons();
static initializeIcons() {
initializeIcons();
registerIcons({
icons: {
// https://iconcloud.design/home/search/camera/Full%20MDL2%20Assets/Full%20MDL2%20Assets/eb35
SecurityCamera: (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2048 2048"
width="1em"
height="1em"
>
<path
fill="currentColor"
d="M1833 925l-71-18-234 469-632-158v702H0v-128h768v-606l-128-32v510H0v-128h512v-414L50 1007q57-224 112-446t111-446l1747 437-187 373zM366 271l-36 146 1413 354-13 54 13-54 21 5 70-139L366 271zm1269 605L299 542l-93 371 1253 314 176-351z"
/>
</svg>
),
},
});
}
registerIcons({
icons: {
// https://iconcloud.design/home/search/camera/Full%20MDL2%20Assets/Full%20MDL2%20Assets/eb35
SecurityCamera: (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="1em" height="1em">
<path
fill="currentColor"
d="M1833 925l-71-18-234 469-632-158v702H0v-128h768v-606l-128-32v510H0v-128h512v-414L50 1007q57-224 112-446t111-446l1747 437-187 373zM366 271l-36 146 1413 354-13 54 13-54 21 5 70-139L366 271zm1269 605L299 542l-93 371 1253 314 176-351z"
/>
</svg>
)
}
});
}
}

Просмотреть файл

@ -1,86 +1,67 @@
import {
ICanvasData,
ICanvasNode,
ICanvasPort,
ICanvasData,
ICanvasNode,
ICanvasPort
} from "@vienna/react-dag-editor";
import Localizer from "../localization/Localizer";
export default class LocalizerHelpers {
static getPortName(node: ICanvasNode, port: ICanvasPort) {
const isOutputPort = port.isInputDisabled;
const localizationKey = isOutputPort
? "ariaOutputPortDescription"
: "ariaInputPortDescription";
return `${Localizer.l(localizationKey).format(node.name)}`;
}
static getPortAriaLabel(
data: ICanvasData,
node: ICanvasNode,
port: ICanvasPort
): string {
const connectedNodeNames: string[] = [];
const isOutputPort = port.isInputDisabled;
if (isOutputPort) {
// for output ports we need to find all edges starting here and
// then get all nodes that are pointed to by the edge
data.edges
.filter((edge) => node.id === edge.source)
.map((edge) =>
data.nodes.filter((otherNode) => otherNode.id === edge.target)
)
.forEach((connectedNodes) => {
// we now have a list of nodes connected to this port, add their names
connectedNodeNames.push(
...connectedNodes.map((node) => node.name || "")
);
});
} else {
// for input ports use the same approach, but vice versa
data.edges
.filter((edge) => node.id === edge.target)
.map((edge) =>
data.nodes.filter((otherNode) => otherNode.id === edge.source)
)
.forEach((connectedNodes) => {
connectedNodeNames.push(
...connectedNodes.map((node) => node.name || "")
);
});
static getPortName(node: ICanvasNode, port: ICanvasPort) {
const isOutputPort = port.isInputDisabled;
const localizationKey = isOutputPort ? "ariaOutputPortDescription" : "ariaInputPortDescription";
return `${Localizer.l(localizationKey).format(node.name)}`;
}
if (connectedNodeNames.length > 0) {
return `${LocalizerHelpers.getPortName(node, port)}. ${Localizer.l(
"ariaPortLabelConnectedToNodes"
).format(connectedNodeNames.join(", "))}`;
} else {
return LocalizerHelpers.getPortName(node, port);
}
}
static getNodeAriaLabel(node: ICanvasNode): string {
let portDescription = "";
if (node.ports && node.ports?.length > 0) {
let hasInput = false;
let hasOutput = false;
node.ports?.forEach((port) => {
if (!port.isInputDisabled) {
hasInput = true;
} else if (!port.isOutputDisabled) {
hasOutput = true;
static getPortAriaLabel(data: ICanvasData, node: ICanvasNode, port: ICanvasPort): string {
const connectedNodeNames: string[] = [];
const isOutputPort = port.isInputDisabled;
if (isOutputPort) {
// for output ports we need to find all edges starting here and
// then get all nodes that are pointed to by the edge
data.edges
.filter((edge) => node.id === edge.source)
.map((edge) => data.nodes.filter((otherNode) => otherNode.id === edge.target))
.forEach((connectedNodes) => {
// we now have a list of nodes connected to this port, add their names
connectedNodeNames.push(...connectedNodes.map((node) => node.name || ""));
});
} else {
// for input ports use the same approach, but vice versa
data.edges
.filter((edge) => node.id === edge.target)
.map((edge) => data.nodes.filter((otherNode) => otherNode.id === edge.source))
.forEach((connectedNodes) => {
connectedNodeNames.push(...connectedNodes.map((node) => node.name || ""));
});
}
if (connectedNodeNames.length > 0) {
return `${LocalizerHelpers.getPortName(node, port)}. ${Localizer.l("ariaPortLabelConnectedToNodes").format(connectedNodeNames.join(", "))}`;
} else {
return LocalizerHelpers.getPortName(node, port);
}
});
if (hasInput && hasOutput) {
portDescription = Localizer.l("ariaHasBothPortsLabel");
} else if (hasInput) {
portDescription = Localizer.l("ariaHasInputPortLabel");
} else if (hasOutput) {
portDescription = Localizer.l("ariaHasOutputPortLabel");
}
}
return Localizer.l("ariaNodeLabelNodeNameWithPorts").format(
node.name,
portDescription
);
}
static getNodeAriaLabel(node: ICanvasNode): string {
let portDescription = "";
if (node.ports && node.ports?.length > 0) {
let hasInput = false;
let hasOutput = false;
node.ports?.forEach((port) => {
if (!port.isInputDisabled) {
hasInput = true;
} else if (!port.isOutputDisabled) {
hasOutput = true;
}
});
if (hasInput && hasOutput) {
portDescription = Localizer.l("ariaHasBothPortsLabel");
} else if (hasInput) {
portDescription = Localizer.l("ariaHasInputPortLabel");
} else if (hasOutput) {
portDescription = Localizer.l("ariaHasOutputPortLabel");
}
}
return Localizer.l("ariaNodeLabelNodeNameWithPorts").format(node.name, portDescription);
}
}

Просмотреть файл

@ -3,117 +3,105 @@ import { ICanvasNode, ICanvasPort } from "@vienna/react-dag-editor";
import { MediaGraphNodeType, NodeDefinition } from "../types/graphTypes";
export default class NodeHelpers {
// maps a MediaGraphNodeType to a string to index into the topology JSON
static getNodeTypeKey(type: MediaGraphNodeType): string {
switch (type) {
case MediaGraphNodeType.Source:
return "sources";
case MediaGraphNodeType.Processor:
return "processors";
case MediaGraphNodeType.Sink:
return "sinks";
default:
return "";
}
}
// maps a string representation of a node's type to a MediaGraphNodeType
static getNodeTypeFromString(type: string): MediaGraphNodeType {
switch (type) {
case "sources":
return MediaGraphNodeType.Source;
case "processors":
return MediaGraphNodeType.Processor;
case "sinks":
return MediaGraphNodeType.Sink;
default:
return MediaGraphNodeType.Other;
}
}
// returns the appropriate ports for a node (proper input and input according to type)
static getPorts(
node: NodeDefinition,
type?: MediaGraphNodeType
): ICanvasPort[] {
const ports = [];
// type might be a value of MediaGraphNodeType that maps to 0, which is falsy
const nodeType = typeof type === "undefined" ? node.nodeType : type;
if (
nodeType === MediaGraphNodeType.Source ||
nodeType === MediaGraphNodeType.Processor
) {
ports.push({
id: uuid(),
shape: "modulePort",
isInputDisabled: true,
isOutputDisabled: false,
name: "", // will be localized later
});
// maps a MediaGraphNodeType to a string to index into the topology JSON
static getNodeTypeKey(type: MediaGraphNodeType): string {
switch (type) {
case MediaGraphNodeType.Source:
return "sources";
case MediaGraphNodeType.Processor:
return "processors";
case MediaGraphNodeType.Sink:
return "sinks";
default:
return "";
}
}
if (
nodeType === MediaGraphNodeType.Sink ||
nodeType === MediaGraphNodeType.Processor
) {
ports.push({
id: uuid(),
shape: "modulePort",
isInputDisabled: false,
isOutputDisabled: true,
name: "", // will be localized later
});
// maps a string representation of a node's type to a MediaGraphNodeType
static getNodeTypeFromString(type: string): MediaGraphNodeType {
switch (type) {
case "sources":
return MediaGraphNodeType.Source;
case "processors":
return MediaGraphNodeType.Processor;
case "sinks":
return MediaGraphNodeType.Sink;
default:
return MediaGraphNodeType.Other;
}
}
return ports;
}
// returns the appropriate ports for a node (proper input and input according to type)
static getPorts(node: NodeDefinition, type?: MediaGraphNodeType): ICanvasPort[] {
const ports = [];
// type might be a value of MediaGraphNodeType that maps to 0, which is falsy
const nodeType = typeof type === "undefined" ? node.nodeType : type;
// determines appearance properties for a node
static getNodeAppearance(type: MediaGraphNodeType) {
switch (type) {
case MediaGraphNodeType.Source:
return {
iconName: "SecurityCamera",
color: "var(--vscode-terminal-ansiBrightBlue)",
colorAlt: "var(--vscode-terminal-ansiBlue)",
};
case MediaGraphNodeType.Processor:
return {
iconName: "Processing",
color: "var(--vscode-terminal-ansiBrightGreen)",
colorAlt: "var(--vscode-terminal-ansiGreen)",
};
case MediaGraphNodeType.Sink:
return {
iconName: "CloudImportExport",
color: "var(--vscode-terminal-ansiBrightYellow)",
colorAlt: "var(--vscode-terminal-ansiYellow)",
};
default:
return {};
if (nodeType === MediaGraphNodeType.Source || nodeType === MediaGraphNodeType.Processor) {
ports.push({
id: uuid(),
shape: "modulePort",
isInputDisabled: true,
isOutputDisabled: false,
name: "" // will be localized later
});
}
if (nodeType === MediaGraphNodeType.Sink || nodeType === MediaGraphNodeType.Processor) {
ports.push({
id: uuid(),
shape: "modulePort",
isInputDisabled: false,
isOutputDisabled: true,
name: "" // will be localized later
});
}
return ports;
}
}
// evaluates if a node can be connected to another node (has to be downstream)
static nodeCanConnectToNode(source: ICanvasNode, target: ICanvasNode) {
if (source.data && target.data) {
const sourceNodeType = source.data.nodeType;
const targetNodeType = target.data.nodeType;
switch (sourceNodeType) {
case MediaGraphNodeType.Source:
case MediaGraphNodeType.Processor:
return (
MediaGraphNodeType.Processor === targetNodeType ||
MediaGraphNodeType.Sink === targetNodeType
);
case MediaGraphNodeType.Sink:
return false;
default:
break;
}
// determines appearance properties for a node
static getNodeAppearance(type: MediaGraphNodeType) {
switch (type) {
case MediaGraphNodeType.Source:
return {
iconName: "SecurityCamera",
color: "var(--vscode-terminal-ansiBrightBlue)",
colorAlt: "var(--vscode-terminal-ansiBlue)"
};
case MediaGraphNodeType.Processor:
return {
iconName: "Processing",
color: "var(--vscode-terminal-ansiBrightGreen)",
colorAlt: "var(--vscode-terminal-ansiGreen)"
};
case MediaGraphNodeType.Sink:
return {
iconName: "CloudImportExport",
color: "var(--vscode-terminal-ansiBrightYellow)",
colorAlt: "var(--vscode-terminal-ansiYellow)"
};
default:
return {};
}
}
// evaluates if a node can be connected to another node (has to be downstream)
static nodeCanConnectToNode(source: ICanvasNode, target: ICanvasNode) {
if (source.data && target.data) {
const sourceNodeType = source.data.nodeType;
const targetNodeType = target.data.nodeType;
switch (sourceNodeType) {
case MediaGraphNodeType.Source:
case MediaGraphNodeType.Processor:
return MediaGraphNodeType.Processor === targetNodeType || MediaGraphNodeType.Sink === targetNodeType;
case MediaGraphNodeType.Sink:
return false;
default:
break;
}
}
return true;
}
return true;
}
}

Просмотреть файл

@ -1,56 +1,51 @@
import {
ThemeGenerator,
getColorFromString,
themeRulesStandardCreator,
BaseSlots,
isDark,
createTheme,
ITheme,
IThemeSlotRule,
IColor,
BaseSlots,
createTheme,
getColorFromString,
IColor,
isDark,
ITheme,
IThemeSlotRule,
ThemeGenerator,
themeRulesStandardCreator
} from "office-ui-fabric-react";
export default class ThemeHelpers {
static getAdaptedTheme(): ITheme {
const themeRules = themeRulesStandardCreator();
const colors = {
primaryColor: getColorFromString("var(--vscode-button-background)")!,
textColor: getColorFromString("var(--vscode-foreground)")!,
backgroundColor: getColorFromString("var(--vscode-editor-background)")!,
};
static getAdaptedTheme(): ITheme {
const themeRules = themeRulesStandardCreator();
const colors = {
primaryColor: getColorFromString("var(--vscode-button-background)")!,
textColor: getColorFromString("var(--vscode-foreground)")!,
backgroundColor: getColorFromString("var(--vscode-editor-background)")!
};
function setSlot(rule: IThemeSlotRule, color: string | IColor) {
ThemeGenerator.setSlot(rule, color, undefined, true, true);
function setSlot(rule: IThemeSlotRule, color: string | IColor) {
ThemeGenerator.setSlot(rule, color, undefined, true, true);
}
setSlot(themeRules[BaseSlots[BaseSlots.primaryColor]], colors.primaryColor);
setSlot(themeRules[BaseSlots[BaseSlots.foregroundColor]], colors.textColor);
setSlot(themeRules[BaseSlots[BaseSlots.backgroundColor]], colors.backgroundColor);
// TODO: Test in VS Code (there were some contrast issues with Dracula)
const isInverted = isDark(themeRules[BaseSlots[BaseSlots.backgroundColor]].color!);
ThemeGenerator.insureSlots(themeRules, isInverted);
return createTheme({
palette: ThemeGenerator.getThemeAsJson(themeRules),
isInverted
});
}
setSlot(themeRules[BaseSlots[BaseSlots.primaryColor]], colors.primaryColor);
setSlot(themeRules[BaseSlots[BaseSlots.foregroundColor]], colors.textColor);
setSlot(
themeRules[BaseSlots[BaseSlots.backgroundColor]],
colors.backgroundColor
);
// TODO: Test in VS Code (there were some contrast issues with Dracula)
const isInverted = isDark(
themeRules[BaseSlots[BaseSlots.backgroundColor]].color!
);
ThemeGenerator.insureSlots(themeRules, isInverted);
return createTheme({
palette: ThemeGenerator.getThemeAsJson(themeRules),
isInverted,
});
}
static attachHtmlStyleAttrListener(callback: () => void): MutationObserver {
const observer = new MutationObserver(() => {
callback();
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["style"],
});
return observer;
}
static attachHtmlStyleAttrListener(callback: () => void): MutationObserver {
const observer = new MutationObserver(() => {
callback();
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["style"]
});
return observer;
}
}

Просмотреть файл

@ -1,27 +1,21 @@
import "./bootstrap.js";
import "./scripts/formatString";
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { initalizeEnvironment } from "./extension/extensionInteraction";
import { InitializationParameters } from "./types/vscodeDelegationTypes";
import "./scripts/formatString";
initalizeEnvironment((window as any).language).then(
(params: InitializationParameters) => {
initalizeEnvironment((window as any).language).then((params: InitializationParameters) => {
// if we are running in VS Code and have stored state, we can recover it from state
// vsCodeSetState will allow for setting that state
// saving and restoring state happens when the webview loses and regains focus
const { state, vsCodeSetState } = params;
ReactDOM.render(
<React.StrictMode>
<App
graphData={state.graphData}
zoomPanSettings={state.zoomPanSettings}
vsCodeSetState={vsCodeSetState}
/>
</React.StrictMode>,
document.getElementById("root")
<React.StrictMode>
<App graphData={state.graphData} zoomPanSettings={state.zoomPanSettings} vsCodeSetState={vsCodeSetState} />
</React.StrictMode>,
document.getElementById("root")
);
}
);
});

Просмотреть файл

@ -1,37 +1,33 @@
export default class Localizer {
private static localized: Record<string, string> = {};
private static localized: Record<string, string> = {};
static async getLanguage(language: string) {
const interfaceLocStrings = await import(
/* webpackMode: "lazy" */ `./${language}.json`
);
const swaggerLocStrings = await import(
/* webpackMode: "lazy" */ `../definitions/v1.0/i18n.${language}.json`
);
static async getLanguage(language: string) {
const interfaceLocStrings = await import(/* webpackMode: "lazy" */ `./${language}.json`);
const swaggerLocStrings = await import(/* webpackMode: "lazy" */ `../definitions/v1.0/i18n.${language}.json`);
return {
...interfaceLocStrings,
...swaggerLocStrings,
};
}
static async loadUserLanguage(forceLang?: string) {
let language = forceLang || "en";
// navigator might not be set, for example when running tests
if (!forceLang && typeof navigator !== "undefined") {
language = navigator.language || navigator.languages[0];
return {
...interfaceLocStrings,
...swaggerLocStrings
};
}
language = language.toLowerCase().split("-")[0]; // en-US -> en
try {
this.localized = await this.getLanguage(language);
} catch (error) {
language = "en";
this.localized = await this.getLanguage(language);
static async loadUserLanguage(forceLang?: string) {
let language = forceLang || "en";
// navigator might not be set, for example when running tests
if (!forceLang && typeof navigator !== "undefined") {
language = navigator.language || navigator.languages[0];
}
language = language.toLowerCase().split("-")[0]; // en-US -> en
try {
this.localized = await this.getLanguage(language);
} catch (error) {
language = "en";
this.localized = await this.getLanguage(language);
}
}
}
static l(key: string) {
return this.localized[key];
}
static l(key: string) {
return this.localized[key];
}
}

Просмотреть файл

@ -1,73 +1,73 @@
{
"conformingToType": "conforming to {0}",
"required": "required",
"example": "Example",
"copy": "Copy",
"delete": "Delete",
"copySelected": "Copy selected",
"deleteSelected": "Delete selected",
"paste": "Paste",
"browserNotSupported": "Browser not supported",
"sidebarHeadingParameters": "Parameters",
"sources": "Sources",
"processors": "Processors",
"sinks": "Sinks",
"saveButtonText": "Save",
"propertyEditorNoneValueLabel": "None",
"propertyEditorBooleanTrueLabel": "True",
"propertyEditorBooleanFalseLabel": "False",
"optionalDefaultValue": "Optional default value",
"connectedToNodes": "Connected to {0}",
"nodeNameWithPorts": "Node named {0} with {1}",
"nodeNamed": "Node named {0}",
"inputPortDescription": "Input port of {0}",
"outputPortDescription": "Output port of {0}",
"input": "input",
"output": "output",
"cancelButtonText": "Cancel",
"closeButtonText": "Close",
"sampleSelectorButtonText": "Try sample topologies",
"sampleSelectorTitle": "Select a Sample Topology",
"sampleSelectorText": "Jumpstart your topology creation with our built-in samples.",
"sampleSelectorDropdownLabel": "Topology Name",
"sampleSelectorDropdownPlaceholderText": "Select a sample topology",
"sampleSelectorLoadSampleButtonText": "Load sample",
"sampleSelectorOverwriteConfirmationTitle": "Existing Changes",
"sampleSelectorOverwriteConfirmationText": "Your existing changes will be lost if you load a sample. Do you want to overwrite or keep your changes?",
"sampleSelectorOverwriteConfirmButtonText": "Overwrite",
"sampleSelectorOverwriteKeepButtonText": "Keep",
"sampleSelectorDropdownEntryCvrAssetTitle": "Continuous video recording to an Azure Media Services Asset",
"sampleSelectorDropdownEntryCvrWithHttpExtensionTitle": "Continuous video recording and inferencing using HTTP Extension",
"sampleSelectorDropdownEntryCvrWithMotionTitle": "Continuous video recording with Motion Detection",
"sampleSelectorDropdownEntryEvrHttpExtensionAssetsTitle": "Event-based video recording to Assets based on events from external AI",
"sampleSelectorDropdownEntryEvrHubMessageAssetsTitle": "Event-based video recording to Assets based on specific objects being detected by external inference engine",
"sampleSelectorDropdownEntryEvrHubMessageFilesTitle": "Event-based recording of video to files based on messages sent via IoT Edge Hub",
"sampleSelectorDropdownEntryEvrMotionAssetsFilesTitle": "Event-based video recording to assets and local files based on motion events",
"sampleSelectorDropdownEntryEvrMotionAssetsTitle": "Event-based video recording to Assets based on motion events",
"sampleSelectorDropdownEntryEvrMotionFilesTitle": "Event-based video recording to local files based on motion events",
"sampleSelectorDropdownEntryHttpExtensionTitle": "Analyzing live video using HTTP Extension to send images to an external inference engine",
"sampleSelectorDropdownEntryMotionDetectionTitle": "Analyzing live video to detect motion and emit events",
"sampleSelectorDropdownEntryMotionWithHttpExtensionTitle": "Event-based video recording to Assets based on motion events, and using HTTP Extension to send images to an external inference engine",
"sidebarTopologyComponentTitle": "Topology Components",
"sidebarTopologyComponentText": "Select a source, processor, and sink to build a topology.",
"sidebarGraphTopologyNameLabel": "Topology name",
"sidebarGraphInstanceNameLabel": "Instance name",
"sidebarGraphNamePlaceholder": "Enter a name",
"sidebarGraphDescriptionLabel": "Description",
"sidebarGraphDescriptionPlaceholder": "Enter a description",
"ariaPortLabelConnectedToNodes": "Connected to {0}",
"ariaNodeLabelNodeNameWithPorts": "Node named {0} with {1}",
"ariaNodeLabelNodeDescription": "Node named {0}",
"ariaHasInputPortLabel": "input port",
"ariaHasOutputPortLabel": "output port",
"ariaHasBothPortsLabel": "input and output port",
"ariaInputPortDescription": "Input port of {0}",
"ariaOutputPortDescription": "Output port of {0}",
"propertyEditorCloseButtonAriaLabel": "Click to close the property editor.",
"propertyEditorInfoButtonTitle": "Show more information",
"propertyEditorInfoButtonAriaLabel": "Click to view more information about this property.",
"propertyEditorValidationUndefined": "This value cannot be undefined.",
"propertyEditorValidationEmpty": "This value cannot be empty.",
"propertyEditorValidationUndefinedOrEmpty": "This value cannot be undefined or empty.",
"propertyEditorValidationInvalidJSON": "The entered JSON does not validate."
"conformingToType": "conforming to {0}",
"required": "required",
"example": "Example",
"copy": "Copy",
"delete": "Delete",
"copySelected": "Copy selected",
"deleteSelected": "Delete selected",
"paste": "Paste",
"browserNotSupported": "Browser not supported",
"sidebarHeadingParameters": "Parameters",
"sources": "Sources",
"processors": "Processors",
"sinks": "Sinks",
"saveButtonText": "Save",
"propertyEditorNoneValueLabel": "None",
"propertyEditorBooleanTrueLabel": "True",
"propertyEditorBooleanFalseLabel": "False",
"optionalDefaultValue": "Optional default value",
"connectedToNodes": "Connected to {0}",
"nodeNameWithPorts": "Node named {0} with {1}",
"nodeNamed": "Node named {0}",
"inputPortDescription": "Input port of {0}",
"outputPortDescription": "Output port of {0}",
"input": "input",
"output": "output",
"cancelButtonText": "Cancel",
"closeButtonText": "Close",
"sampleSelectorButtonText": "Try sample topologies",
"sampleSelectorTitle": "Select a Sample Topology",
"sampleSelectorText": "Jumpstart your topology creation with our built-in samples.",
"sampleSelectorDropdownLabel": "Topology Name",
"sampleSelectorDropdownPlaceholderText": "Select a sample topology",
"sampleSelectorLoadSampleButtonText": "Load sample",
"sampleSelectorOverwriteConfirmationTitle": "Existing Changes",
"sampleSelectorOverwriteConfirmationText": "Your existing changes will be lost if you load a sample. Do you want to overwrite or keep your changes?",
"sampleSelectorOverwriteConfirmButtonText": "Overwrite",
"sampleSelectorOverwriteKeepButtonText": "Keep",
"sampleSelectorDropdownEntryCvrAssetTitle": "Continuous video recording to an Azure Media Services Asset",
"sampleSelectorDropdownEntryCvrWithHttpExtensionTitle": "Continuous video recording and inferencing using HTTP Extension",
"sampleSelectorDropdownEntryCvrWithMotionTitle": "Continuous video recording with Motion Detection",
"sampleSelectorDropdownEntryEvrHttpExtensionAssetsTitle": "Event-based video recording to Assets based on events from external AI",
"sampleSelectorDropdownEntryEvrHubMessageAssetsTitle": "Event-based video recording to Assets based on specific objects being detected by external inference engine",
"sampleSelectorDropdownEntryEvrHubMessageFilesTitle": "Event-based recording of video to files based on messages sent via IoT Edge Hub",
"sampleSelectorDropdownEntryEvrMotionAssetsFilesTitle": "Event-based video recording to assets and local files based on motion events",
"sampleSelectorDropdownEntryEvrMotionAssetsTitle": "Event-based video recording to Assets based on motion events",
"sampleSelectorDropdownEntryEvrMotionFilesTitle": "Event-based video recording to local files based on motion events",
"sampleSelectorDropdownEntryHttpExtensionTitle": "Analyzing live video using HTTP Extension to send images to an external inference engine",
"sampleSelectorDropdownEntryMotionDetectionTitle": "Analyzing live video to detect motion and emit events",
"sampleSelectorDropdownEntryMotionWithHttpExtensionTitle": "Event-based video recording to Assets based on motion events, and using HTTP Extension to send images to an external inference engine",
"sidebarTopologyComponentTitle": "Topology Components",
"sidebarTopologyComponentText": "Select a source, processor, and sink to build a topology.",
"sidebarGraphTopologyNameLabel": "Topology name",
"sidebarGraphInstanceNameLabel": "Instance name",
"sidebarGraphNamePlaceholder": "Enter a name",
"sidebarGraphDescriptionLabel": "Description",
"sidebarGraphDescriptionPlaceholder": "Enter a description",
"ariaPortLabelConnectedToNodes": "Connected to {0}",
"ariaNodeLabelNodeNameWithPorts": "Node named {0} with {1}",
"ariaNodeLabelNodeDescription": "Node named {0}",
"ariaHasInputPortLabel": "input port",
"ariaHasOutputPortLabel": "output port",
"ariaHasBothPortsLabel": "input and output port",
"ariaInputPortDescription": "Input port of {0}",
"ariaOutputPortDescription": "Output port of {0}",
"propertyEditorCloseButtonAriaLabel": "Click to close the property editor.",
"propertyEditorInfoButtonTitle": "Show more information",
"propertyEditorInfoButtonAriaLabel": "Click to view more information about this property.",
"propertyEditorValidationUndefined": "This value cannot be undefined.",
"propertyEditorValidationEmpty": "This value cannot be empty.",
"propertyEditorValidationUndefinedOrEmpty": "This value cannot be undefined or empty.",
"propertyEditorValidationInvalidJSON": "The entered JSON does not validate."
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,7 +1,7 @@
String.prototype.format = function (...args) {
return this.replace(/{(\d+)}/g, function (match: string, number: number) {
return typeof args[number] != "undefined" ? args[number] : match;
});
return this.replace(/{(\d+)}/g, function (match: string, number: number) {
return typeof args[number] != "undefined" ? args[number] : match;
});
};
export {}; // All files must be modules, this makes it a module

Просмотреть файл

@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
import "@testing-library/jest-dom/extend-expect";

Просмотреть файл

@ -6,228 +6,201 @@ import NodeHelpers from "../../helpers/NodeHelpers";
import { MediaGraphNodeType, NodeDefinition } from "../../types/graphTypes";
export default class DefinitionGenerator {
private apiDefinition: any;
private outputFolder: string;
private version: string;
private definitions: any;
private apiDefinition: any;
private outputFolder: string;
private version: string;
private definitions: any;
private localizable: Record<string, string> = {};
private availableNodes: NodeDefinition[] = [];
private itemPanelNodes: any[] = [];
private usableNodes: Record<string, string[]> = {};
private localizable: Record<string, string> = {};
private availableNodes: NodeDefinition[] = [];
private itemPanelNodes: any[] = [];
private usableNodes: Record<string, string[]> = {};
private static readonly nodeTypeList = [
MediaGraphNodeType.Source,
MediaGraphNodeType.Processor,
MediaGraphNodeType.Sink,
];
private static readonly nodeTypeList = [MediaGraphNodeType.Source, MediaGraphNodeType.Processor, MediaGraphNodeType.Sink];
private static resolveFile(filePath: string) {
return path.join(__dirname, "/../../../src", filePath);
}
public constructor(version: string, outputFolder: string) {
this.apiDefinition = JSON.parse(
fs.readFileSync(
DefinitionGenerator.resolveFile(
`tools/definition-generator/v${version}/LiveVideoAnalytics.json`
),
"utf8"
)
);
this.usableNodes = JSON.parse(
fs.readFileSync(
DefinitionGenerator.resolveFile(
`tools/definition-generator/v${version}/usableNodes.json`
),
"utf8"
)
);
this.outputFolder = outputFolder;
this.definitions = this.apiDefinition["definitions"] as any;
this.version = version;
const detectedVersion = this.apiDefinition["info"]["version"] as string;
if (detectedVersion !== version) {
console.warn(
`Warning: file version ${detectedVersion} does not match expected version ${version}. Will continue to generate as ${version}.`
);
private static resolveFile(filePath: string) {
return path.join(__dirname, "/../../../src", filePath);
}
this.extractLocalizable();
this.recursivelyExpandAllNodes();
this.generateItemPanelNodeList();
public constructor(version: string, outputFolder: string) {
this.apiDefinition = JSON.parse(fs.readFileSync(DefinitionGenerator.resolveFile(`tools/definition-generator/v${version}/LiveVideoAnalytics.json`), "utf8"));
this.usableNodes = JSON.parse(fs.readFileSync(DefinitionGenerator.resolveFile(`tools/definition-generator/v${version}/usableNodes.json`), "utf8"));
this.writeFiles();
}
this.outputFolder = outputFolder;
this.definitions = this.apiDefinition["definitions"] as any;
this.version = version;
private extractLocalizable() {
// Extract all description fields
for (const nodeName in this.definitions) {
const node = this.definitions[nodeName];
if (node.description) {
const key = nodeName;
this.localizable[nodeName] = node.description;
node.description = key;
}
const detectedVersion = this.apiDefinition["info"]["version"] as string;
for (const propertyName in node.properties) {
const property = node.properties[propertyName];
if (property.description) {
const key = `${nodeName}.${propertyName}`;
this.localizable[`${nodeName}.${propertyName}`] =
property.description;
property.description = key;
if (detectedVersion !== version) {
console.warn(`Warning: file version ${detectedVersion} does not match expected version ${version}. Will continue to generate as ${version}.`);
}
if (property["x-ms-enum"]) {
for (const value of property["x-ms-enum"].values) {
if (value.description) {
const key = `${nodeName}.${propertyName}.${value.value}`;
this.localizable[key] = value.description;
value.description = key;
this.extractLocalizable();
this.recursivelyExpandAllNodes();
this.generateItemPanelNodeList();
this.writeFiles();
}
private extractLocalizable() {
// Extract all description fields
for (const nodeName in this.definitions) {
const node = this.definitions[nodeName];
if (node.description) {
const key = nodeName;
this.localizable[nodeName] = node.description;
node.description = key;
}
}
}
}
}
}
private recursivelyExpandAllNodes() {
// expand all nodes
for (const name in this.definitions) {
const definition = (this.definitions as Record<string, any>)[name];
this.availableNodes.push({
name,
nodeType: this.getNodeType(definition),
...this.expand(definition),
});
}
}
for (const propertyName in node.properties) {
const property = node.properties[propertyName];
if (property.description) {
const key = `${nodeName}.${propertyName}`;
this.localizable[`${nodeName}.${propertyName}`] = property.description;
property.description = key;
}
private generateItemPanelNodeList() {
// generate nodes shown in the drag-and-droppable item panel on the left
this.itemPanelNodes = DefinitionGenerator.nodeTypeList.map((nodeType) => ({
title: NodeHelpers.getNodeTypeKey(nodeType),
id: NodeHelpers.getNodeTypeKey(nodeType),
searchKeys: [NodeHelpers.getNodeTypeKey(nodeType)],
children: this.availableNodes
.filter((node) => node.nodeType === nodeType)
.map((node) => {
return {
id: uuid(),
name: Helpers.lowercaseFirstCharacter(node.name),
shape: "module",
ports: NodeHelpers.getPorts(node),
data: {
...NodeHelpers.getNodeAppearance(node.nodeType),
nodeProperties: {
"@type": node["x-ms-discriminator-value"],
name: node.name,
},
nodeType: node.nodeType,
},
};
})
.map((node) => ({
title: node.name,
extra: node,
id: uuid(),
searchKeys: [node.name],
children: [],
})),
expanded: true,
}));
}
public writeFiles() {
// write to files in appropriate versioned folder
const base = DefinitionGenerator.resolveFile(
`${this.outputFolder}/v` + this.version
);
if (!fs.existsSync(base)) {
fs.mkdirSync(base);
}
fs.writeFileSync(
base + "/nodes.json",
JSON.stringify(
{
availableNodes: this.availableNodes,
itemPanelNodes: this.itemPanelNodes,
},
null,
2
),
"utf8"
);
fs.writeFileSync(
base + "/i18n.en.json",
JSON.stringify(this.localizable, null, 2),
"utf8"
);
}
// returns the MediaGraphNodeType given a node definition
private getNodeType(definition: any): MediaGraphNodeType {
const discriminatorValue = definition["x-ms-discriminator-value"];
if (discriminatorValue) {
for (const type of DefinitionGenerator.nodeTypeList) {
const key = NodeHelpers.getNodeTypeKey(type);
if (this.usableNodes[key].includes(discriminatorValue)) {
return type;
}
}
}
return MediaGraphNodeType.Other;
}
// inline all inherited properties
private expand(object: Record<string, any>): any {
if (typeof object === "object") {
for (const key in object) {
// include all attributes of referenced object
if (key === "allOf") {
object.parsedAllOf = [];
for (const reference of object["allOf"]) {
if (reference["$ref"]) {
object = {
...this.resolvePathReference(reference["$ref"]),
...object,
};
object.parsedAllOf.push(reference["$ref"]);
if (property["x-ms-enum"]) {
for (const value of property["x-ms-enum"].values) {
if (value.description) {
const key = `${nodeName}.${propertyName}.${value.value}`;
this.localizable[key] = value.description;
value.description = key;
}
}
}
}
}
}
// add all referenced attributes to current node
if (key === "$ref") {
object = {
parsedRef: object["$ref"],
...this.resolvePathReference(object["$ref"]),
...object,
};
}
object[key] = this.expand(object[key]);
}
delete object["$ref"];
delete object.allOf;
}
return object;
}
// find matching entry in definition file
private resolvePathReference(ref: string): any {
ref = ref.replace("#/definitions/", "");
let current: Record<string, any> = this.definitions;
for (const piece of ref.split("/")) {
current = current[piece];
private recursivelyExpandAllNodes() {
// expand all nodes
for (const name in this.definitions) {
const definition = (this.definitions as Record<string, any>)[name];
this.availableNodes.push({
name,
nodeType: this.getNodeType(definition),
...this.expand(definition)
});
}
}
private generateItemPanelNodeList() {
// generate nodes shown in the drag-and-droppable item panel on the left
this.itemPanelNodes = DefinitionGenerator.nodeTypeList.map((nodeType) => ({
title: NodeHelpers.getNodeTypeKey(nodeType),
id: NodeHelpers.getNodeTypeKey(nodeType),
searchKeys: [NodeHelpers.getNodeTypeKey(nodeType)],
children: this.availableNodes
.filter((node) => node.nodeType === nodeType)
.map((node) => {
return {
id: uuid(),
name: Helpers.lowercaseFirstCharacter(node.name),
shape: "module",
ports: NodeHelpers.getPorts(node),
data: {
...NodeHelpers.getNodeAppearance(node.nodeType),
nodeProperties: {
"@type": node["x-ms-discriminator-value"],
name: node.name
},
nodeType: node.nodeType
}
};
})
.map((node) => ({
title: node.name,
extra: node,
id: uuid(),
searchKeys: [node.name],
children: []
})),
expanded: true
}));
}
public writeFiles() {
// write to files in appropriate versioned folder
const base = DefinitionGenerator.resolveFile(`${this.outputFolder}/v` + this.version);
if (!fs.existsSync(base)) {
fs.mkdirSync(base);
}
fs.writeFileSync(
base + "/nodes.json",
JSON.stringify(
{
availableNodes: this.availableNodes,
itemPanelNodes: this.itemPanelNodes
},
null,
2
),
"utf8"
);
fs.writeFileSync(base + "/i18n.en.json", JSON.stringify(this.localizable, null, 2), "utf8");
}
// returns the MediaGraphNodeType given a node definition
private getNodeType(definition: any): MediaGraphNodeType {
const discriminatorValue = definition["x-ms-discriminator-value"];
if (discriminatorValue) {
for (const type of DefinitionGenerator.nodeTypeList) {
const key = NodeHelpers.getNodeTypeKey(type);
if (this.usableNodes[key].includes(discriminatorValue)) {
return type;
}
}
}
return MediaGraphNodeType.Other;
}
// inline all inherited properties
private expand(object: Record<string, any>): any {
if (typeof object === "object") {
for (const key in object) {
// include all attributes of referenced object
if (key === "allOf") {
object.parsedAllOf = [];
for (const reference of object["allOf"]) {
if (reference["$ref"]) {
object = {
...this.resolvePathReference(reference["$ref"]),
...object
};
object.parsedAllOf.push(reference["$ref"]);
}
}
}
// add all referenced attributes to current node
if (key === "$ref") {
object = {
parsedRef: object["$ref"],
...this.resolvePathReference(object["$ref"]),
...object
};
}
object[key] = this.expand(object[key]);
}
delete object["$ref"];
delete object.allOf;
}
return object;
}
// find matching entry in definition file
private resolvePathReference(ref: string): any {
ref = ref.replace("#/definitions/", "");
let current: Record<string, any> = this.definitions;
for (const piece of ref.split("/")) {
current = current[piece];
}
return this.expand(current);
}
return this.expand(current);
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,17 +1,10 @@
{
"sources": [
"#Microsoft.Media.MediaGraphRtspSource",
"#Microsoft.Media.MediaGraphIoTHubMessageSource"
],
"processors": [
"#Microsoft.Media.MediaGraphMotionDetectionProcessor",
"#Microsoft.Media.MediaGraphHttpExtension",
"#Microsoft.Media.MediaGraphSignalGateProcessor",
"#Microsoft.Media.MediaGraphFrameRateFilterProcessor"
],
"sinks": [
"#Microsoft.Media.MediaGraphIoTHubMessageSink",
"#Microsoft.Media.MediaGraphFileSink",
"#Microsoft.Media.MediaGraphAssetSink"
]
"sources": ["#Microsoft.Media.MediaGraphRtspSource", "#Microsoft.Media.MediaGraphIoTHubMessageSource"],
"processors": [
"#Microsoft.Media.MediaGraphMotionDetectionProcessor",
"#Microsoft.Media.MediaGraphHttpExtension",
"#Microsoft.Media.MediaGraphSignalGateProcessor",
"#Microsoft.Media.MediaGraphFrameRateFilterProcessor"
],
"sinks": ["#Microsoft.Media.MediaGraphIoTHubMessageSink", "#Microsoft.Media.MediaGraphFileSink", "#Microsoft.Media.MediaGraphAssetSink"]
}

Просмотреть файл

@ -1,50 +1,50 @@
import { ICanvasEdge, ICanvasNode } from "@vienna/react-dag-editor";
import {
MediaGraphTopology,
MediaGraphNodeInput,
MediaGraphNodeInput,
MediaGraphTopology
} from "../lva-sdk/lvaSDKtypes";
export enum MediaGraphNodeType {
Source = "source",
Processor = "processor",
Sink = "sink",
Other = "other",
Source = "source",
Processor = "processor",
Sink = "sink",
Other = "other"
}
export interface GraphInfo {
meta: MediaGraphTopology;
nodes: ICanvasNode[];
edges: ICanvasEdge[];
meta: MediaGraphTopology;
nodes: ICanvasNode[];
edges: ICanvasEdge[];
}
export interface NodeDefinitionProperty {
type?: string;
parsedRef?: string;
format?: string;
properties?: Record<string, NodeDefinitionProperty | undefined>;
description?: string;
required?: string[];
enum?: string[];
example?: string;
"x-ms-discriminator-value"?: string;
type?: string;
parsedRef?: string;
format?: string;
properties?: Record<string, NodeDefinitionProperty | undefined>;
description?: string;
required?: string[];
enum?: string[];
example?: string;
"x-ms-discriminator-value"?: string;
}
export interface NodeDefinition extends NodeDefinitionProperty {
name: string;
nodeType: MediaGraphNodeType;
parsedAllOf?: string[];
name: string;
nodeType: MediaGraphNodeType;
parsedAllOf?: string[];
}
export interface CanvasNodeProperties {
"@type": string;
inputs?: MediaGraphNodeInput[];
name: string;
"@type": string;
inputs?: MediaGraphNodeInput[];
name: string;
}
export interface CanvasNodeData {
color: string;
colorAlt: string;
iconName: string;
nodeProperties: CanvasNodeProperties | Record<string, any>;
nodeType: MediaGraphNodeType;
color: string;
colorAlt: string;
iconName: string;
nodeProperties: CanvasNodeProperties | Record<string, any>;
nodeType: MediaGraphNodeType;
}

2
src/types/string.d.ts поставляемый
Просмотреть файл

@ -1,3 +1,3 @@
interface String {
format(...any): string;
format(...any): string;
}

Просмотреть файл

@ -4,15 +4,15 @@ import { GraphInfo } from "./graphTypes";
export type VSCodeSetState = (state: VSCodeState) => void;
export interface VSCodeDelegate {
setState: VSCodeSetState;
setState: VSCodeSetState;
}
export interface VSCodeState {
graphData?: GraphInfo;
zoomPanSettings?: IZoomPanSettings;
graphData?: GraphInfo;
zoomPanSettings?: IZoomPanSettings;
}
export interface InitializationParameters {
state: VSCodeState;
vsCodeSetState: VSCodeSetState;
state: VSCodeState;
vsCodeSetState: VSCodeSetState;
}

Просмотреть файл

@ -1,17 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"lib": ["ES2019"],
"outDir": "definition-generator",
"sourceMap": false,
"strict": true,
"rootDir": "src",
"moduleResolution": "node",
"resolveJsonModule": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true
},
"files": ["src/tools/definition-generator/entry.ts"]
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"lib": ["ES2019"],
"outDir": "definition-generator",
"sourceMap": false,
"strict": true,
"rootDir": "src",
"moduleResolution": "node",
"resolveJsonModule": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true
},
"files": ["src/tools/definition-generator/entry.ts"]
}

Просмотреть файл

@ -1,12 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"lib": ["ES2019"],
"outDir": "out",
"sourceMap": true,
"strict": true,
"rootDir": "ext"
},
"exclude": ["node_modules", "src", "definitions", ".vscode-test"]
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"lib": ["ES2019"],
"outDir": "out",
"sourceMap": true,
"strict": true,
"rootDir": "ext"
},
"exclude": ["node_modules", "src", "definitions", ".vscode-test"]
}

Просмотреть файл

@ -1,23 +1,23 @@
{
"compilerOptions": {
"module": "esnext",
"target": "es2019",
"lib": ["ES2019", "DOM"],
"outDir": "out",
"sourceMap": true,
"strict": true,
"rootDir": "src",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react",
"noEmit": true
},
"exclude": ["node_modules", ".vscode-test"],
"include": ["src"]
"compilerOptions": {
"module": "esnext",
"target": "es2019",
"lib": ["ES2019", "DOM"],
"outDir": "out",
"sourceMap": true,
"strict": true,
"rootDir": "src",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react",
"noEmit": true
},
"exclude": ["node_modules", ".vscode-test"],
"include": ["src"]
}