diff --git a/modules/ui/components/Toaster.jsx b/modules/ui/components/Toaster.jsx new file mode 100644 index 00000000..0fa6e2ed --- /dev/null +++ b/modules/ui/components/Toaster.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + + + +export function Toaster({}) { + + return
+ +
; + +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 46ec9226..875d39b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1181,6 +1181,21 @@ "integrity": "sha512-A8ia2Wus0OAP6hh28ZgPSCBJEX3Jnql3kg9di/I+Lmg1gbJXgDZBrHr/UGZXl20Vi1lXgMuUq8c8J899KFr5gA==", "dev": true }, + "@babel/runtime": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", + "requires": { + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" + } + } + }, "@babel/template": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", @@ -1287,14 +1302,6 @@ } } }, - "@dagrejs/graphlib": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.1.4.tgz", - "integrity": "sha512-QCg9sL4uhjn468FDEsb/S9hS2xUZSrv/+dApb1Ze5VKO96pTXKNJZ6MGhIpgWkc1TVhbVGH9/7rq/Mf8/jWicw==", - "requires": { - "lodash": "^4.11.1" - } - }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -3107,6 +3114,11 @@ } } }, + "csstype": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", + "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -3405,6 +3417,15 @@ "esutils": "^2.0.2" } }, + "dom-helpers": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz", + "integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==", + "requires": { + "@babel/runtime": "^7.6.3", + "csstype": "^2.6.7" + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -6386,7 +6407,8 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true }, "lodash.camelcase": { "version": "4.3.0", @@ -8337,6 +8359,73 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" }, + "react-toastify": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.5.0.tgz", + "integrity": "sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ==", + "requires": { + "@babel/runtime": "^7.4.2", + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-transition-group": "^4" + }, + "dependencies": { + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-transition-group": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", + "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", diff --git a/package.json b/package.json index 3c5b4892..acf0a264 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "prop-types": "15.6.0", "react": "^16.8.6", "react-dom": "^16.8.6", + "react-toastify": "^5.5.0", "sprintf": "0.1.5", "three": "0.89.0" } diff --git a/web/app/sketcher/actions/constraintActions.js b/web/app/sketcher/actions/constraintActions.js index 0dca9d31..30f43c75 100644 --- a/web/app/sketcher/actions/constraintActions.js +++ b/web/app/sketcher/actions/constraintActions.js @@ -2,8 +2,7 @@ import {AlgNumConstraint, ConstraintDefinitions} from "../constr/ANConstraints"; import {EndPoint} from "../shapes/point"; import {Circle} from "../shapes/circle"; import {Segment} from "../shapes/segment"; -import {isInstanceOf, matchAll, matchTypes, sortSelectionByType} from "./matchUtils"; -import constraints from "../../../test/cases/constraints"; +import {isInstanceOf, matchAll, matchTypes} from "./matchUtils"; import {Arc} from "../shapes/arc"; import {FilletTool} from "../tools/fillet"; import {editConstraint as _editConstraint} from "../components/ConstraintEditor"; @@ -13,13 +12,17 @@ export default [ { id: 'Coincident', - shortName: 'Coincident', + shortName: 'Coincident Constraint', description: 'Point Coincident', - selectionMatcher: (selection, sortedByType) => matchAll(selection, EndPoint, 2), + selectionMatcher: { + selector: 'matchAll', + types: [EndPoint], + minQuantity: 2 + }, - invoke: ctx => { + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [first, ...others] = viewer.selected; + const [first, ...others] = matchedObjects; let pm = viewer.parametricManager; for (let obj of others) { pm._add( @@ -32,17 +35,26 @@ export default [ { id: 'Tangent', - shortName: 'Tangent', + shortName: 'Tangent Constraint', description: 'Tangent Between Line And Circle', - selectionMatcher: [ - (selection, sortedByType) => matchTypes(sortedByType, Circle, 1, Segment, 1), - (selection, sortedByType) => matchTypes(sortedByType, Arc, 1, Segment, 1), - ], + selectionMatcher: { + selector: 'matchSequence', + sequence: [ + { + types: [Circle, Arc], + quantity: 1 + }, + { + types: [Segment], + quantity: 1 + }, + ] + }, - invoke: ctx => { + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [circle, line] = sortSelectionByType(viewer.selected); + const [circle, line] = matchedObjects; const constraint = new AlgNumConstraint(ConstraintDefinitions.TangentLC, [line, circle]); constraint.initConstants(); @@ -54,24 +66,21 @@ export default [ { id: 'EqualRadius', - shortName: 'Equal Radius', + shortName: 'Equal Radius Constraint', description: 'Equal Radius Between Two Circle', - selectionMatcher: selection => { - for (let obj of selection) { - if (!(isInstanceOf(obj, Circle) || isInstanceOf(obj, Arc))) { - return false; - } - } - return true; + selectionMatcher: { + selector: 'matchAll', + types: [Circle, Arc], + minQuantity: 2 }, - invoke: ctx => { + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; const pm = viewer.parametricManager; - for (let i = 1; i < viewer.selected.length; ++i) { - pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualRadius, [viewer.selected[i-1], viewer.selected[i]])); + for (let i = 1; i < matchedObjects.length; ++i) { + pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualRadius, [matchedObjects[i-1], matchedObjects[i]])); } pm.commit(); } @@ -80,15 +89,18 @@ export default [ { id: 'EqualLength', - shortName: 'Equal Length', + shortName: 'Equal Length Constraint', description: 'Equal Length Between Two Segments', - selectionMatcher: selection => matchAll(selection, Segment, 2), - - invoke: ctx => { + selectionMatcher: { + selector: 'matchAll', + types: [Segment], + minQuantity: 2 + }, + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; const pm = viewer.parametricManager; - for (let i = 1; i < viewer.selected.length; ++i) { - pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualLength, [viewer.selected[i-1], viewer.selected[i]])); + for (let i = 1; i < matchedObjects.length; ++i) { + pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualLength, [matchedObjects[i-1], matchedObjects[i]])); } pm.commit(); } @@ -97,13 +109,25 @@ export default [ { id: 'PointOnLine', - shortName: 'Point On Line', + shortName: 'Point On Line Constraint', description: 'Point On Line', - selectionMatcher: (selection, sortedByType) => matchTypes(sortedByType, EndPoint, 1, Segment, 1), + selectionMatcher: { + selector: 'matchSequence', + sequence: [ + { + types: [EndPoint], + quantity: 1 + }, + { + types: [Segment], + quantity: 1 + }, + ] + }, - invoke: ctx => { + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [pt, line] = sortSelectionByType(viewer.selected); + const [pt, line] = matchedObjects; let pm = viewer.parametricManager; pm.add(new AlgNumConstraint(ConstraintDefinitions.PointOnLine, [pt, line])); } @@ -111,14 +135,17 @@ export default [ { id: 'Angle', - shortName: 'Angle', + shortName: 'Angle Constraint', description: 'Angle', - selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 1), - - invoke: ctx => { + selectionMatcher: { + selector: 'matchAll', + types: [Segment], + minQuantity: 1 + }, + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const firstSegment = viewer.selected[0]; + const firstSegment = matchedObjects[0]; const firstConstr = new AlgNumConstraint(ConstraintDefinitions.Angle, [firstSegment]); firstConstr.initConstants(); @@ -126,8 +153,8 @@ export default [ editConstraint(ctx, firstConstr, () => { const pm = viewer.parametricManager; pm._add(firstConstr); - for (let i = 1; i < viewer.selected.length; ++i) { - pm._add(new AlgNumConstraint(ConstraintDefinitions.Angle, [viewer.selected[i]], {...firstConstr.constants})); + for (let i = 1; i < matchedObjects.length; ++i) { + pm._add(new AlgNumConstraint(ConstraintDefinitions.Angle, [matchedObjects[i]], {...firstConstr.constants})); } pm.commit(); }); @@ -136,15 +163,20 @@ export default [ { id: 'Vertical', - shortName: 'Vertical', + shortName: 'Vertical Constraint', description: 'Vertical', - selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 1), - invoke: ctx => { + selectionMatcher: { + selector: 'matchAll', + types: [Segment], + minQuantity: 1 + }, + + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; const pm = viewer.parametricManager; - viewer.selected.forEach(obj => { + matchedObjects.forEach(obj => { const constr = new AlgNumConstraint(ConstraintDefinitions.Vertical, [obj]); constr.initConstants(); pm._add(constr); @@ -155,15 +187,20 @@ export default [ { id: 'Horizontal', - shortName: 'Horizontal', + shortName: 'Horizontal Constraint', description: 'Horizontal', - selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 1), - invoke: ctx => { + selectionMatcher: { + selector: 'matchAll', + types: [Segment], + minQuantity: 1 + }, + + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; const pm = viewer.parametricManager; - viewer.selected.forEach(obj => { + matchedObjects.forEach(obj => { const constr = new AlgNumConstraint(ConstraintDefinitions.Horizontal, [obj]); constr.initConstants(); pm._add(constr); @@ -174,14 +211,19 @@ export default [ { id: 'AngleBetween', - shortName: 'Angle Between', + shortName: 'Angle Between Constraint', description: 'Angle Between Lines', - selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 2), - invoke: ctx => { + selectionMatcher: { + selector: 'matchAll', + types: [Segment], + minQuantity: 2 + }, + + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [firstSegment, secondSegment] = viewer.selected; + const [firstSegment, secondSegment] = matchedObjects; const firstConstr = new AlgNumConstraint(ConstraintDefinitions.AngleBetween, [firstSegment, secondSegment]); firstConstr.initConstants(); @@ -189,9 +231,9 @@ export default [ editConstraint(ctx, firstConstr, () => { const pm = viewer.parametricManager; pm._add(firstConstr); - for (let i = 2; i < viewer.selected.length; ++i) { + for (let i = 2; i < matchedObjects.length; ++i) { pm._add(new AlgNumConstraint(ConstraintDefinitions.Angle, - [viewer.selected[i-1], viewer.selected[i]], {...firstConstr.constants})); + [matchedObjects[i-1], matchedObjects[i]], {...firstConstr.constants})); } pm.commit(); }); @@ -200,17 +242,22 @@ export default [ { id: 'Perpendicular', - shortName: 'Perpendicular', + shortName: 'Perpendicular Constraint', description: 'Perpendicularity between two or more lines', - selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 2), - invoke: ctx => { + selectionMatcher: { + selector: 'matchAll', + types: [Segment], + minQuantity: 2 + }, + + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; const pm = viewer.parametricManager; - for (let i = 1; i < viewer.selected.length; ++i) { - const constr = new AlgNumConstraint(ConstraintDefinitions.Perpendicular, [viewer.selected[i-1], viewer.selected[i]]); + for (let i = 1; i < matchedObjects.length; ++i) { + const constr = new AlgNumConstraint(ConstraintDefinitions.Perpendicular, [matchedObjects[i-1], matchedObjects[i]]); constr.initConstants(); pm._add(constr); } @@ -220,17 +267,22 @@ export default [ { id: 'Parallel', - shortName: 'Parallel', + shortName: 'Parallel Constraint', description: 'Parallelism between two or more lines', - selectionMatcher: (selection, sortedByType) => matchAll(sortedByType, Segment, 2), - invoke: ctx => { + selectionMatcher: { + selector: 'matchAll', + types: [Segment], + minQuantity: 2 + }, + + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; const pm = viewer.parametricManager; - for (let i = 1; i < viewer.selected.length; ++i) { - const constr = new AlgNumConstraint(ConstraintDefinitions.Parallel, [viewer.selected[i-1], viewer.selected[i]]); + for (let i = 1; i < matchedObjects.length; ++i) { + const constr = new AlgNumConstraint(ConstraintDefinitions.Parallel, [matchedObjects[i-1], matchedObjects[i]]); constr.initConstants(); pm._add(constr); } @@ -240,14 +292,19 @@ export default [ { id: 'Length', - shortName: 'Length', + shortName: 'Length Constraint', description: 'Segment Length', - selectionMatcher: (selection) => matchAll(selection, Segment, 1), - invoke: ctx => { + selectionMatcher: { + selector: 'matchAll', + types: [Segment], + minQuantity: 1 + }, + + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [firstSegment, ...others] = viewer.selected; + const [firstSegment, ...others] = matchedObjects; const firstConstr = new AlgNumConstraint(ConstraintDefinitions.SegmentLength, [firstSegment]); firstConstr.initConstants(); @@ -265,21 +322,19 @@ export default [ { id: 'RadiusLength', - shortName: 'RadiusLength', + shortName: 'Radius Length Constraint', description: 'Radius Length', - selectionMatcher: (selection) => { - for (let obj of selection) { - if (!(isInstanceOf(obj, Circle) || isInstanceOf(obj, Arc))) { - return false; - } - } - return true; + + selectionMatcher: { + selector: 'matchAll', + types: [Circle, Arc], + minQuantity: 1 }, - invoke: ctx => { + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [firstCircle, ...others] = viewer.selected; + const [firstCircle, ...others] = matchedObjects; const firstConstr = new AlgNumConstraint(ConstraintDefinitions.RadiusLength, [firstCircle]); firstConstr.initConstants(); @@ -297,14 +352,28 @@ export default [ { id: 'DistancePL', - shortName: 'Point to Line Distance', + shortName: 'Point to Line Distance Constraint', description: 'Distance between Point and Line', - selectionMatcher: (selection, sortedByType) => matchTypes(sortedByType, EndPoint, 1, Segment, 1), - invoke: ctx => { + selectionMatcher: { + selector: 'matchSequence', + sequence: [ + { + types: [EndPoint], + quantity: 1 + }, + { + types: [Segment], + quantity: 1 + }, + ] + }, + + + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [pt, seg] = sortSelectionByType(viewer.selected); + const [pt, seg] = matchedObjects; const constr = new AlgNumConstraint(ConstraintDefinitions.DistancePL, [pt, seg]); constr.initConstants(); @@ -318,14 +387,23 @@ export default [ { id: 'DistancePP', - shortName: 'Two Point Distance', + shortName: 'Two Point Distance Constraint', description: 'Distance between two Points', - selectionMatcher: (selection, sortedByType) => matchTypes(sortedByType, EndPoint, 2), - invoke: ctx => { + selectionMatcher: { + selector: 'matchSequence', + sequence: [ + { + types: [EndPoint], + quantity: 2 + } + ] + }, + + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [p1, p2] = sortSelectionByType(viewer.selected); + const [p1, p2] = matchedObjects; const constr = new AlgNumConstraint(ConstraintDefinitions.DistancePP, [p1, p2]); constr.initConstants(); @@ -339,14 +417,22 @@ export default [ { id: 'Lock', - shortName: 'Lock', + shortName: 'Lock Point Constraint', description: 'Lock Point', - selectionMatcher: (selection) => matchTypes(selection, EndPoint, 1), + selectionMatcher: { + selector: 'matchSequence', + sequence: [ + { + types: [EndPoint], + quantity: 1 + } + ] + }, - invoke: ctx => { + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - const [point] = viewer.selected; + const [point] = matchedObjects; const constr = new AlgNumConstraint(ConstraintDefinitions.LockPoint, [point]); constr.initConstants(); @@ -356,32 +442,33 @@ export default [ { id: 'Fillet', - shortName: 'Fillet', + shortName: 'Fillet Tool', description: 'Add a Fillet', - selectionMatcher: (selection) => { - if (matchTypes(selection, EndPoint, 1)) { - const [point] = selection; - if (isInstanceOf(point.parent, Segment)) { - let pair = null; - point.visitLinked(l => { - if (l !== point && isInstanceOf(l.parent, Segment)) { - pair = l; + selectionMatcher: { + selector: 'function', + match: (selection) => { + if (matchTypes(selection, EndPoint, 1)) { + const [point] = selection; + if (isInstanceOf(point.parent, Segment)) { + let pair = null; + point.visitLinked(l => { + if (l !== point && isInstanceOf(l.parent, Segment)) { + pair = l; + return true; + } + }); + if (pair) { return true; } - }); - if (pair) { - return true; } } - } - return false; + return false; + }, + description: 'a point linking two segment' }, - invoke: ctx => { + invoke: (ctx, matchedObjects) => { const {viewer} = ctx; - - - const filletTool = new FilletTool(ctx.viewer); const cands = filletTool.getCandidateFromSelection(viewer.selected); if (cands) { diff --git a/web/app/sketcher/actions/index.js b/web/app/sketcher/actions/index.js index d583d00d..0bdb33bb 100644 --- a/web/app/sketcher/actions/index.js +++ b/web/app/sketcher/actions/index.js @@ -1,42 +1,44 @@ import constraintActions from "./constraintActions"; -import {sortSelectionByType} from "./matchUtils"; +import {getDescription, MatchIndex, matchSelection} from "../selectionMatcher"; +import {toast} from "react-toastify"; const ALL_CONTEXTUAL_ACTIONS = [ ...constraintActions, //keep going here ]; +const index = {}; +ALL_CONTEXTUAL_ACTIONS.forEach(a => index[a.id] = a); +Object.freeze(index); + export function matchAvailableActions(selection) { - let sortedByType = sortSelectionByType(selection); let matched = []; - + let matchIndex = new MatchIndex(selection); if (selection.length) { for (let action of ALL_CONTEXTUAL_ACTIONS) { - - if (Array.isArray(action.selectionMatcher)) { - action.selectionMatcher.forEach(matcher => { - if (matcher(selection, sortedByType)) { - matched.push(action); - } - }) - } else { - if (action.selectionMatcher(selection, sortedByType)) { - matched.push(action); - } + if (matchSelection(action.selectionMatcher, matchIndex, true)) { + matched.push(action); } - } } return matched; - } //For backward compatibility -export function getActionIfAvailable(actionId, selection, cb) { +export function runActionOrToastWhyNot(actionId, selection, ctx) { + const action = index[actionId]; + if (action) { + const matched = matchSelection(action.selectionMatcher, new MatchIndex(selection), false); + if (matched) { + action.invoke(ctx, matched) + } else { + toast('The action "' + action.shortName + '" requires selection of ' + getDescription(action.selectionMatcher)); + } + } matchAvailableActions(selection).forEach(a => { if (a.id === actionId) { cb(a); diff --git a/web/app/sketcher/components/ContextualControls.jsx b/web/app/sketcher/components/ContextualControls.jsx index e3f5d074..132c0d3f 100644 --- a/web/app/sketcher/components/ContextualControls.jsx +++ b/web/app/sketcher/components/ContextualControls.jsx @@ -3,6 +3,7 @@ import ls from './ContextualControls.less'; import {matchAvailableActions} from "../actions"; import {useStream} from "../../../../modules/ui/effects"; import {SketcherAppContext} from "./SketcherApp"; +import {MatchIndex, matchSelection} from "../selectionMatcher"; export function ContextualControls() { @@ -25,7 +26,7 @@ export function ContextualControls() {
AVAILABLE ACTIONS:
{ - availableActions.map(a => ) } diff --git a/web/app/sketcher/components/SketcherApp.jsx b/web/app/sketcher/components/SketcherApp.jsx index 4176543c..e2f7351f 100644 --- a/web/app/sketcher/components/SketcherApp.jsx +++ b/web/app/sketcher/components/SketcherApp.jsx @@ -4,12 +4,15 @@ import {ConstraintEditor} from './ConstraintEditor'; import {ContextualControls} from './ContextualControls'; import {ConstraintList} from './ConstraintExplorer'; import {StreamsContext} from 'ui/streamsContext'; +import {ToastContainer} from "react-toastify"; +import 'react-toastify/dist/ReactToastify.css'; export const SketcherAppContext = React.createContext({}); export function SketcherApp({applicationContext}) { return + {ReactDOM.createPortal( , diff --git a/web/app/sketcher/selectionMatcher.js b/web/app/sketcher/selectionMatcher.js index f969228e..e3995c92 100644 --- a/web/app/sketcher/selectionMatcher.js +++ b/web/app/sketcher/selectionMatcher.js @@ -1,26 +1,126 @@ -export function SelectionMatcher() { +export function matchSelection(definition, matchIndex, fast) { + const selection = matchIndex.selection; + if (definition.selector === 'function') { + return definition.match(selection, fast) + } else if (definition.selector === 'matchAll') { + const {minQuantity: min, types} = definition; + if (min !== undefined && selection.length < min) { + return false; + } + for (let obj of selection) { - const lowerBounds = []; - - return { - - - moreThan(amount, type) { - lowerBounds.push(amount, type); - return this; - }, - - - match: selection => { - - - for (let [amount, type] of lowerBounds) { - + let hit = false; + for (let constructor of types) { + if (constructor.prototype._class === obj._class) { + hit = true; + break; + } + } + if (!hit) { + return false; } } + return fast ? true : selection; + + } else if (definition.selector === 'matchSequence') { + + matchIndex.reset(fast); + + + for (let item of definition.sequence) { + if (!matchIndex.mark(item.types, item.quantity)) { + return false; + } + } + + return matchIndex.allHit() ? (fast ? true : matchIndex.result) : false; + + } else { + throw 'unsupported' } +} +export function getDescription(definition) { + + if (definition.selector === 'function') { + return definition.description; + } else if (definition.selector === 'matchAll') { + return `at least ${definition.minQuantity} of ${stringifyTypes(definition.types, definition.minQuantity)}`; + } else if (definition.selector === 'matchSequence') { + + let out = ''; + + + for (let i = 0; i< definition.sequence.length; ++i) { + const item = definition.sequence[i]; + if (i !== 0) { + out += i === definition.sequence.length - 1 ? ' and ': ', '; + } + + out += item.quantity + ' ' + stringifyTypes(item.types, item.quantity); + } + + return out; + + } else { + throw 'unsupported' + } +} + +function stringifyTypes(types, minQuantity) { + return types.map(t => t.prototype._class.replace('TCAD.TWO.', '') + (minQuantity > 1 ? 's' : '')).join(' or '); +} + +export class MatchIndex { + + typeMap = new Map(); + + overallHits = 0; + + constructor(selection) { + this.selection = selection; + selection.forEach(obj => { + let info = this.typeMap.get(obj._class); + if (!info) { + info = { + hits: 0, + objects: [] + }; + this.typeMap.set(obj._class, info); + } + info.objects.push(obj); + }) + } + + reset(fast) { + this.overallHits = 0; + this.typeMap.forEach(i => i.hits = 0); + this.result = fast ? null : []; + } + + mark(types, quantity) { + for (let type of types) { + const info = this.typeMap.get(type.prototype._class); + if (!info) { + return false; + } + const toAdd = Math.min(quantity, info.objects.length - info.hits); + if (this.result) { + for (let i = 0; i < toAdd; ++i) { + this.result.push(info.objects[info.hits + i]); + } + } + quantity -= toAdd; + info.hits += toAdd; + this.overallHits += toAdd; + } + return quantity === 0; + } + + allHit() { + return this.selection.length === this.overallHits; + } } \ No newline at end of file diff --git a/web/app/sketcher/shapes/segment.js b/web/app/sketcher/shapes/segment.js index 834a6e60..6d0f6f3b 100644 --- a/web/app/sketcher/shapes/segment.js +++ b/web/app/sketcher/shapes/segment.js @@ -127,18 +127,18 @@ export class Segment extends SketchObject { drawImpl(ctx, scale) { - let ang = this.params.ang.get(); - let nx = -Math.sin(ang); - let ny = Math.cos(ang); - let w = this.w; - - ctx.save(); - draw_utils.SetStyle(Styles.CONSTRUCTION_OF_OBJECT, ctx, scale ); - ctx.beginPath(); - ctx.moveTo(nx * w + ny * 1000, ny * w - nx * 1000); - ctx.lineTo(nx * w - ny * 1000, ny * w + nx * 1000); - ctx.stroke(); - ctx.restore(); + // let ang = this.params.ang.get(); + // let nx = -Math.sin(ang); + // let ny = Math.cos(ang); + // let w = this.w; + // + // ctx.save(); + // draw_utils.SetStyle(Styles.CONSTRUCTION_OF_OBJECT, ctx, scale ); + // ctx.beginPath(); + // ctx.moveTo(nx * w + ny * 1000, ny * w - nx * 1000); + // ctx.lineTo(nx * w - ny * 1000, ny * w + nx * 1000); + // ctx.stroke(); + // ctx.restore(); ctx.beginPath(); ctx.moveTo(this.a.x, this.a.y); diff --git a/web/app/sketcher/sketcher-app.js b/web/app/sketcher/sketcher-app.js index e9ceb3dc..1aea40cd 100644 --- a/web/app/sketcher/sketcher-app.js +++ b/web/app/sketcher/sketcher-app.js @@ -16,7 +16,7 @@ import {ReferencePointTool} from './tools/origin' import {InputManager} from './input-manager' import genSerpinski from '../utils/genSerpinski'; import React from "react"; -import {getActionIfAvailable} from "./actions"; +import {runActionOrToastWhyNot} from "./actions"; import {stream} from "../../../modules/lstream"; function App2D() { @@ -25,8 +25,8 @@ function App2D() { this.viewer = new Viewer(document.getElementById('viewer'), IO); this.context = createAppContext(this.viewer); this.winManager = new ui.WinManager(); - this.inputManager = new InputManager(this); - + this.inputManager = new InputManager(this); + this.initSketchManager(); this._exportWin = new ui.Window($('#exportManager'), app.winManager); @@ -61,7 +61,7 @@ function App2D() { this.winManager.registerResize(dockEl, ui.DIRECTIONS.EAST, function() {$('body').trigger('layout'); }); $('body').on('layout', this.viewer.onWindowResize); - + this.registerAction = function(id, desc, action, command) { app.actions[id] = {id, desc, action}; if (command) { @@ -69,7 +69,7 @@ function App2D() { } app._actionsOrder.push(id); }; - + function checkForTerminalVisibility() { const terminalVisible = app.commandsWin.root.is(':visible'); if (terminalVisible) { @@ -78,11 +78,11 @@ function App2D() { app.viewer.referencePoint.visible = terminalVisible; } checkForTerminalVisibility(); - + this.registerAction('new', "Create New Sketch", function () { app.newSketch(); }); - + this.registerAction('terminal', "Open/Close Terminal Window", function () { app.commandsWin.toggle(); checkForTerminalVisibility(); @@ -109,7 +109,7 @@ function App2D() { this.registerAction('exportDXF', "Export To DXF", function () { IO.exportTextData(app.viewer.io.dxfExport(), app.getSketchId() + ".dxf"); }); - + this.registerAction('undo', "Undo", function () { app.viewer.historyManager.undo(); }); @@ -129,7 +129,7 @@ function App2D() { this.registerAction('addPoint', "Add Point", function () { app.viewer.toolManager.takeControl(new AddPointTool(app.viewer)); }, "point"); - + this.registerAction('addSegment', "Add Segment", function () { app.viewer.toolManager.takeControl(new AddSegmentTool(app.viewer, false)); }, 'line'); @@ -157,7 +157,7 @@ function App2D() { this.registerAction('addBezierCurve', "Add Bezier Curve", function () { app.viewer.toolManager.takeControl(new BezierCurveTool(app.viewer)); }); - + this.registerAction('addRectangle', "Add Rectangle", function () { app.viewer.toolManager.takeControl(new RectangleTool(app.viewer)); }, 'rect'); @@ -169,7 +169,7 @@ function App2D() { this.registerAction('pan', "Pan", function () { app.viewer.toolManager.releaseControl(); }); - + this.registerAction('addFillet', "Add Fillet", function () { app.viewer.toolManager.takeControl(new FilletTool(app.viewer)); }); @@ -177,11 +177,11 @@ function App2D() { this.registerAction('addDim', "Add Dimension", function () { app.viewer.toolManager.takeControl(new AddFreeDimTool(app.viewer, app.viewer.dimLayer)); }); - + this.registerAction('addHDim', "Add Horizontal Dimension", function () { app.viewer.toolManager.takeControl(new AddHorizontalDimTool(app.viewer, app.viewer.dimLayer)); }); - + this.registerAction('addVDim', "Add Vertical Dimension", function () { app.viewer.toolManager.takeControl(new AddVerticalDimTool(app.viewer, app.viewer.dimLayer)); }); @@ -198,27 +198,27 @@ function App2D() { }); this.registerAction('coincident', "Coincident", function () { - getActionIfAvailable('Coincident', app.viewer.selected, action => action.invoke(app.context)); + runActionOrToastWhyNot('Coincident', app.viewer.selected, app.context); }); this.registerAction('verticalConstraint', "Vertical Constraint", function () { - getActionIfAvailable('Vertical', app.viewer.selected, action => action.invoke(app.context)); + runActionOrToastWhyNot('Vertical', app.viewer.selected, app.context); }); this.registerAction('horizontalConstraint', "Horizontal Constraint", function () { - getActionIfAvailable('Horizontal', app.viewer.selected, action => action.invoke(app.context)); + runActionOrToastWhyNot('Horizontal', app.viewer.selected, app.context); }); this.registerAction('parallelConstraint', "Parallel Constraint", function () { - getActionIfAvailable('Parallel', app.viewer.selected, action => action.invoke(app.context)); + runActionOrToastWhyNot('Parallel', app.viewer.selected, app.context); }); this.registerAction('perpendicularConstraint', "Perpendicular Constraint", function () { - getActionIfAvailable('Perpendicular', app.viewer.selected, action => action.invoke(app.context)); + runActionOrToastWhyNot('Perpendicular', app.viewer.selected, app.context); }); this.registerAction('P2LDistanceConstraint', "Distance Between Point and Line", function () { - getActionIfAvailable('DistancePL', app.viewer.selected, action => action.invoke(app.context)); + runActionOrToastWhyNot('DistancePL', app.viewer.selected, app.context); }); this.registerAction('mirrorConstraint', "Mirror Constraint", function () { @@ -226,7 +226,7 @@ function App2D() { }); this.registerAction('P2PDistanceConstraint', "Distance Between two Points", function () { - getActionIfAvailable('DistancePP', app.viewer.selected, action => action.invoke(app.context)); + runActionOrToastWhyNot('DistancePP', app.viewer.selected, app.context); }); this.registerAction('RadiusConstraint', "Radius Constraint", function () { diff --git a/webpack.config.js b/webpack.config.js index 98afaded..329491e7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,7 @@ const generateCSSScopedName = require('./build/cssScoopeGenerator')(); const WEB_APP = path.join(__dirname, 'web/app'); const MODULES = path.join(__dirname, 'modules'); +const NODE_MODULES = path.join(__dirname, 'node_modules'); const INTEGRATION_TESTS = path.join(__dirname, 'web/test'); const GLOBAL_CSS = path.join(__dirname, 'web/css'); @@ -59,6 +60,14 @@ module.exports = { 'less-loader', ] }, + { + test: /\.(css)$/, + include: [NODE_MODULES], + use: [ + 'style-loader', + 'css-loader', + ] + }, { test: /\.(less|css)$/, include: [MODULES, WEB_APP],