Skip to content

Commit b408259

Browse files
Merged master
2 parents 7ef1cce + 6edab77 commit b408259

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1354
-338
lines changed

.github/workflows/deploy_nightly.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ jobs:
1717
runs-on: ubuntu-latest
1818
steps:
1919
- name: Checkout
20-
uses: actions/checkout@master
20+
uses: actions/checkout@main
2121

2222
- name: Setup GCloud
23-
uses: google-github-actions/setup-gcloud@master
23+
uses: google-github-actions/setup-gcloud@main
2424
with:
2525
version: '274.0.0'
2626
service_account_email: ${{ secrets.GCP_SA_EMAIL }}
2727
service_account_key: ${{ secrets.GCP_SA_KEY }}
2828
export_default_credentials: true
2929

3030
- name: Set Node.js 16.x
31-
uses: actions/setup-node@master
31+
uses: actions/setup-node@main
3232
with:
3333
node-version: 16.x
3434

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
docs/ts/app/core
1313
docs/ts/app/digital
1414

15+
.devCache
1516
node_modules
1617
coverage
1718
build

scripts/webpack/customDevServer.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const bodyParser = require("body-parser");
4+
5+
6+
const CACHE_PATH = path.resolve(process.cwd(), ".devCache");
7+
8+
/**
9+
* Custom dev server middleware for use in issue #1037
10+
*
11+
* Specifically creates a dev API for saving/fetching files
12+
*/
13+
module.exports = (devServer) => {
14+
if (!devServer)
15+
throw new Error("webpack-dev-server is not defined");
16+
17+
// Create new file
18+
devServer.app.post("/dev/file/:id", bodyParser.text(), (req, res) => {
19+
// Make cached directory if it doesn't already exist
20+
if (!fs.existsSync(CACHE_PATH))
21+
fs.mkdirSync(CACHE_PATH);
22+
23+
const fileId = req.params.id;
24+
const file = req.body;
25+
26+
// Well aware of how unsafe this is, but since it's a DEV only feature
27+
// and only runs on the dev's computer, it should be fine
28+
fs.writeFileSync(path.resolve(CACHE_PATH, fileId), file);
29+
30+
res.status(200);
31+
});
32+
33+
// Get file by ID
34+
devServer.app.get("/dev/file/:id", (req, res) => {
35+
const fileId = req.params.id;
36+
37+
const filePath = path.resolve(CACHE_PATH, fileId);
38+
if (!fs.existsSync(filePath))
39+
return res.status(404);
40+
41+
const data = fs.readFileSync(filePath).toString("utf-8");
42+
43+
res.status(200).send(data);
44+
});
45+
46+
// List saved files
47+
devServer.app.get("/dev/filelist", (req, res) => {
48+
if (!fs.existsSync(CACHE_PATH))
49+
return res.status(200).json({ files: [] });
50+
51+
const files = fs.readdirSync(CACHE_PATH).filter(f => f.endsWith(".circuit"));
52+
53+
res.status(200).json({ files });
54+
});
55+
}

scripts/webpack/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ module.exports = async (dir, mode) => {
111111
client: {
112112
overlay: true,
113113
},
114+
// Allows devs to save local circuits for use in #1037
115+
onBeforeSetupMiddleware: require("./customDevServer"),
114116
}, compiler);
115117

116118
["SIGINT", "SIGTERM"].forEach(sig => {

src/app/core/actions/Action.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export interface Action {
33
execute(): Action;
44
undo(): Action;
55
getName(): string;
6+
getCustomInfo?(): string[] | undefined;
67
}

src/app/core/actions/GroupAction.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import {Action} from "core/actions/Action";
33
export class GroupAction implements Action {
44
private actions: Action[];
55
private customName?: string;
6+
private customInfo?: string[];
67

7-
public constructor(actions?: Action[], customName?: string) {
8+
public constructor(actions?: Action[], customName?: string, customInfo?: string[]) {
89
this.actions = actions || [];
910
this.customName = customName;
11+
this.customInfo = customInfo;
1012
}
1113

1214
public add(action: Action | Action[]): GroupAction {
@@ -51,6 +53,10 @@ export class GroupAction implements Action {
5153
return `Grouped ${this.actions.length} actions` ;
5254
}
5355

56+
public getCustomInfo(): string[] | undefined {
57+
return this.customInfo;
58+
}
59+
5460
public getActions(): Action[] {
5561
return this.actions;
5662
}

src/app/core/actions/HistoryManager.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {Action} from "core/actions/Action";
22

33

4-
export type HistoryCallbackType = "add" | "undo" | "redo";
5-
export type HistoryCallback = (type: HistoryCallbackType, action: Action) => void;
4+
export type HistoryCallbackType = "add" | "undo" | "redo" | "reset";
5+
export type HistoryCallback = (type: HistoryCallbackType, action?: Action) => void;
66

77
/**
88
* Manages undo/redo actions
@@ -22,7 +22,7 @@ export class HistoryManager {
2222
this.callbacks = new Set();
2323
}
2424

25-
private callback(type: HistoryCallbackType, action: Action): void {
25+
private callback(type: HistoryCallbackType, action?: Action): void {
2626
this.callbacks.forEach(c => c(type, action));
2727
}
2828

@@ -99,6 +99,7 @@ export class HistoryManager {
9999
public reset(): void {
100100
this.undoStack = [];
101101
this.redoStack = [];
102+
this.callback("reset");
102103
}
103104

104105
public getActions(): Action[] {

src/app/core/actions/ShiftAction.ts

+3
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,7 @@ export class ShiftAction implements Action {
3131
return "Shift Object";
3232
}
3333

34+
public getCustomInfo(): string[] {
35+
return [`${this.obj.getName()}: ${this.i}`];
36+
}
3437
}

src/app/core/actions/ports/PortChangeAction.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Action} from "core/actions/Action";
22

3-
import {GetPath} from "core/utils/ComponentUtils";
3+
import {GetWirePath} from "core/utils/ComponentUtils";
44

55
import {CircuitDesigner, Port} from "core/models";
66

@@ -33,7 +33,7 @@ export abstract class PortChangeAction implements Action {
3333
// that will be remove if target < ports.length
3434
while (ports.length > this.targetCount) {
3535
const wires = ports.pop()!.getWires();
36-
action.add(wires.map(w => CreateDeletePathAction(this.designer, GetPath(w))));
36+
action.add(wires.map(w => CreateDeletePathAction(this.designer, GetWirePath(w))));
3737
}
3838

3939
return action;

src/app/core/actions/transform/RotateAction.ts

+8
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,12 @@ export class RotateAction implements Action {
8484
return "Rotate";
8585
}
8686

87+
public getCustomInfo(): string[] {
88+
const deg = String.fromCharCode(176);
89+
return Array.from(
90+
this.objects, (obj, i) =>
91+
`${obj.getName()}: rotated from ${Math.round(this.initialAngles[i] * (180 / Math.PI))}${deg}
92+
to ${Math.round(this.finalAngles[i] * (180 / Math.PI))}${deg}`
93+
);
94+
}
8795
}

src/app/core/actions/transform/TranslateAction.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {SnapPos} from "./SnapUtils";
1212
* used for moving componets from one position to another.
1313
*/
1414
export class TranslateAction implements Action {
15-
15+
1616
/**
1717
* An array of the selected component(s)
1818
*/
@@ -28,6 +28,12 @@ export class TranslateAction implements Action {
2828
*/
2929
protected targetPositions: Vector[];
3030

31+
/**
32+
* Flag that represents whether or not positions should be snapped.
33+
* Necessary to resolve issue #910
34+
*/
35+
protected snap: boolean;
36+
3137
/**
3238
* Creates a translation of component(s) from one position to another.
3339
* Each component in objs list has corresponding initial position and target position in those
@@ -36,12 +42,14 @@ export class TranslateAction implements Action {
3642
* @param objs Initializes the array with the selected component(s)
3743
* @param initialPositions Initializes the array with the selected components' starting positions
3844
* @param targetPositions Initializes the array with the selected components' final positions
45+
* @param snap Sets whether or not components will snap. Defaults to true.
3946
*/
40-
public constructor(objs: Component[], initialPositions: Vector[], targetPositions: Vector[]) {
47+
public constructor(objs: Component[], initialPositions: Vector[], targetPositions: Vector[], snap = true) {
4148
this.objs = objs;
4249

4350
this.initialPositions = initialPositions;
4451
this.targetPositions = targetPositions;
52+
this.snap = snap;
4553
}
4654

4755
/**
@@ -53,7 +61,8 @@ export class TranslateAction implements Action {
5361
this.objs.forEach((o, i) => o.setPos(this.targetPositions[i]));
5462

5563
// Always snap afterwards to avoid issue #417
56-
this.objs.forEach(o => SnapPos(o));
64+
if (this.snap)
65+
this.objs.forEach(o => SnapPos(o));
5766

5867
return this;
5968
}
@@ -67,12 +76,21 @@ export class TranslateAction implements Action {
6776
this.objs.forEach((o, i) => o.setPos(this.initialPositions[i]));
6877

6978
// Always snap afterwards to avoid issue #417
70-
this.objs.forEach(o => SnapPos(o));
79+
if (this.snap)
80+
this.objs.forEach(o => SnapPos(o));
7181

7282
return this;
7383
}
7484

7585
public getName(): string {
7686
return "Move Object";
7787
}
88+
89+
public getCustomInfo(): string[] {
90+
return Array.from(
91+
this.objs, (obj, i) =>
92+
`${obj.getName()}: moved from (${this.initialPositions[i].x.toFixed(2)}, ${this.initialPositions[i].y.toFixed(2)})
93+
to (${this.targetPositions[i].x.toFixed(2)}, ${this.targetPositions[i].y.toFixed(2)})`
94+
)
95+
}
7896
}

src/app/core/tools/InteractionTool.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import {LEFT_MOUSE_BUTTON} from "core/utils/Constants";
2+
13
import {Vector} from "Vector";
24

35
import {CircuitInfo} from "core/utils/CircuitInfo";
46
import {Event} from "core/utils/Events";
57
import {isPressable} from "core/utils/Pressable";
6-
import {LEFT_MOUSE_BUTTON} from "core/utils/Constants";
78

89
import {CircuitDesigner, IOObject} from "core/models";
910

src/app/core/tools/PanTool.ts

+27-23
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,24 @@ import {Tool} from "core/tools/Tool";
1212
export const PanTool: Tool = (() => {
1313
let isDragging = false;
1414
return {
15-
shouldActivate(event: Event, {input}: CircuitInfo): boolean {
15+
shouldActivate(event: Event, { input }: CircuitInfo): boolean {
1616
// Activate if the user just pressed the "option key"
1717
// or if the user began dragging with either 2 fingers
1818
// or the middle mouse button
1919
// or if the user pressed one of of the arrow keys while no components are selected
20-
return ((event.type === "keydown" && ((event.key === "Alt") ||
21-
(event.key === "ArrowLeft" || event.key === "ArrowRight" ||
20+
return ((event.type === "keydown" && ((event.key === "Alt") ||
21+
(event.key === "ArrowLeft" || event.key === "ArrowRight" ||
2222
event.key === "ArrowUp" || event.key === "ArrowDown" ))) ||
2323
(event.type === "mousedrag" && (event.button === MIDDLE_MOUSE_BUTTON ||
2424
input.getTouchCount() === 2)));
2525
},
26-
shouldDeactivate(event: Event, {}: CircuitInfo): boolean {
27-
// Deactivate if stopped dragging by releasing mouse
28-
// or if no dragging happened and "Alt" was released
26+
shouldDeactivate(event: Event, { input }: CircuitInfo): boolean {
27+
// Deactivate user stopped dragging
28+
// and the alt key isn't currently pressed
2929
// or if one of the arrow keys were released
30-
return (event.type === "mouseup") ||
31-
(event.type === "keyup" && ((!isDragging && event.key === "Alt" ||
32-
(event.key === "ArrowLeft" || event.key === "ArrowRight" ||
33-
event.key === "ArrowUp" || event.key === "ArrowDown"))))
30+
return (!isDragging && !input.isAltKeyDown()) ||
31+
(event.type === "keyup" && (event.key === "ArrowLeft" || event.key === "ArrowRight" ||
32+
event.key === "ArrowUp" || event.key === "ArrowDown"));
3433
},
3534

3635

@@ -43,20 +42,25 @@ export const PanTool: Tool = (() => {
4342
},
4443

4544

46-
onEvent(event: Event, {input, camera}: CircuitInfo): boolean {
45+
onEvent(event: Event, { input, camera }: CircuitInfo): boolean {
4746
if (event.type === "mousedrag") {
4847
isDragging = true;
49-
48+
5049
const dPos = input.getDeltaMousePos();
5150
camera.translate(dPos.scale(-1 * camera.getZoom()));
52-
51+
52+
return true;
53+
}
54+
55+
if (event.type === "mouseup") {
56+
isDragging = false;
5357
return true;
5458
}
55-
59+
5660
if (event.type === "keydown") {
5761
let dPos = new Vector();
58-
59-
// No else if because it introduces bugs when
62+
63+
// No else if because it introduces bugs when
6064
// multiple arrow keys are pressed
6165
if (input.isKeyDown("ArrowLeft"))
6266
dPos = dPos.add(-1, 0);
@@ -66,18 +70,18 @@ export const PanTool: Tool = (() => {
6670
dPos = dPos.add(0, -1);
6771
if (input.isKeyDown("ArrowDown"))
6872
dPos = dPos.add(0, 1);
69-
73+
7074
// Screen gets moved different amounts depending on if the shift key is held
71-
const factor = (input.isShiftKeyDown() ? ARROW_PAN_DISTANCE_SMALL : ARROW_PAN_DISTANCE_NORMAL);
72-
75+
const factor = (input.isShiftKeyDown() ? ARROW_PAN_DISTANCE_SMALL : ARROW_PAN_DISTANCE_NORMAL);
76+
7377
camera.translate(dPos.scale(factor * camera.getZoom()));
74-
78+
7579
return true;
7680
}
77-
78-
// Since it wasn't one of the two event types we want we
81+
82+
// Since it wasn't one of the two event types we want we
7983
// don't need a re-render
8084
return false;
81-
}
85+
},
8286
}
8387
})();

src/app/core/tools/ToolManager.ts

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export class ToolManager {
1616
this.tools = tools;
1717
}
1818

19+
public reset(info: CircuitInfo): void {
20+
if (this.currentTool)
21+
this.currentTool.onDeactivate({ type: "unknown" }, info);
22+
this.currentTool = undefined;
23+
}
24+
1925
public onEvent(event: Event, info: CircuitInfo): boolean {
2026
// Call the current tool's (or default tool's) onEvent method
2127

0 commit comments

Comments
 (0)