From 86584b1874bddc55c6cb832d9deba5db2dbdeebc Mon Sep 17 00:00:00 2001 From: Piotr Skalski Date: Wed, 4 Sep 2019 19:43:14 +0200 Subject: [PATCH] 1.2.1-alpha relese merge (#45) --- README.md | 11 + src/App.tsx | 2 +- src/data/Direction.ts | 4 - src/data/EditorData.ts | 1 + src/data/RectAnchor.ts | 4 +- src/data/{ => enums}/AcceptedFileType.ts | 0 src/data/{Context.ts => enums/ContextType.ts} | 2 +- src/data/{ => enums}/CustomCursorStyle.ts | 0 .../{AnchorType.ts => enums/Direction.ts} | 2 +- src/data/{ => enums}/EventType.ts | 0 src/data/{ => enums}/ExportFormatType.ts | 0 src/data/{ => enums}/LabelType.ts | 0 src/data/{ => enums}/PopupWindowType.ts | 0 src/data/{ => enums}/ProjectType.ts | 0 .../{ => export}/PointExportFormatData.ts | 4 +- .../{ => export}/PolygonExportFormatData.ts | 4 +- src/data/{ => export}/RectExportFormatData.ts | 6 +- src/data/{ => info}/EditorFeatureData.ts | 0 src/data/{ => info}/LabelToolkitData.ts | 2 +- src/data/{ => info}/SocialMediaData.ts | 2 +- src/interfaces/IExportFormat.ts | 2 +- src/logic/actions/EditorActions.ts | 164 ++++++++++ src/logic/context/BaseContext.ts | 9 + src/logic/context/ContextManager.ts | 26 +- src/logic/context/EditorContext.ts | 58 ++++ src/logic/context/PopupContext.ts | 22 ++ src/logic/export/PointLabelsExport.ts | 4 +- src/logic/export/PolygonLabelsExporter.ts | 2 +- src/logic/export/RectLabelsExporter.ts | 2 +- src/logic/render/BaseRenderEngine.ts | 4 +- src/logic/render/PointRenderEngine.ts | 77 ++--- src/logic/render/PolygonRenderEngine.ts | 68 ++-- src/logic/render/RectRenderEngine.ts | 56 ++-- src/model/EditorModel.ts | 17 + src/settings/_Settings.scss | 1 + src/store/editor/actionCreators.ts | 4 +- src/store/editor/types.ts | 4 +- src/store/general/actionCreators.ts | 8 +- src/store/general/reducer.ts | 2 +- src/store/general/types.ts | 10 +- src/store/selectors/GeneralSelector.ts | 8 + src/utils/DrawUtil.ts | 28 -- src/utils/EditorUtil.ts | 33 ++ src/utils/MouseEventUtil.ts | 6 +- src/utils/RectUtil.ts | 47 +-- src/utils/RenderEngineUtil.ts | 64 +++- .../BottomNavigationBar.scss | 4 + .../BottomNavigationBar.tsx | 19 +- src/views/EditorView/Editor/Editor.tsx | 300 ++++-------------- .../EditorContainer/EditorContainer.scss | 5 +- .../EditorContainer/EditorContainer.tsx | 8 +- src/views/EditorView/EditorView.tsx | 2 +- .../ImagesList/ImagesList.tsx | 2 +- .../LabelInputField/LabelInputField.tsx | 15 +- .../LabelsToolkit/LabelsToolkit.tsx | 6 +- .../SideNavigationBar/SideNavigationBar.tsx | 2 +- src/views/EditorView/StateBar/StateBar.tsx | 2 +- .../TopNavigationBar/TopNavigationBar.scss | 3 +- .../TopNavigationBar/TopNavigationBar.tsx | 2 +- .../ImagesDropZone/ImagesDropZone.tsx | 6 +- src/views/MainView/MainView.tsx | 4 +- src/views/MobileMainView/MobileMainView.tsx | 4 +- .../ExitProjectPopup/ExitProjectPopup.tsx | 4 +- .../ExportLabelsPopup/ExportLabelPopup.tsx | 12 +- .../GenericYesNoPopup/GenericYesNoPopup.tsx | 13 +- .../InsertLabelNamesPopup.tsx | 2 +- .../LoadLabelNamesPopup.tsx | 4 +- .../LoadMoreImagesPopup.tsx | 4 +- src/views/PopupView/PopupView.tsx | 2 +- tsconfig.json | 2 +- 70 files changed, 692 insertions(+), 505 deletions(-) delete mode 100644 src/data/Direction.ts rename src/data/{ => enums}/AcceptedFileType.ts (100%) rename src/data/{Context.ts => enums/ContextType.ts} (84%) rename src/data/{ => enums}/CustomCursorStyle.ts (100%) rename src/data/{AnchorType.ts => enums/Direction.ts} (90%) rename src/data/{ => enums}/EventType.ts (100%) rename src/data/{ => enums}/ExportFormatType.ts (100%) rename src/data/{ => enums}/LabelType.ts (100%) rename src/data/{ => enums}/PopupWindowType.ts (100%) rename src/data/{ => enums}/ProjectType.ts (100%) rename src/data/{ => export}/PointExportFormatData.ts (53%) rename src/data/{ => export}/PolygonExportFormatData.ts (57%) rename src/data/{ => export}/RectExportFormatData.ts (74%) rename src/data/{ => info}/EditorFeatureData.ts (100%) rename src/data/{ => info}/LabelToolkitData.ts (94%) rename src/data/{ => info}/SocialMediaData.ts (94%) create mode 100644 src/logic/actions/EditorActions.ts create mode 100644 src/logic/context/BaseContext.ts create mode 100644 src/logic/context/EditorContext.ts create mode 100644 src/logic/context/PopupContext.ts create mode 100644 src/model/EditorModel.ts create mode 100644 src/store/selectors/GeneralSelector.ts create mode 100644 src/utils/EditorUtil.ts diff --git a/README.md b/README.md index f2cdccab..2daf6db1 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,17 @@ Our application is being actively developed. If you have an idea for a new funct - [ ] Integration with external storage - Amazon S3, Google Drive, Dropbox - [ ] Copy annotations from previous image into the next one +## Citation + +``` +@MISC{make-sense, + author = {Piotr Skalski}, + title = {{Make Sense}}, + howpublished = "\url{https://github.com/SkalskiP/make-sense/}", + year = {2019}, +} +``` + ## License This project is licensed under the GPL-3.0 License - see the [LICENSE][2] file for details diff --git a/src/App.tsx b/src/App.tsx index 735e4d08..1cb8a25b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import React from 'react'; import './App.scss'; import EditorView from "./views/EditorView/EditorView"; import MainView from "./views/MainView/MainView"; -import {ProjectType} from "./data/ProjectType"; +import {ProjectType} from "./data/enums/ProjectType"; import {AppState} from "./store"; import {connect} from "react-redux"; import PopupView from "./views/PopupView/PopupView"; diff --git a/src/data/Direction.ts b/src/data/Direction.ts deleted file mode 100644 index 16044177..00000000 --- a/src/data/Direction.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum Direction { - LEFT = "LEFT", - RIGHT = "RIGHT" -} \ No newline at end of file diff --git a/src/data/EditorData.ts b/src/data/EditorData.ts index 09c72f31..2e7b6586 100644 --- a/src/data/EditorData.ts +++ b/src/data/EditorData.ts @@ -7,5 +7,6 @@ export interface EditorData { canvasSize: ISize, activeImageScale: number, activeImageRectOnCanvas: IRect, + activeKeyCombo: string[], event?: Event } \ No newline at end of file diff --git a/src/data/RectAnchor.ts b/src/data/RectAnchor.ts index cbe8e263..126d42d7 100644 --- a/src/data/RectAnchor.ts +++ b/src/data/RectAnchor.ts @@ -1,7 +1,7 @@ -import {AnchorType} from "./AnchorType"; import {IPoint} from "../interfaces/IPoint"; +import {Direction} from "./enums/Direction"; export interface RectAnchor { - type: AnchorType, + type: Direction, position: IPoint } \ No newline at end of file diff --git a/src/data/AcceptedFileType.ts b/src/data/enums/AcceptedFileType.ts similarity index 100% rename from src/data/AcceptedFileType.ts rename to src/data/enums/AcceptedFileType.ts diff --git a/src/data/Context.ts b/src/data/enums/ContextType.ts similarity index 84% rename from src/data/Context.ts rename to src/data/enums/ContextType.ts index 40d21ce7..c806707c 100644 --- a/src/data/Context.ts +++ b/src/data/enums/ContextType.ts @@ -1,4 +1,4 @@ -export enum Context { +export enum ContextType { EDITOR = "EDITOR", LEFT_NAVBAR = "LEFT_NAVBAR", RIGHT_NAVBAR = "RIGHT_NAVBAR", diff --git a/src/data/CustomCursorStyle.ts b/src/data/enums/CustomCursorStyle.ts similarity index 100% rename from src/data/CustomCursorStyle.ts rename to src/data/enums/CustomCursorStyle.ts diff --git a/src/data/AnchorType.ts b/src/data/enums/Direction.ts similarity index 90% rename from src/data/AnchorType.ts rename to src/data/enums/Direction.ts index 0abda66f..9681278c 100644 --- a/src/data/AnchorType.ts +++ b/src/data/enums/Direction.ts @@ -1,4 +1,4 @@ -export enum AnchorType { +export enum Direction { TOP = "TOP", BOTTOM = "BOTTOM", LEFT = "LEFT", diff --git a/src/data/EventType.ts b/src/data/enums/EventType.ts similarity index 100% rename from src/data/EventType.ts rename to src/data/enums/EventType.ts diff --git a/src/data/ExportFormatType.ts b/src/data/enums/ExportFormatType.ts similarity index 100% rename from src/data/ExportFormatType.ts rename to src/data/enums/ExportFormatType.ts diff --git a/src/data/LabelType.ts b/src/data/enums/LabelType.ts similarity index 100% rename from src/data/LabelType.ts rename to src/data/enums/LabelType.ts diff --git a/src/data/PopupWindowType.ts b/src/data/enums/PopupWindowType.ts similarity index 100% rename from src/data/PopupWindowType.ts rename to src/data/enums/PopupWindowType.ts diff --git a/src/data/ProjectType.ts b/src/data/enums/ProjectType.ts similarity index 100% rename from src/data/ProjectType.ts rename to src/data/enums/ProjectType.ts diff --git a/src/data/PointExportFormatData.ts b/src/data/export/PointExportFormatData.ts similarity index 53% rename from src/data/PointExportFormatData.ts rename to src/data/export/PointExportFormatData.ts index 96122dbb..1406a41a 100644 --- a/src/data/PointExportFormatData.ts +++ b/src/data/export/PointExportFormatData.ts @@ -1,5 +1,5 @@ -import {ExportFormatType} from "./ExportFormatType"; -import {IExportFormat} from "../interfaces/IExportFormat"; +import {ExportFormatType} from "../enums/ExportFormatType"; +import {IExportFormat} from "../../interfaces/IExportFormat"; export const PointExportFormatData: IExportFormat[] = [ { diff --git a/src/data/PolygonExportFormatData.ts b/src/data/export/PolygonExportFormatData.ts similarity index 57% rename from src/data/PolygonExportFormatData.ts rename to src/data/export/PolygonExportFormatData.ts index d6910dd2..317586f5 100644 --- a/src/data/PolygonExportFormatData.ts +++ b/src/data/export/PolygonExportFormatData.ts @@ -1,5 +1,5 @@ -import {IExportFormat} from "../interfaces/IExportFormat"; -import {ExportFormatType} from "./ExportFormatType"; +import {IExportFormat} from "../../interfaces/IExportFormat"; +import {ExportFormatType} from "../enums/ExportFormatType"; export const PolygonExportFormatData: IExportFormat[] = [ { diff --git a/src/data/RectExportFormatData.ts b/src/data/export/RectExportFormatData.ts similarity index 74% rename from src/data/RectExportFormatData.ts rename to src/data/export/RectExportFormatData.ts index 1b814222..5203add0 100644 --- a/src/data/RectExportFormatData.ts +++ b/src/data/export/RectExportFormatData.ts @@ -1,5 +1,5 @@ -import {ExportFormatType} from "./ExportFormatType"; -import {IExportFormat} from "../interfaces/IExportFormat"; +import {ExportFormatType} from "../enums/ExportFormatType"; +import {IExportFormat} from "../../interfaces/IExportFormat"; export const RectExportFormatData: IExportFormat[] = [ { @@ -14,4 +14,4 @@ export const RectExportFormatData: IExportFormat[] = [ type: ExportFormatType.CSV, label: "Single CSV file." } -] \ No newline at end of file +]; \ No newline at end of file diff --git a/src/data/EditorFeatureData.ts b/src/data/info/EditorFeatureData.ts similarity index 100% rename from src/data/EditorFeatureData.ts rename to src/data/info/EditorFeatureData.ts diff --git a/src/data/LabelToolkitData.ts b/src/data/info/LabelToolkitData.ts similarity index 94% rename from src/data/LabelToolkitData.ts rename to src/data/info/LabelToolkitData.ts index 07b0d211..462bc711 100644 --- a/src/data/LabelToolkitData.ts +++ b/src/data/info/LabelToolkitData.ts @@ -1,4 +1,4 @@ -import {LabelType} from "./LabelType"; +import {LabelType} from "../enums/LabelType"; export interface ILabelToolkit { labelType: LabelType; diff --git a/src/data/SocialMediaData.ts b/src/data/info/SocialMediaData.ts similarity index 94% rename from src/data/SocialMediaData.ts rename to src/data/info/SocialMediaData.ts index 911f2f1a..eb9d8777 100644 --- a/src/data/SocialMediaData.ts +++ b/src/data/info/SocialMediaData.ts @@ -1,4 +1,4 @@ -import {Settings} from "../settings/Settings"; +import {Settings} from "../../settings/Settings"; export interface ISocialMedia { displayName:string; diff --git a/src/interfaces/IExportFormat.ts b/src/interfaces/IExportFormat.ts index 9a498965..a30aa973 100644 --- a/src/interfaces/IExportFormat.ts +++ b/src/interfaces/IExportFormat.ts @@ -1,4 +1,4 @@ -import {ExportFormatType} from "../data/ExportFormatType"; +import {ExportFormatType} from "../data/enums/ExportFormatType"; export interface IExportFormat { type: ExportFormatType, diff --git a/src/logic/actions/EditorActions.ts b/src/logic/actions/EditorActions.ts new file mode 100644 index 00000000..887c835f --- /dev/null +++ b/src/logic/actions/EditorActions.ts @@ -0,0 +1,164 @@ +import {LabelType} from "../../data/enums/LabelType"; +import {EditorModel} from "../../model/EditorModel"; +import {RectRenderEngine} from "../render/RectRenderEngine"; +import {PointRenderEngine} from "../render/PointRenderEngine"; +import {PolygonRenderEngine} from "../render/PolygonRenderEngine"; +import {IRect} from "../../interfaces/IRect"; +import {Settings} from "../../settings/Settings"; +import {RectUtil} from "../../utils/RectUtil"; +import {EditorData} from "../../data/EditorData"; +import {CanvasUtil} from "../../utils/CanvasUtil"; +import {ISize} from "../../interfaces/ISize"; +import React from "react"; +import {IPoint} from "../../interfaces/IPoint"; +import {DrawUtil} from "../../utils/DrawUtil"; +import {PrimaryEditorRenderEngine} from "../render/PrimaryEditorRenderEngine"; +import {ContextManager} from "../context/ContextManager"; + +export class EditorActions { + + // ================================================================================================================= + // RENDER ENGINES + // ================================================================================================================= + + public static mountSupportRenderingEngine(activeLabelType: LabelType) { + switch (activeLabelType) { + case LabelType.RECTANGLE: + EditorModel.supportRenderingEngine = new RectRenderEngine(EditorModel.canvas); + break; + case LabelType.POINT: + EditorModel.supportRenderingEngine = new PointRenderEngine(EditorModel.canvas); + break; + case LabelType.POLYGON: + EditorModel.supportRenderingEngine = new PolygonRenderEngine(EditorModel.canvas); + break; + default: + EditorModel.supportRenderingEngine = null; + break; + } + }; + + public static swapSupportRenderingEngine(activeLabelType: LabelType) { + EditorActions.mountSupportRenderingEngine(activeLabelType); + }; + + public static mountRenderEngines(activeLabelType: LabelType) { + EditorModel.primaryRenderingEngine = new PrimaryEditorRenderEngine(EditorModel.canvas); + EditorActions.mountSupportRenderingEngine(activeLabelType); + } + + // ================================================================================================================= + // RENDER + // ================================================================================================================= + + public static fullRender() { + DrawUtil.clearCanvas(EditorModel.canvas); + EditorModel.primaryRenderingEngine.drawImage(EditorModel.image, EditorModel.imageRectOnCanvas); + EditorModel.primaryRenderingEngine.render(EditorActions.getEditorData()); + EditorModel.supportRenderingEngine && EditorModel.supportRenderingEngine.render(EditorActions.getEditorData()); + } + + // ================================================================================================================= + // SETTERS + // ================================================================================================================= + + public static setLoadingStatus(status: boolean) { + EditorModel.isLoading = status; + } + + public static setActiveImage(image: HTMLImageElement) { + EditorModel.image = image; + } + + // ================================================================================================================= + // GETTERS + // ================================================================================================================= + + public static getImageRect(image: HTMLImageElement): IRect | null { + if (!!image) { + const canvasPaddingWidth: number = Settings.CANVAS_PADDING_WIDTH_PX; + const imageRect: IRect = { x: 0, y: 0, width: image.width, height: image.height}; + const canvasRect: IRect = { + x: canvasPaddingWidth, + y: canvasPaddingWidth, + width: EditorModel.canvas.width - 2 * canvasPaddingWidth, + height: EditorModel.canvas.height - 2 * canvasPaddingWidth + }; + return RectUtil.fitInsideRectWithRatio(canvasRect, RectUtil.getRatio(imageRect)); + } + return null; + }; + + public static getImageScale(image: HTMLImageElement): number | null { + if (!image || !EditorModel.imageRectOnCanvas) + return null; + + return image.width / EditorModel.imageRectOnCanvas.width; + } + + public static getEditorData(event?: Event): EditorData { + return { + mousePositionOnCanvas: EditorModel.mousePositionOnCanvas, + canvasSize: CanvasUtil.getSize(EditorModel.canvas), + activeImageScale: EditorModel.imageScale, + activeImageRectOnCanvas: EditorModel.imageRectOnCanvas, + activeKeyCombo: ContextManager.getActiveCombo(), + event: event + } + } + + // ================================================================================================================= + // HELPERS + // ================================================================================================================= + + public static calculateActiveImageCharacteristics() { + EditorModel.imageRectOnCanvas = EditorActions.getImageRect(EditorModel.image); + EditorModel.imageScale = EditorActions.getImageScale(EditorModel.image); + } + + public static resizeCanvas = (newCanvasSize: ISize) => { + if (!!newCanvasSize && !!EditorModel.canvas) { + EditorModel.canvas.width = newCanvasSize.width; + EditorModel.canvas.height = newCanvasSize.height; + } + }; + + public static updateMousePositionIndicator(event: React.MouseEvent | MouseEvent) { + + if (!EditorModel.imageRectOnCanvas || !EditorModel.canvas) { + EditorModel.mousePositionIndicator.style.display = "none"; + EditorModel.cursor.style.display = "none"; + return; + } + + const mousePositionOnCanvas: IPoint = CanvasUtil.getMousePositionOnCanvasFromEvent(event, EditorModel.canvas); + const canvasRect: IRect = {x: 0, y: 0, ...CanvasUtil.getSize(EditorModel.canvas)}; + const isOverCanvas: boolean = RectUtil.isPointInside(canvasRect, mousePositionOnCanvas); + + if (!isOverCanvas) { + EditorModel.mousePositionIndicator.style.display = "none"; + EditorModel.cursor.style.display = "none"; + return; + } + + const isOverImage: boolean = RectUtil.isPointInside(EditorModel.imageRectOnCanvas, mousePositionOnCanvas); + + if (isOverImage) { + const scale = EditorModel.imageScale; + const x: number = Math.round((mousePositionOnCanvas.x - EditorModel.imageRectOnCanvas.x) * scale); + const y: number = Math.round((mousePositionOnCanvas.y - EditorModel.imageRectOnCanvas.y) * scale); + const text: string = "x: " + x + ", y: " + y; + + EditorModel.mousePositionIndicator.innerHTML = text; + EditorModel.mousePositionIndicator.style.left = (mousePositionOnCanvas.x + 15) + "px"; + EditorModel.mousePositionIndicator.style.top = (mousePositionOnCanvas.y + 15) + "px"; + EditorModel.mousePositionIndicator.style.display = "block"; + } else { + EditorModel.mousePositionIndicator.style.display = "none"; + } + + EditorModel.cursor.style.left = mousePositionOnCanvas.x + "px"; + EditorModel.cursor.style.top = mousePositionOnCanvas.y + "px"; + EditorModel.cursor.style.display = "block"; + }; +} \ No newline at end of file diff --git a/src/logic/context/BaseContext.ts b/src/logic/context/BaseContext.ts new file mode 100644 index 00000000..7f4fa443 --- /dev/null +++ b/src/logic/context/BaseContext.ts @@ -0,0 +1,9 @@ +import {HotKeyAction} from "../../data/HotKeyAction"; + +export class BaseContext { + public static actions: HotKeyAction[] = []; + + public static getActions(): HotKeyAction[] { + return this.actions; + } +} \ No newline at end of file diff --git a/src/logic/context/ContextManager.ts b/src/logic/context/ContextManager.ts index 678b2f2e..b06341c0 100644 --- a/src/logic/context/ContextManager.ts +++ b/src/logic/context/ContextManager.ts @@ -1,21 +1,41 @@ -import {Context} from "../../data/Context"; +import {ContextType} from "../../data/enums/ContextType"; import {HotKeyAction} from "../../data/HotKeyAction"; import {store} from "../../index"; import {updateActiveContext} from "../../store/general/actionCreators"; import * as _ from "lodash"; +import {EditorContext} from "./EditorContext"; +import {PopupContext} from "./PopupContext"; export class ContextManager { private static activeCombo: string[] = []; private static actions: HotKeyAction[] = []; + private static contextHistory: ContextType[] = []; + + public static getActiveCombo(): string[] { + return ContextManager.activeCombo; + } public static init(): void { window.addEventListener("keydown", ContextManager.onDown); window.addEventListener("keyup", ContextManager.onUp); } - public static switchCtx(context: Context, actions: HotKeyAction[]): void { + public static switchCtx(context: ContextType): void { store.dispatch(updateActiveContext(context)); - ContextManager.actions = actions; + switch (context) { + case ContextType.EDITOR: + ContextManager.actions = EditorContext.getActions(); + break; + case ContextType.POPUP: + ContextManager.actions = PopupContext.getActions(); + break; + default: + ContextManager.actions = []; + } + } + + public static restoreContext(): void { + ContextManager.switchCtx(ContextManager.contextHistory.pop()); } private static onDown(event: KeyboardEvent): void { diff --git a/src/logic/context/EditorContext.ts b/src/logic/context/EditorContext.ts new file mode 100644 index 00000000..eb32cede --- /dev/null +++ b/src/logic/context/EditorContext.ts @@ -0,0 +1,58 @@ +import {HotKeyAction} from "../../data/HotKeyAction"; +import {EditorModel} from "../../model/EditorModel"; +import {LabelType} from "../../data/enums/LabelType"; +import {EditorData} from "../../data/EditorData"; +import {EditorActions} from "../actions/EditorActions"; +import {PolygonRenderEngine} from "../render/PolygonRenderEngine"; +import {EditorSelector} from "../../store/selectors/EditorSelector"; +import {store} from "../../index"; +import {updateActiveImageIndex} from "../../store/editor/actionCreators"; +import {BaseContext} from "./BaseContext"; + +export class EditorContext extends BaseContext { + public static actions: HotKeyAction[] = [ + { + keyCombo: ["Enter"], + action: (event: KeyboardEvent) => { + if (EditorModel.supportRenderingEngine && EditorModel.supportRenderingEngine.labelType === LabelType.POLYGON) { + const editorData: EditorData = EditorActions.getEditorData(); + (EditorModel.supportRenderingEngine as PolygonRenderEngine).addLabelAndFinishCreation(editorData); + } + EditorActions.fullRender(); + } + }, + { + keyCombo: ["Escape"], + action: (event: KeyboardEvent) => { + if (EditorModel.supportRenderingEngine && EditorModel.supportRenderingEngine.labelType === LabelType.POLYGON) + (EditorModel.supportRenderingEngine as PolygonRenderEngine).cancelLabelCreation(); + EditorActions.fullRender(); + } + }, + { + keyCombo: ["ArrowLeft"], + action: (event: KeyboardEvent) => { + EditorContext.getPreviousImage(); + } + }, + { + keyCombo: ["ArrowRight"], + action: (event: KeyboardEvent) => { + EditorContext.getNextImage(); + } + } + ]; + + private static getPreviousImage(): void { + const currentImageIndex: number = EditorSelector.getActiveImageIndex(); + const previousImageIndex: number = Math.max(0, currentImageIndex - 1); + store.dispatch(updateActiveImageIndex(previousImageIndex)); + } + + private static getNextImage(): void { + const currentImageIndex: number = EditorSelector.getActiveImageIndex(); + const imageCount: number = EditorSelector.getImagesData().length; + const nextImageIndex: number = Math.min(imageCount - 1, currentImageIndex + 1); + store.dispatch(updateActiveImageIndex(nextImageIndex)); + } +} \ No newline at end of file diff --git a/src/logic/context/PopupContext.ts b/src/logic/context/PopupContext.ts new file mode 100644 index 00000000..41f9d1ac --- /dev/null +++ b/src/logic/context/PopupContext.ts @@ -0,0 +1,22 @@ +import {HotKeyAction} from "../../data/HotKeyAction"; +import {PopupWindowType} from "../../data/enums/PopupWindowType"; +import {GeneralSelector} from "../../store/selectors/GeneralSelector"; +import {store} from "../../index"; +import {updateActivePopupType} from "../../store/general/actionCreators"; +import {ContextManager} from "./ContextManager"; +import {BaseContext} from "./BaseContext"; + +export class PopupContext extends BaseContext { + public static actions: HotKeyAction[] = [ + { + keyCombo: ["Escape"], + action: (event: KeyboardEvent) => { + const popupType: PopupWindowType = GeneralSelector.getActivePopupType(); + if (popupType === PopupWindowType.LOAD_IMAGES || popupType === PopupWindowType.EXIT_PROJECT || popupType === PopupWindowType.EXPORT_LABELS) { + store.dispatch(updateActivePopupType(null)); + ContextManager.restoreContext(); + } + } + } + ]; +} \ No newline at end of file diff --git a/src/logic/export/PointLabelsExport.ts b/src/logic/export/PointLabelsExport.ts index 1acb18eb..4acd4782 100644 --- a/src/logic/export/PointLabelsExport.ts +++ b/src/logic/export/PointLabelsExport.ts @@ -1,4 +1,4 @@ -import {ExportFormatType} from "../../data/ExportFormatType"; +import {ExportFormatType} from "../../data/enums/ExportFormatType"; import {ImageData, LabelPoint} from "../../store/editor/types"; import {saveAs} from "file-saver"; import {ImageRepository} from "../imageRepository/ImageRepository"; @@ -34,7 +34,7 @@ export class PointLabelsExporter { } private static wrapRectLabelsIntoCSV(imageData: ImageData): string { - if (imageData.labelRects.length === 0 || !imageData.loadStatus) + if (imageData.labelPoints.length === 0 || !imageData.loadStatus) return null; const image: HTMLImageElement = ImageRepository.getById(imageData.id); diff --git a/src/logic/export/PolygonLabelsExporter.ts b/src/logic/export/PolygonLabelsExporter.ts index d52a8b23..23e0afe1 100644 --- a/src/logic/export/PolygonLabelsExporter.ts +++ b/src/logic/export/PolygonLabelsExporter.ts @@ -1,4 +1,4 @@ -import {ExportFormatType} from "../../data/ExportFormatType"; +import {ExportFormatType} from "../../data/enums/ExportFormatType"; import {IPoint} from "../../interfaces/IPoint"; import {VGGFileData, VGGObject, VGGPolygon, VGGRegionsData} from "../../data/VGG/IVGG"; import {ImageData, LabelPolygon} from "../../store/editor/types"; diff --git a/src/logic/export/RectLabelsExporter.ts b/src/logic/export/RectLabelsExporter.ts index 7e8a5b25..1238e617 100644 --- a/src/logic/export/RectLabelsExporter.ts +++ b/src/logic/export/RectLabelsExporter.ts @@ -1,4 +1,4 @@ -import {ExportFormatType} from "../../data/ExportFormatType"; +import {ExportFormatType} from "../../data/enums/ExportFormatType"; import {ImageData, LabelRect} from "../../store/editor/types"; import {ImageRepository} from "../imageRepository/ImageRepository"; import JSZip from 'jszip'; diff --git a/src/logic/render/BaseRenderEngine.ts b/src/logic/render/BaseRenderEngine.ts index 875cd655..5705e331 100644 --- a/src/logic/render/BaseRenderEngine.ts +++ b/src/logic/render/BaseRenderEngine.ts @@ -1,9 +1,11 @@ import {EditorData} from "../../data/EditorData"; import {MouseEventUtil} from "../../utils/MouseEventUtil"; -import {EventType} from "../../data/EventType"; +import {EventType} from "../../data/enums/EventType"; +import {LabelType} from "../../data/enums/LabelType"; export abstract class BaseRenderEngine { protected readonly canvas: HTMLCanvasElement; + public labelType: LabelType; protected constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; diff --git a/src/logic/render/PointRenderEngine.ts b/src/logic/render/PointRenderEngine.ts index a22c0bc2..5d49748d 100644 --- a/src/logic/render/PointRenderEngine.ts +++ b/src/logic/render/PointRenderEngine.ts @@ -13,13 +13,13 @@ import { } from "../../store/editor/actionCreators"; import {RectUtil} from "../../utils/RectUtil"; import {DrawUtil} from "../../utils/DrawUtil"; -import {PointUtil} from "../../utils/PointUtil"; import {updateCustomCursorStyle} from "../../store/general/actionCreators"; -import {CustomCursorStyle} from "../../data/CustomCursorStyle"; +import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; import {EditorSelector} from "../../store/selectors/EditorSelector"; import {EditorData} from "../../data/EditorData"; import {BaseRenderEngine} from "./BaseRenderEngine"; import {RenderEngineUtil} from "../../utils/RenderEngineUtil"; +import {LabelType} from "../../data/enums/LabelType"; export class PointRenderEngine extends BaseRenderEngine { private config: RenderEngineConfig = new RenderEngineConfig(); @@ -28,10 +28,11 @@ export class PointRenderEngine extends BaseRenderEngine { // STATE // ================================================================================================================= - private transformInProgress: boolean; + private transformInProgress: boolean = false; public constructor(canvas: HTMLCanvasElement) { super(canvas); + this.labelType = LabelType.POINT; } // ================================================================================================================= @@ -39,17 +40,14 @@ export class PointRenderEngine extends BaseRenderEngine { // ================================================================================================================= public mouseDownHandler(data: EditorData): void { - const isMouseOverImage: boolean = RectUtil.isPointInside(data.activeImageRectOnCanvas, - data.mousePositionOnCanvas); - const isMouseOverCanvas: boolean = RectUtil.isPointInside({x: 0, y: 0, ...data.canvasSize}, - data.mousePositionOnCanvas); + const isMouseOverImage: boolean = RenderEngineUtil.isMouseOverImage(data); + const isMouseOverCanvas: boolean = RenderEngineUtil.isMouseOverCanvas(data); if (isMouseOverCanvas) { - const labelPoint: LabelPoint = this.getLabelPointUnderMouse(data.mousePositionOnCanvas, data.activeImageRectOnCanvas, data.activeImageScale); + const labelPoint: LabelPoint = this.getLabelPointUnderMouse(data.mousePositionOnCanvas, data); if (!!labelPoint) { - const labelPointOnImage: IPoint = PointUtil.multiply(labelPoint.point, 1/data.activeImageScale); - const pointBetweenPixels = DrawUtil - .setPointBetweenPixels(PointUtil.add(labelPointOnImage, data.activeImageRectOnCanvas)); + const pointOnCanvas: IPoint = RenderEngineUtil.transferPointFromImageToCanvas(labelPoint.point, data); + const pointBetweenPixels = RenderEngineUtil.setPointBetweenPixels(pointOnCanvas); const handleRect: IRect = RectUtil.getRectWithCenterAndSize(pointBetweenPixels, this.config.anchorHoverSize); if (RectUtil.isPointInside(handleRect, data.mousePositionOnCanvas)) { store.dispatch(updateActiveLabelId(labelPoint.id)); @@ -57,31 +55,28 @@ export class PointRenderEngine extends BaseRenderEngine { return; } else { store.dispatch(updateActiveLabelId(null)); - const point: IPoint = PointUtil.multiply(PointUtil.subtract( - data.mousePositionOnCanvas, data.activeImageRectOnCanvas), data.activeImageScale); - this.addPointLabel(point); + const pointOnImage: IPoint = RenderEngineUtil.transferPointFromCanvasToImage(data.mousePositionOnCanvas, data); + this.addPointLabel(pointOnImage); } } else if (isMouseOverImage) { - const point: IPoint = PointUtil.multiply(PointUtil.subtract( - data.mousePositionOnCanvas, data.activeImageRectOnCanvas), data.activeImageScale); - this.addPointLabel(point); + const pointOnImage: IPoint = RenderEngineUtil.transferPointFromCanvasToImage(data.mousePositionOnCanvas, data); + this.addPointLabel(pointOnImage); } } } public mouseUpHandler(data: EditorData): void { - if (this.transformInProgress) { + if (this.isInProgress()) { const activeLabelPoint: LabelPoint = EditorSelector.getActivePointLabel(); - const snappedPoint: IPoint = RectUtil.snapPointToRect(data.mousePositionOnCanvas, data.activeImageRectOnCanvas); - const scaledPoint: IPoint = PointUtil.multiply(PointUtil.subtract(snappedPoint, data.activeImageRectOnCanvas), - data.activeImageScale); - + const pointSnapped: IPoint = RectUtil.snapPointToRect(data.mousePositionOnCanvas, data.activeImageRectOnCanvas); + const pointOnImage: IPoint = RenderEngineUtil.transferPointFromCanvasToImage(pointSnapped, data); const imageData = EditorSelector.getActiveImageData(); + imageData.labelPoints = imageData.labelPoints.map((labelPoint: LabelPoint) => { if (labelPoint.id === activeLabelPoint.id) { return { ...labelPoint, - point: scaledPoint + point: pointOnImage }; } return labelPoint; @@ -92,9 +87,9 @@ export class PointRenderEngine extends BaseRenderEngine { } public mouseMoveHandler(data: EditorData): void { - const isOverImage: boolean = RectUtil.isPointInside(data.activeImageRectOnCanvas, data.mousePositionOnCanvas); + const isOverImage: boolean = RenderEngineUtil.isMouseOverImage(data); if (isOverImage) { - const labelPoint: LabelPoint = this.getLabelPointUnderMouse(data.mousePositionOnCanvas, data.activeImageRectOnCanvas, data.activeImageScale); + const labelPoint: LabelPoint = this.getLabelPointUnderMouse(data.mousePositionOnCanvas, data); if (!!labelPoint) { if (EditorSelector.getHighlightedLabelId() !== labelPoint.id) { store.dispatch(updateHighlightedLabelId(labelPoint.id)) @@ -118,26 +113,25 @@ export class PointRenderEngine extends BaseRenderEngine { if (imageData) { imageData.labelPoints.forEach((labelPoint: LabelPoint) => { if (labelPoint.id === activeLabelId) { - if (this.transformInProgress) { + if (this.isInProgress()) { const pointSnapped: IPoint = RectUtil.snapPointToRect(data.mousePositionOnCanvas, data.activeImageRectOnCanvas); - const pointBetweenPixels: IPoint = DrawUtil.setPointBetweenPixels(pointSnapped); + const pointBetweenPixels: IPoint = RenderEngineUtil.setPointBetweenPixels(pointSnapped); const handleRect: IRect = RectUtil.getRectWithCenterAndSize(pointBetweenPixels, this.config.anchorSize); DrawUtil.drawRectWithFill(this.canvas, handleRect, this.config.activeAnchorColor); } else { - this.renderPoint(labelPoint, true, data.activeImageRectOnCanvas, data.activeImageScale); + this.renderPoint(labelPoint, true, data); } } else { - this.renderPoint(labelPoint, labelPoint.id === activeLabelId || labelPoint.id === highlightedLabelId, data.activeImageRectOnCanvas, data.activeImageScale); + this.renderPoint(labelPoint, labelPoint.id === activeLabelId || labelPoint.id === highlightedLabelId, data); } }); } this.updateCursorStyle(data); } - private renderPoint(labelPoint: LabelPoint, isActive: boolean, imageRect: IRect, scale: number) { - const pointOnImage: IPoint = PointUtil.multiply(labelPoint.point, 1/scale); - const pointBetweenPixels = DrawUtil - .setPointBetweenPixels(PointUtil.add(pointOnImage, imageRect)); + private renderPoint(labelPoint: LabelPoint, isActive: boolean, data: EditorData) { + const pointOnImage: IPoint = RenderEngineUtil.transferPointFromImageToCanvas(labelPoint.point, data); + const pointBetweenPixels = RenderEngineUtil.setPointBetweenPixels(pointOnImage); const handleRect: IRect = RectUtil.getRectWithCenterAndSize(pointBetweenPixels, this.config.anchorSize); const handleColor: string = isActive ? this.config.activeAnchorColor : this.config.inactiveAnchorColor; DrawUtil.drawRectWithFill(this.canvas, handleRect, handleColor); @@ -145,17 +139,16 @@ export class PointRenderEngine extends BaseRenderEngine { private updateCursorStyle(data: EditorData) { if (!!this.canvas && !!data.mousePositionOnCanvas) { - const labelPoint: LabelPoint = this.getLabelPointUnderMouse(data.mousePositionOnCanvas, data.activeImageRectOnCanvas, data.activeImageScale); + const labelPoint: LabelPoint = this.getLabelPointUnderMouse(data.mousePositionOnCanvas, data); if (!!labelPoint) { - const pointOnImage: IPoint = PointUtil.multiply(labelPoint.point, 1/data.activeImageScale); - const pointBetweenPixels = DrawUtil - .setPointBetweenPixels(PointUtil.add(pointOnImage, data.activeImageRectOnCanvas)); + const pointOnCanvas: IPoint = RenderEngineUtil.transferPointFromImageToCanvas(labelPoint.point, data); + const pointBetweenPixels = RenderEngineUtil.setPointBetweenPixels(pointOnCanvas); const handleRect: IRect = RectUtil.getRectWithCenterAndSize(pointBetweenPixels, this.config.anchorHoverSize); if (RectUtil.isPointInside(handleRect, data.mousePositionOnCanvas)) { store.dispatch(updateCustomCursorStyle(CustomCursorStyle.MOVE)); return; } - } else if (this.transformInProgress) { + } else if (this.isInProgress()) { store.dispatch(updateCustomCursorStyle(CustomCursorStyle.MOVE)); return; } @@ -174,14 +167,14 @@ export class PointRenderEngine extends BaseRenderEngine { // ================================================================================================================= public isInProgress(): boolean { - return !!this.transformInProgress; + return this.transformInProgress; } - private getLabelPointUnderMouse(mousePosition: IPoint, imageRect: IRect, scale: number): LabelPoint { + private getLabelPointUnderMouse(mousePosition: IPoint, data: EditorData): LabelPoint { const labelPoints: LabelPoint[] = EditorSelector.getActiveImageData().labelPoints; for (let i = 0; i < labelPoints.length; i++) { - const pointOnImage: IPoint = PointUtil.multiply(labelPoints[i].point, 1/scale); - const handleRect: IRect = RectUtil.getRectWithCenterAndSize(PointUtil.add(pointOnImage, imageRect), this.config.anchorHoverSize); + const pointOnCanvas: IPoint = RenderEngineUtil.transferPointFromImageToCanvas(labelPoints[i].point, data); + const handleRect: IRect = RectUtil.getRectWithCenterAndSize(pointOnCanvas, this.config.anchorHoverSize); if (RectUtil.isPointInside(handleRect, mousePosition)) { return labelPoints[i]; } diff --git a/src/logic/render/PolygonRenderEngine.ts b/src/logic/render/PolygonRenderEngine.ts index 00dd04e9..3b76571b 100644 --- a/src/logic/render/PolygonRenderEngine.ts +++ b/src/logic/render/PolygonRenderEngine.ts @@ -1,16 +1,14 @@ import {store} from "../../index"; import {RectUtil} from "../../utils/RectUtil"; import {updateCustomCursorStyle} from "../../store/general/actionCreators"; -import {CustomCursorStyle} from "../../data/CustomCursorStyle"; +import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; import {EditorData} from "../../data/EditorData"; import {BaseRenderEngine} from "./BaseRenderEngine"; import {RenderEngineConfig} from "../../settings/RenderEngineConfig"; import {IPoint} from "../../interfaces/IPoint"; -import {CanvasUtil} from "../../utils/CanvasUtil"; import {ILine} from "../../interfaces/ILine"; import {DrawUtil} from "../../utils/DrawUtil"; import {IRect} from "../../interfaces/IRect"; -import {PointUtil} from "../../utils/PointUtil"; import {ImageData, LabelPolygon} from "../../store/editor/types"; import {EditorSelector} from "../../store/selectors/EditorSelector"; import uuidv1 from 'uuid/v1'; @@ -22,8 +20,9 @@ import { } from "../../store/editor/actionCreators"; import {LineUtil} from "../../utils/LineUtil"; import {MouseEventUtil} from "../../utils/MouseEventUtil"; -import {EventType} from "../../data/EventType"; +import {EventType} from "../../data/enums/EventType"; import {RenderEngineUtil} from "../../utils/RenderEngineUtil"; +import {LabelType} from "../../data/enums/LabelType"; export class PolygonRenderEngine extends BaseRenderEngine { private config: RenderEngineConfig = new RenderEngineConfig(); @@ -39,6 +38,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { public constructor(canvas: HTMLCanvasElement) { super(canvas); + this.labelType = LabelType.POLYGON; } // ================================================================================================================= @@ -57,12 +57,6 @@ export class PolygonRenderEngine extends BaseRenderEngine { case EventType.MOUSE_DOWN: this.mouseDownHandler(data); break; - case EventType.KEY_DOWN: - if ((data.event as KeyboardEvent).key === "Escape") - this.cancelLabelCreation(); - else if ((data.event as KeyboardEvent).key === "Enter") - this.addLabelAndFinishCreation(data); - break; default: break; } @@ -70,9 +64,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { } public mouseDownHandler(data: EditorData): void { - const isMouseOverCanvas: boolean = RectUtil.isPointInside({x: 0, y: 0, ...CanvasUtil.getSize(this.canvas)}, - data.mousePositionOnCanvas); - + const isMouseOverCanvas: boolean = RenderEngineUtil.isMouseOverCanvas(data); if (isMouseOverCanvas) { if (this.isCreationInProgress()) { const isMouseOverStartAnchor: boolean = this.isMouseOverAnchor(data.mousePositionOnCanvas, this.activePath[0]); @@ -87,7 +79,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { const anchorIndex: number = polygonUnderMouse.vertices.reduce( (indexUnderMouse: number, anchor: IPoint, index: number) => { if (indexUnderMouse === null) { - const anchorOnCanvas: IPoint = this.mapPointFromImageToCanvas(anchor, data); + const anchorOnCanvas: IPoint = RenderEngineUtil.transferPointFromImageToCanvas(anchor, data); if (this.isMouseOverAnchor(data.mousePositionOnCanvas, anchorOnCanvas)) { return index; } @@ -120,14 +112,14 @@ export class PolygonRenderEngine extends BaseRenderEngine { public mouseMoveHandler(data: EditorData): void { if (!!data.activeImageRectOnCanvas && !!data.mousePositionOnCanvas) { - const isOverImage: boolean = RectUtil.isPointInside(data.activeImageRectOnCanvas, data.mousePositionOnCanvas); + const isOverImage: boolean = RenderEngineUtil.isMouseOverImage(data); if (isOverImage && !this.isCreationInProgress()) { const labelPolygon: LabelPolygon = this.getPolygonUnderMouse(data); if (!!labelPolygon && !this.isResizeInProgress()) { if (EditorSelector.getHighlightedLabelId() !== labelPolygon.id) { store.dispatch(updateHighlightedLabelId(labelPolygon.id)) } - const pathOnCanvas: IPoint[] = this.mapPolygonFromImageToCanvas(labelPolygon.vertices, data); + const pathOnCanvas: IPoint[] = RenderEngineUtil.transferPolygonFromImageToCanvas(labelPolygon.vertices, data); const linesOnCanvas: ILine[] = this.mapPointsToLines(pathOnCanvas.concat(pathOnCanvas[0])); for (let j = 0; j < linesOnCanvas.length; j++) { @@ -164,7 +156,8 @@ export class PolygonRenderEngine extends BaseRenderEngine { private updateCursorStyle(data: EditorData) { if (!!this.canvas && !!data.mousePositionOnCanvas) { - if (RectUtil.isPointInside({x: 0, y: 0, ...CanvasUtil.getSize(this.canvas)}, data.mousePositionOnCanvas)) { + const isMouseOverCanvas: boolean = RenderEngineUtil.isMouseOverCanvas(data); + if (isMouseOverCanvas) { if (this.isCreationInProgress()) { const isMouseOverStartAnchor: boolean = this.isMouseOverAnchor(data.mousePositionOnCanvas, this.activePath[0]); if (isMouseOverStartAnchor && this.activePath.length > 2) @@ -192,7 +185,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { } private drawActivelyCreatedLabel(data: EditorData) { - const standardizedPoints: IPoint[] = this.activePath.map((point: IPoint) => DrawUtil.setPointBetweenPixels(point)); + const standardizedPoints: IPoint[] = this.activePath.map((point: IPoint) => RenderEngineUtil.setPointBetweenPixels(point)); const path = standardizedPoints.concat(data.mousePositionOnCanvas); const lines: ILine[] = this.mapPointsToLines(path); @@ -210,7 +203,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { if (!!activeLabelPolygon && this.isResizeInProgress()) { const snappedMousePosition: IPoint = RectUtil.snapPointToRect(data.mousePositionOnCanvas, data.activeImageRectOnCanvas); const polygonOnCanvas: IPoint[] = activeLabelPolygon.vertices.map((point: IPoint, index: number) => { - return index === this.resizeAnchorIndex ? snappedMousePosition : this.mapPointFromImageToCanvas(point, data); + return index === this.resizeAnchorIndex ? snappedMousePosition : RenderEngineUtil.transferPointFromImageToCanvas(point, data); }); this.drawPolygon(polygonOnCanvas, true); } @@ -222,7 +215,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { const imageData: ImageData = EditorSelector.getActiveImageData(); imageData.labelPolygons.forEach((labelPolygon: LabelPolygon) => { const isActive: boolean = labelPolygon.id === activeLabelId || labelPolygon.id === highlightedLabelId; - const pathOnCanvas: IPoint[] = this.mapPolygonFromImageToCanvas(labelPolygon.vertices, data); + const pathOnCanvas: IPoint[] = RenderEngineUtil.transferPolygonFromImageToCanvas(labelPolygon.vertices, data); if (!(labelPolygon.id === activeLabelId && this.isResizeInProgress())) { this.drawPolygon(pathOnCanvas, isActive); } @@ -231,7 +224,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { private drawPolygon(polygon: IPoint[], isActive: boolean) { const color: string = isActive ? this.config.lineActiveColor : this.config.lineInactiveColor; - const standardizedPoints: IPoint[] = polygon.map((point: IPoint) => DrawUtil.setPointBetweenPixels(point)); + const standardizedPoints: IPoint[] = polygon.map((point: IPoint) => RenderEngineUtil.setPointBetweenPixels(point)); if (isActive) { DrawUtil.drawPolygonWithFill(this.canvas, standardizedPoints, DrawUtil.hexToRGB(color, 0.2)); } @@ -265,8 +258,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { const mousePositionSnapped: IPoint = RectUtil.snapPointToRect(data.mousePositionOnCanvas, data.activeImageRectOnCanvas); this.activePath.push(mousePositionSnapped); } else { - const isMouseOverImage: boolean = RectUtil.isPointInside(data.activeImageRectOnCanvas, - data.mousePositionOnCanvas); + const isMouseOverImage: boolean = RectUtil.isPointInside(data.activeImageRectOnCanvas, data.mousePositionOnCanvas); if (isMouseOverImage) { this.activePath.push(data.mousePositionOnCanvas); store.dispatch(updateActiveLabelId(null)); @@ -274,7 +266,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { } } - private cancelLabelCreation() { + public cancelLabelCreation() { this.activePath = []; } @@ -282,9 +274,9 @@ export class PolygonRenderEngine extends BaseRenderEngine { this.activePath = []; } - private addLabelAndFinishCreation(data: EditorData) { + public addLabelAndFinishCreation(data: EditorData) { if (this.isCreationInProgress() && this.activePath.length > 2) { - const polygonOnImage: IPoint[] = this.mapPolygonFromCanvasToImage(this.activePath, data); + const polygonOnImage: IPoint[] = RenderEngineUtil.transferPolygonFromCanvasToImage(this.activePath, data); this.addPolygonLabel(polygonOnImage); this.finishLabelCreation(); } @@ -333,7 +325,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { } else { const snappedMousePosition: IPoint = RectUtil.snapPointToRect(data.mousePositionOnCanvas, data.activeImageRectOnCanvas); - return this.mapPointFromCanvasToImage(snappedMousePosition, data); + return RenderEngineUtil.transferPointFromCanvasToImage(snappedMousePosition, data); } }) } @@ -356,7 +348,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { const imageData: ImageData = EditorSelector.getActiveImageData(); const activeLabel: LabelPolygon = EditorSelector.getActivePolygonLabel(); const newAnchorPositionOnImage: IPoint = - this.mapPointFromCanvasToImage(this.suggestedAnchorPositionOnCanvas, data); + RenderEngineUtil.transferPointFromCanvasToImage(this.suggestedAnchorPositionOnCanvas, data); const insert = (arr, index, newItem) => [...arr.slice(0, index), newItem, ...arr.slice(index)]; const newImageData: ImageData = { @@ -427,22 +419,6 @@ export class PolygonRenderEngine extends BaseRenderEngine { return points.map((point: IPoint) => RectUtil.getRectWithCenterAndSize(point, this.config.anchorSize)); } - private mapPolygonFromImageToCanvas(polygon: IPoint[], data: EditorData): IPoint[] { - return polygon.map((point: IPoint) => this.mapPointFromImageToCanvas(point, data)); - } - - private mapPointFromImageToCanvas(point: IPoint, data: EditorData): IPoint { - return PointUtil.add(PointUtil.multiply(point, 1/data.activeImageScale), data.activeImageRectOnCanvas); - } - - private mapPolygonFromCanvasToImage(polygon: IPoint[], data: EditorData): IPoint[] { - return polygon.map((point: IPoint) => this.mapPointFromCanvasToImage(point, data)); - } - - private mapPointFromCanvasToImage(point: IPoint, data: EditorData): IPoint { - return PointUtil.multiply(PointUtil.subtract(point, data.activeImageRectOnCanvas), data.activeImageScale); - } - // ================================================================================================================= // GETTERS // ================================================================================================================= @@ -450,7 +426,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { private getPolygonUnderMouse(data: EditorData): LabelPolygon { const labelPolygons: LabelPolygon[] = EditorSelector.getActiveImageData().labelPolygons; for (let i = 0; i < labelPolygons.length; i++) { - const pathOnCanvas: IPoint[] = this.mapPolygonFromImageToCanvas(labelPolygons[i].vertices, data); + const pathOnCanvas: IPoint[] = RenderEngineUtil.transferPolygonFromImageToCanvas(labelPolygons[i].vertices, data); const linesOnCanvas: ILine[] = this.mapPointsToLines(pathOnCanvas.concat(pathOnCanvas[0])); for (let j = 0; j < linesOnCanvas.length; j++) { @@ -468,7 +444,7 @@ export class PolygonRenderEngine extends BaseRenderEngine { private getAnchorUnderMouse(data: EditorData): IPoint { const labelPolygons: LabelPolygon[] = EditorSelector.getActiveImageData().labelPolygons; for (let i = 0; i < labelPolygons.length; i++) { - const pathOnCanvas: IPoint[] = this.mapPolygonFromImageToCanvas(labelPolygons[i].vertices, data); + const pathOnCanvas: IPoint[] = RenderEngineUtil.transferPolygonFromImageToCanvas(labelPolygons[i].vertices, data); for (let j = 0; j < pathOnCanvas.length; j ++) { if (this.isMouseOverAnchor(data.mousePositionOnCanvas, pathOnCanvas[j])) return pathOnCanvas[j]; diff --git a/src/logic/render/RectRenderEngine.ts b/src/logic/render/RectRenderEngine.ts index 7dee5659..497a4c16 100644 --- a/src/logic/render/RectRenderEngine.ts +++ b/src/logic/render/RectRenderEngine.ts @@ -14,13 +14,13 @@ import { import {PointUtil} from "../../utils/PointUtil"; import {RectAnchor} from "../../data/RectAnchor"; import {RenderEngineConfig} from "../../settings/RenderEngineConfig"; -import {CanvasUtil} from "../../utils/CanvasUtil"; import {updateCustomCursorStyle} from "../../store/general/actionCreators"; -import {CustomCursorStyle} from "../../data/CustomCursorStyle"; +import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; import {EditorSelector} from "../../store/selectors/EditorSelector"; import {EditorData} from "../../data/EditorData"; import {BaseRenderEngine} from "./BaseRenderEngine"; import {RenderEngineUtil} from "../../utils/RenderEngineUtil"; +import {LabelType} from "../../data/enums/LabelType"; export class RectRenderEngine extends BaseRenderEngine { private config: RenderEngineConfig = new RenderEngineConfig(); @@ -34,6 +34,7 @@ export class RectRenderEngine extends BaseRenderEngine { public constructor(canvas: HTMLCanvasElement) { super(canvas); + this.labelType = LabelType.RECTANGLE; } // ================================================================================================================= @@ -41,11 +42,8 @@ export class RectRenderEngine extends BaseRenderEngine { // ================================================================================================================= public mouseDownHandler = (data: EditorData) => { - const isMouseOverImage: boolean = RectUtil.isPointInside(data.activeImageRectOnCanvas, - data.mousePositionOnCanvas); - const isMouseOverCanvas: boolean = RectUtil.isPointInside({x: 0, y: 0, ...CanvasUtil.getSize(this.canvas)}, - data.mousePositionOnCanvas); - + const isMouseOverImage: boolean = RenderEngineUtil.isMouseOverImage(data); + const isMouseOverCanvas: boolean = RenderEngineUtil.isMouseOverCanvas(data); if (isMouseOverCanvas) { const rectUnderMouse: LabelRect = this.getRectUnderMouse(data.activeImageScale, data.activeImageRectOnCanvas, data.mousePositionOnCanvas); if (!!rectUnderMouse) { @@ -55,9 +53,13 @@ export class RectRenderEngine extends BaseRenderEngine { store.dispatch(updateActiveLabelId(rectUnderMouse.id)); this.startRectResize(anchorUnderMouse); } else { - this.startRectCreation(data.mousePositionOnCanvas); + if (!!EditorSelector.getHighlightedLabelId()) + store.dispatch(updateActiveLabelId(EditorSelector.getHighlightedLabelId())); + else + this.startRectCreation(data.mousePositionOnCanvas); } } else if (isMouseOverImage) { + this.startRectCreation(data.mousePositionOnCanvas); } } @@ -90,7 +92,7 @@ export class RectRenderEngine extends BaseRenderEngine { data.activeImageRectOnCanvas); const delta: IPoint = PointUtil.subtract(mousePositionSnapped, startAnchorPosition); const resizeRect: IRect = RectUtil.resizeRect(rect, this.startResizeRectAnchor.type, delta); - const scaledRect: IRect = RectRenderEngine.scaleRect(resizeRect, data.activeImageScale); + const scaledRect: IRect = RectUtil.scaleRect(resizeRect, data.activeImageScale); const imageData = EditorSelector.getActiveImageData(); imageData.labelRects = imageData.labelRects.map((labelRect: LabelRect) => { @@ -110,7 +112,7 @@ export class RectRenderEngine extends BaseRenderEngine { public mouseMoveHandler = (data: EditorData) => { if (!!data.activeImageRectOnCanvas && !!data.mousePositionOnCanvas) { - const isOverImage: boolean = RectUtil.isPointInside(data.activeImageRectOnCanvas, data.mousePositionOnCanvas); + const isOverImage: boolean = RenderEngineUtil.isMouseOverImage(data); if (isOverImage && !this.startResizeRectAnchor) { const labelRect: LabelRect = this.getRectUnderMouse(data.activeImageScale, data.activeImageRectOnCanvas, data.mousePositionOnCanvas); if (!!labelRect) { @@ -136,7 +138,7 @@ export class RectRenderEngine extends BaseRenderEngine { if (imageData) { imageData.labelRects.forEach((labelRect: LabelRect) => { - labelRect.id === activeLabelId ? this.drawActiveRect(labelRect, data.mousePositionOnCanvas, data.activeImageRectOnCanvas, data.activeImageScale) : this.drawInactiveRect(labelRect, data.activeImageScale, data.activeImageRectOnCanvas); + labelRect.id === activeLabelId ? this.drawActiveRect(labelRect, data.mousePositionOnCanvas, data.activeImageRectOnCanvas, data.activeImageScale) : this.drawInactiveRect(labelRect, data); }); this.drawCurrentlyCreatedRect(data.mousePositionOnCanvas, data.activeImageRectOnCanvas); this.updateCursorStyle(data); @@ -152,13 +154,13 @@ export class RectRenderEngine extends BaseRenderEngine { width: mousePositionSnapped.x - this.startCreateRectPoint.x, height: mousePositionSnapped.y - this.startCreateRectPoint.y }; - const activeRectBetweenPixels = DrawUtil.setRectBetweenPixels(activeRect); + const activeRectBetweenPixels = RenderEngineUtil.setRectBetweenPixels(activeRect); DrawUtil.drawRect(this.canvas, activeRectBetweenPixels, this.config.lineActiveColor, this.config.lineThickness); } } - private drawInactiveRect(labelRect: LabelRect, scale: number, imageRect: IRect) { - const rectOnImage: IRect = this.transferRectToImage(labelRect.rect, scale, imageRect); + private drawInactiveRect(labelRect: LabelRect, data: EditorData) { + const rectOnImage: IRect = RenderEngineUtil.transferRectFromCanvasToImage(labelRect.rect, data); const highlightedLabelId: string = EditorSelector.getHighlightedLabelId(); this.renderRect(rectOnImage, labelRect.id === highlightedLabelId); } @@ -176,14 +178,14 @@ export class RectRenderEngine extends BaseRenderEngine { } private renderRect(rectOnImage: IRect, isActive: boolean) { - const rectBetweenPixels = DrawUtil.setRectBetweenPixels(rectOnImage); + const rectBetweenPixels = RenderEngineUtil.setRectBetweenPixels(rectOnImage); const lineColor: string = isActive ? this.config.lineActiveColor : this.config.lineInactiveColor; DrawUtil.drawRect(this.canvas, rectBetweenPixels, lineColor, this.config.lineThickness); if (isActive) { const handleCenters: IPoint[] = RectUtil.mapRectToAnchors(rectOnImage).map((rectAnchor: RectAnchor) => rectAnchor.position); handleCenters.forEach((center: IPoint) => { const handleRect: IRect = RectUtil.getRectWithCenterAndSize(center, this.config.anchorSize); - const handleRectBetweenPixels: IRect = DrawUtil.setRectBetweenPixels(handleRect); + const handleRectBetweenPixels: IRect = RenderEngineUtil.setRectBetweenPixels(handleRect); DrawUtil.drawRectWithFill(this.canvas, handleRectBetweenPixels, this.config.activeAnchorColor); }) } @@ -196,7 +198,7 @@ export class RectRenderEngine extends BaseRenderEngine { store.dispatch(updateCustomCursorStyle(CustomCursorStyle.MOVE)); return; } - if (RectUtil.isPointInside({x: 0, y: 0, ...CanvasUtil.getSize(this.canvas)}, data.mousePositionOnCanvas)) { + if (RenderEngineUtil.isMouseOverCanvas(data)) { if (!RectUtil.isPointInside(data.activeImageRectOnCanvas, data.mousePositionOnCanvas) && !!this.startCreateRectPoint) store.dispatch(updateCustomCursorStyle(CustomCursorStyle.MOVE)); else @@ -216,17 +218,8 @@ export class RectRenderEngine extends BaseRenderEngine { return !!this.startCreateRectPoint || !!this.startResizeRectAnchor; } - private static scaleRect(inputRect:IRect, scale: number): IRect { - return { - x: inputRect.x * scale, - y: inputRect.y * scale, - width: inputRect.width * scale, - height: inputRect.height * scale - } - } - private calculateRectRelativeToActiveImage(rect: IRect, scale: number):IRect { - return RectRenderEngine.scaleRect(rect, 1/scale); + return RectUtil.scaleRect(rect, 1/scale); } private addRectLabel = (rect: IRect) => { @@ -312,13 +305,4 @@ export class RectRenderEngine extends BaseRenderEngine { this.startCreateRectPoint = null; this.startResizeRectAnchor = null; } - - private transferRectToImage(rect:IRect, scale: number, imageRect: IRect): IRect { - const scaledRect = RectRenderEngine.scaleRect(rect, 1/scale); - return { - ...scaledRect, - x: scaledRect.x + imageRect.x, - y: scaledRect.y + imageRect.y - } - } } \ No newline at end of file diff --git a/src/model/EditorModel.ts b/src/model/EditorModel.ts new file mode 100644 index 00000000..69c0c0bd --- /dev/null +++ b/src/model/EditorModel.ts @@ -0,0 +1,17 @@ +import {PrimaryEditorRenderEngine} from "../logic/render/PrimaryEditorRenderEngine"; +import {BaseRenderEngine} from "../logic/render/BaseRenderEngine"; +import {IRect} from "../interfaces/IRect"; +import {IPoint} from "../interfaces/IPoint"; + +export class EditorModel { + public static canvas: HTMLCanvasElement; + public static mousePositionIndicator: HTMLDivElement; + public static cursor: HTMLDivElement; + public static primaryRenderingEngine: PrimaryEditorRenderEngine; + public static supportRenderingEngine: BaseRenderEngine; + public static image: HTMLImageElement; + public static imageRectOnCanvas: IRect; + public static imageScale: number; // Image / Canvas + public static mousePositionOnCanvas: IPoint; + public static isLoading: boolean = false; +} \ No newline at end of file diff --git a/src/settings/_Settings.scss b/src/settings/_Settings.scss index 11b6bc17..4d8e6ecd 100644 --- a/src/settings/_Settings.scss +++ b/src/settings/_Settings.scss @@ -1,6 +1,7 @@ $darkThemeFirstColor: #171717; $darkThemeSecondColor: #282828; $darkThemeThirdColor: #4c4c4c; +$darkThemeForthColor: #1f2c33; //$primaryColor: #33d1e5; //$secondaryColor: #39f5c2; diff --git a/src/store/editor/actionCreators.ts b/src/store/editor/actionCreators.ts index 6ea08d0d..ae6f72e0 100644 --- a/src/store/editor/actionCreators.ts +++ b/src/store/editor/actionCreators.ts @@ -1,7 +1,7 @@ -import {ProjectType} from "../../data/ProjectType"; +import {ProjectType} from "../../data/enums/ProjectType"; import {EditorActionTypes, ImageData} from "./types"; import {Action} from "../Actions"; -import {LabelType} from "../../data/LabelType"; +import {LabelType} from "../../data/enums/LabelType"; export function updateProjectType(projectType: ProjectType): EditorActionTypes { return { diff --git a/src/store/editor/types.ts b/src/store/editor/types.ts index 248b4c51..dec2f4f9 100644 --- a/src/store/editor/types.ts +++ b/src/store/editor/types.ts @@ -1,7 +1,7 @@ import {IRect} from "../../interfaces/IRect"; -import {ProjectType} from "../../data/ProjectType"; +import {ProjectType} from "../../data/enums/ProjectType"; import {Action} from "../Actions"; -import {LabelType} from "../../data/LabelType"; +import {LabelType} from "../../data/enums/LabelType"; import {IPoint} from "../../interfaces/IPoint"; export type LabelRect = { diff --git a/src/store/general/actionCreators.ts b/src/store/general/actionCreators.ts index 491ad900..876961d8 100644 --- a/src/store/general/actionCreators.ts +++ b/src/store/general/actionCreators.ts @@ -1,10 +1,10 @@ import {ISize} from "../../interfaces/ISize"; import {GeneralActionTypes} from "./types"; import {Action} from "../Actions"; -import {PopupWindowType} from "../../data/PopupWindowType"; +import {PopupWindowType} from "../../data/enums/PopupWindowType"; import {MobileDeviceData} from "../../data/MobileDeviceData"; -import {CustomCursorStyle} from "../../data/CustomCursorStyle"; -import {Context} from "../../data/Context"; +import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; +import {ContextType} from "../../data/enums/ContextType"; export function updateWindowSize(windowSize: ISize): GeneralActionTypes { return { @@ -42,7 +42,7 @@ export function updateCustomCursorStyle(customCursorStyle: CustomCursorStyle): G } } -export function updateActiveContext(activeContext: Context): GeneralActionTypes { +export function updateActiveContext(activeContext: ContextType): GeneralActionTypes { return { type: Action.UPDATE_CONTEXT, payload: { diff --git a/src/store/general/reducer.ts b/src/store/general/reducer.ts index c3de1689..3d8e1705 100644 --- a/src/store/general/reducer.ts +++ b/src/store/general/reducer.ts @@ -1,6 +1,6 @@ import {GeneralActionTypes, GeneralState} from "./types"; import {Action} from "../Actions"; -import {CustomCursorStyle} from "../../data/CustomCursorStyle"; +import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; const initialState: GeneralState = { windowSize: null, diff --git a/src/store/general/types.ts b/src/store/general/types.ts index d4ca715c..59c6b29b 100644 --- a/src/store/general/types.ts +++ b/src/store/general/types.ts @@ -1,16 +1,16 @@ import {ISize} from "../../interfaces/ISize"; import {Action} from "../Actions"; -import {PopupWindowType} from "../../data/PopupWindowType"; +import {PopupWindowType} from "../../data/enums/PopupWindowType"; import {MobileDeviceData} from "../../data/MobileDeviceData"; -import {CustomCursorStyle} from "../../data/CustomCursorStyle"; -import {Context} from "../../data/Context"; +import {CustomCursorStyle} from "../../data/enums/CustomCursorStyle"; +import {ContextType} from "../../data/enums/ContextType"; export type GeneralState = { windowSize: ISize; mobileDeviceData: MobileDeviceData; activePopupType: PopupWindowType; customCursorStyle: CustomCursorStyle; - activeContext: Context; + activeContext: ContextType; } interface UpdateWindowSize { @@ -44,7 +44,7 @@ interface UpdateCustomCursorStyle { interface UpdateActiveContext { type: typeof Action.UPDATE_CONTEXT; payload: { - activeContext: Context; + activeContext: ContextType; } } diff --git a/src/store/selectors/GeneralSelector.ts b/src/store/selectors/GeneralSelector.ts new file mode 100644 index 00000000..d8ce27bd --- /dev/null +++ b/src/store/selectors/GeneralSelector.ts @@ -0,0 +1,8 @@ +import {store} from "../.."; +import {PopupWindowType} from "../../data/enums/PopupWindowType"; + +export class GeneralSelector { + public static getActivePopupType(): PopupWindowType { + return store.getState().general.activePopupType; + } +} \ No newline at end of file diff --git a/src/utils/DrawUtil.ts b/src/utils/DrawUtil.ts index a1e95914..3ccaf9fb 100644 --- a/src/utils/DrawUtil.ts +++ b/src/utils/DrawUtil.ts @@ -123,35 +123,7 @@ export class DrawUtil { return "rgb(" + Math.round(Math.random() * 255) + "," + Math.round(Math.random() * 255) + "," + Math.round(Math.random() * 255) + ")"; } - public static setValueBetweenPixels(value: number): number { - return Math.floor(value) + 0.5; - } - - public static setPointBetweenPixels(point: IPoint): IPoint { - return { - x: DrawUtil.setValueBetweenPixels(point.x), - y: DrawUtil.setValueBetweenPixels(point.y) - } - } - public static setRectBetweenPixels(rect: IRect): IRect { - const topLeft: IPoint = { - x: rect.x, - y: rect.y - }; - const bottomRight: IPoint = { - x: rect.x + rect.width, - y: rect.y + rect.height - }; - const topLeftBetweenPixels = DrawUtil.setPointBetweenPixels(topLeft); - const bottomRightBetweenPixels = DrawUtil.setPointBetweenPixels(bottomRight); - return { - x: topLeftBetweenPixels.x, - y: topLeftBetweenPixels.y, - width: bottomRightBetweenPixels.x - topLeftBetweenPixels.x, - height: bottomRightBetweenPixels.y - topLeftBetweenPixels.y - } - } public static hexToRGB(hex: string, alpha: number): string { const r = parseInt(hex.slice(1, 3), 16); diff --git a/src/utils/EditorUtil.ts b/src/utils/EditorUtil.ts new file mode 100644 index 00000000..5746d4e7 --- /dev/null +++ b/src/utils/EditorUtil.ts @@ -0,0 +1,33 @@ +import {CustomCursorStyle} from "../data/enums/CustomCursorStyle"; +import classNames from "classnames"; + +export class EditorUtil { + public static getIndicator = (cursorStyle: CustomCursorStyle): string => { + switch (cursorStyle) { + case CustomCursorStyle.ADD: + return "ico/plus.png"; + case CustomCursorStyle.RESIZE: + return "ico/resize.png"; + case CustomCursorStyle.CLOSE: + return "ico/close.png"; + case CustomCursorStyle.MOVE: + return "ico/move.png"; + case CustomCursorStyle.CANCEL: + return "ico/cancel.png"; + default: + return null; + } + }; + + public static getCursorStyle = (cursorStyle: CustomCursorStyle) => { + return classNames( + "Cursor", { + "move": cursorStyle === CustomCursorStyle.MOVE, + "add": cursorStyle === CustomCursorStyle.ADD, + "resize": cursorStyle === CustomCursorStyle.RESIZE, + "close": cursorStyle === CustomCursorStyle.CLOSE, + "cancel": cursorStyle === CustomCursorStyle.CANCEL, + } + ); + }; +} \ No newline at end of file diff --git a/src/utils/MouseEventUtil.ts b/src/utils/MouseEventUtil.ts index a25b6e9c..1e20173a 100644 --- a/src/utils/MouseEventUtil.ts +++ b/src/utils/MouseEventUtil.ts @@ -1,4 +1,4 @@ -import {EventType} from "../data/EventType"; +import {EventType} from "../data/enums/EventType"; export class MouseEventUtil { public static getEventType(event: Event): EventType | null { @@ -11,10 +11,6 @@ export class MouseEventUtil { return EventType.MOUSE_UP; case EventType.MOUSE_MOVE: return EventType.MOUSE_MOVE; - case EventType.KEY_DOWN: - return EventType.KEY_DOWN; - case EventType.KEY_UP: - return EventType.KEY_UP; default: return null; } diff --git a/src/utils/RectUtil.ts b/src/utils/RectUtil.ts index e26e4ab1..af034209 100644 --- a/src/utils/RectUtil.ts +++ b/src/utils/RectUtil.ts @@ -1,9 +1,9 @@ import {IRect} from "../interfaces/IRect"; import {IPoint} from "../interfaces/IPoint"; import {ISize} from "../interfaces/ISize"; -import {AnchorType} from "../data/AnchorType"; import {RectAnchor} from "../data/RectAnchor"; import {NumberUtil} from "./NumberUtil"; +import {Direction} from "../data/enums/Direction"; export class RectUtil { public static getRatio(rect: IRect): number { @@ -13,6 +13,7 @@ export class RectUtil { } public static intersect(r1: IRect, r2: IRect) { + if (!r1 || !r2) return null; return !( r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || @@ -22,6 +23,7 @@ export class RectUtil { } public static isPointInside(rect: IRect, point: IPoint): boolean { + if (!rect || !point) return null; return ( rect.x < point.x && rect.x + rect.width > point.x && @@ -60,39 +62,39 @@ export class RectUtil { } } - public static resizeRect(inputRect: IRect, rectAnchor: AnchorType, delta): IRect { + public static resizeRect(inputRect: IRect, rectAnchor: Direction, delta): IRect { const rect: IRect = {...inputRect}; switch (rectAnchor) { - case AnchorType.RIGHT: + case Direction.RIGHT: rect.width += delta.x; break; - case AnchorType.BOTTOM_RIGHT: + case Direction.BOTTOM_RIGHT: rect.width += delta.x; rect.height += delta.y; break; - case AnchorType.BOTTOM: + case Direction.BOTTOM: rect.height += delta.y; break; - case AnchorType.TOP_RIGHT: + case Direction.TOP_RIGHT: rect.width += delta.x; rect.y += delta.y; rect.height -= delta.y; break; - case AnchorType.TOP: + case Direction.TOP: rect.y += delta.y; rect.height -= delta.y; break; - case AnchorType.TOP_LEFT: + case Direction.TOP_LEFT: rect.x += delta.x; rect.width -= delta.x; rect.y += delta.y; rect.height -= delta.y; break; - case AnchorType.LEFT: + case Direction.LEFT: rect.x += delta.x; rect.width -= delta.x; break; - case AnchorType.BOTTOM_LEFT: + case Direction.BOTTOM_LEFT: rect.x += delta.x; rect.width -= delta.x; rect.height += delta.y; @@ -129,16 +131,25 @@ export class RectUtil { } } + public static scaleRect(rect:IRect, scale: number): IRect { + return { + x: rect.x * scale, + y: rect.y * scale, + width: rect.width * scale, + height: rect.height * scale + } + } + public static mapRectToAnchors(rect: IRect): RectAnchor[] { return [ - {type: AnchorType.TOP_LEFT, position: {x: rect.x, y: rect.y}}, - {type: AnchorType.TOP, position: {x: rect.x + 0.5 * rect.width, y: rect.y}}, - {type: AnchorType.TOP_RIGHT, position: {x: rect.x + rect.width, y: rect.y}}, - {type: AnchorType.LEFT, position: {x: rect.x, y: rect.y + 0.5 * rect.height}}, - {type: AnchorType.RIGHT, position: {x: rect.x + rect.width, y: rect.y + 0.5 * rect.height}}, - {type: AnchorType.BOTTOM_LEFT, position: {x: rect.x, y: rect.y + rect.height}}, - {type: AnchorType.BOTTOM, position: {x: rect.x + 0.5 * rect.width, y: rect.y + rect.height}}, - {type: AnchorType.BOTTOM_RIGHT, position: {x: rect.x + rect.width, y: rect.y + rect.height}} + {type: Direction.TOP_LEFT, position: {x: rect.x, y: rect.y}}, + {type: Direction.TOP, position: {x: rect.x + 0.5 * rect.width, y: rect.y}}, + {type: Direction.TOP_RIGHT, position: {x: rect.x + rect.width, y: rect.y}}, + {type: Direction.LEFT, position: {x: rect.x, y: rect.y + 0.5 * rect.height}}, + {type: Direction.RIGHT, position: {x: rect.x + rect.width, y: rect.y + 0.5 * rect.height}}, + {type: Direction.BOTTOM_LEFT, position: {x: rect.x, y: rect.y + rect.height}}, + {type: Direction.BOTTOM, position: {x: rect.x + 0.5 * rect.width, y: rect.y + rect.height}}, + {type: Direction.BOTTOM_RIGHT, position: {x: rect.x + rect.width, y: rect.y + rect.height}} ] } diff --git a/src/utils/RenderEngineUtil.ts b/src/utils/RenderEngineUtil.ts index 4fc0b8b4..a24cd4e1 100644 --- a/src/utils/RenderEngineUtil.ts +++ b/src/utils/RenderEngineUtil.ts @@ -1,10 +1,42 @@ import {EditorData} from "../data/EditorData"; import {RectUtil} from "./RectUtil"; import {store} from "../index"; -import {CustomCursorStyle} from "../data/CustomCursorStyle"; +import {CustomCursorStyle} from "../data/enums/CustomCursorStyle"; import {updateCustomCursorStyle} from "../store/general/actionCreators"; +import {IPoint} from "../interfaces/IPoint"; +import {PointUtil} from "./PointUtil"; +import {IRect} from "../interfaces/IRect"; export class RenderEngineUtil { + + public static isMouseOverImage(data: EditorData): boolean { + return RectUtil.isPointInside(data.activeImageRectOnCanvas, data.mousePositionOnCanvas); + } + + public static isMouseOverCanvas(data: EditorData): boolean { + return RectUtil.isPointInside({x: 0, y: 0, ...data.canvasSize}, data.mousePositionOnCanvas); + } + + public static transferPolygonFromImageToCanvas(polygon: IPoint[], data: EditorData): IPoint[] { + return polygon.map((point: IPoint) => RenderEngineUtil.transferPointFromImageToCanvas(point, data)); + } + + public static transferPointFromImageToCanvas(point: IPoint, data: EditorData): IPoint { + return PointUtil.add(PointUtil.multiply(point, 1/data.activeImageScale), data.activeImageRectOnCanvas); + } + + public static transferPolygonFromCanvasToImage(polygon: IPoint[], data: EditorData): IPoint[] { + return polygon.map((point: IPoint) => RenderEngineUtil.transferPointFromCanvasToImage(point, data)); + } + + public static transferPointFromCanvasToImage(point: IPoint, data: EditorData): IPoint { + return PointUtil.multiply(PointUtil.subtract(point, data.activeImageRectOnCanvas), data.activeImageScale); + } + + public static transferRectFromCanvasToImage(rect: IRect, data: EditorData): IRect { + return RectUtil.translate(RectUtil.scaleRect(rect, 1/data.activeImageScale), data.activeImageRectOnCanvas); + } + public static wrapDefaultCursorStyleInCancel(data: EditorData) { if (RectUtil.isPointInside(data.activeImageRectOnCanvas, data.mousePositionOnCanvas)) { store.dispatch(updateCustomCursorStyle(CustomCursorStyle.DEFAULT)); @@ -12,4 +44,34 @@ export class RenderEngineUtil { store.dispatch(updateCustomCursorStyle(CustomCursorStyle.CANCEL)); } } + + public static setValueBetweenPixels(value: number): number { + return Math.floor(value) + 0.5; + } + + public static setPointBetweenPixels(point: IPoint): IPoint { + return { + x: RenderEngineUtil.setValueBetweenPixels(point.x), + y: RenderEngineUtil.setValueBetweenPixels(point.y) + } + } + + public static setRectBetweenPixels(rect: IRect): IRect { + const topLeft: IPoint = { + x: rect.x, + y: rect.y + }; + const bottomRight: IPoint = { + x: rect.x + rect.width, + y: rect.y + rect.height + }; + const topLeftBetweenPixels = RenderEngineUtil.setPointBetweenPixels(topLeft); + const bottomRightBetweenPixels = RenderEngineUtil.setPointBetweenPixels(bottomRight); + return { + x: topLeftBetweenPixels.x, + y: topLeftBetweenPixels.y, + width: bottomRightBetweenPixels.x - topLeftBetweenPixels.x, + height: bottomRightBetweenPixels.y - topLeftBetweenPixels.y + } + } } \ No newline at end of file diff --git a/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.scss b/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.scss index a32544fb..384d9565 100644 --- a/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.scss +++ b/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.scss @@ -14,6 +14,10 @@ align-items: center; align-content: center; + &.with-context { + background-color: $darkThemeForthColor; + } + .ImageButton { transition: transform 0.3s; diff --git a/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.tsx b/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.tsx index 27acc6b9..8b3aea34 100644 --- a/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.tsx +++ b/src/views/EditorView/BottomNavigationBar/BottomNavigationBar.tsx @@ -6,16 +6,19 @@ import {AppState} from "../../../store"; import {connect} from "react-redux"; import {ImageButton} from "../../Common/ImageButton/ImageButton"; import {ISize} from "../../../interfaces/ISize"; +import {ContextType} from "../../../data/enums/ContextType"; +import classNames from "classnames"; interface IProps { size: ISize; imageData: ImageData; totalImageCount: number; activeImageIndex: number; + activeContext: ContextType; updateActiveImageIndex: (activeImageIndex: number) => any; } -const BottomNavigationBar: React.FC = ({size, imageData, totalImageCount, activeImageIndex, updateActiveImageIndex}) => { +const BottomNavigationBar: React.FC = ({size, imageData, totalImageCount, activeImageIndex, activeContext, updateActiveImageIndex}) => { const minWidth:number = 400; const viewPreviousImage = () => { if (activeImageIndex > 0) { @@ -33,8 +36,17 @@ const BottomNavigationBar: React.FC = ({size, imageData, totalImageCount return (activeImageIndex + 1) + " / " + totalImageCount; }; + const getClassName = () => { + return classNames( + "BottomNavigationBar", + { + "with-context": activeContext === ContextType.EDITOR + } + ); + }; + return ( -
+
({ - activeImageIndex: state.editor.activeImageIndex + activeImageIndex: state.editor.activeImageIndex, + activeContext: state.general.activeContext }); export default connect( diff --git a/src/views/EditorView/Editor/Editor.tsx b/src/views/EditorView/Editor/Editor.tsx index 20e19537..d25086e0 100644 --- a/src/views/EditorView/Editor/Editor.tsx +++ b/src/views/EditorView/Editor/Editor.tsx @@ -6,27 +6,19 @@ import {FileUtil} from "../../../utils/FileUtil"; import {AppState} from "../../../store"; import {connect} from "react-redux"; import {updateImageDataById} from "../../../store/editor/actionCreators"; -import {IRect} from "../../../interfaces/IRect"; import {ImageRepository} from "../../../logic/imageRepository/ImageRepository"; -import {PrimaryEditorRenderEngine} from "../../../logic/render/PrimaryEditorRenderEngine"; -import {LabelType} from "../../../data/LabelType"; -import {RectRenderEngine} from "../../../logic/render/RectRenderEngine"; -import {RectUtil} from "../../../utils/RectUtil"; -import {Settings} from "../../../settings/Settings"; -import {DrawUtil} from "../../../utils/DrawUtil"; -import {IPoint} from "../../../interfaces/IPoint"; -import {PopupWindowType} from "../../../data/PopupWindowType"; +import {LabelType} from "../../../data/enums/LabelType"; +import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {CanvasUtil} from "../../../utils/CanvasUtil"; -import {PointRenderEngine} from "../../../logic/render/PointRenderEngine"; -import {CustomCursorStyle} from "../../../data/CustomCursorStyle"; -import classNames from "classnames"; -import {PolygonRenderEngine} from "../../../logic/render/PolygonRenderEngine"; +import {CustomCursorStyle} from "../../../data/enums/CustomCursorStyle"; import {ImageLoadManager} from "../../../logic/imageRepository/ImageLoadManager"; -import {EventType} from "../../../data/EventType"; +import {EventType} from "../../../data/enums/EventType"; import {EditorData} from "../../../data/EditorData"; -import {BaseRenderEngine} from "../../../logic/render/BaseRenderEngine"; +import {EditorModel} from "../../../model/EditorModel"; +import {EditorActions} from "../../../logic/actions/EditorActions"; +import {EditorUtil} from "../../../utils/EditorUtil"; import {ContextManager} from "../../../logic/context/ContextManager"; -import {Context} from "../../../data/Context"; +import {ContextType} from "../../../data/enums/ContextType"; interface IProps { size: ISize; @@ -38,79 +30,50 @@ interface IProps { customCursorStyle: CustomCursorStyle; } -interface IState { - image: HTMLImageElement; -} - -class Editor extends React.Component { - private canvas: HTMLCanvasElement; - private mousePositionIndicator: HTMLDivElement; - private cursor: HTMLDivElement; - private primaryRenderingEngine: PrimaryEditorRenderEngine; - private supportRenderingEngine: BaseRenderEngine; - private imageRectOnCanvas: IRect; - private mousePositionOnCanvas: IPoint; - private isLoading: boolean = false; - - constructor(props) { - super(props); - this.state = { image: null } - } +class Editor extends React.Component { // ================================================================================================================= // LIFE CYCLE // ================================================================================================================= public componentDidMount(): void { - window.addEventListener(EventType.MOUSE_MOVE, this.update); - window.addEventListener(EventType.MOUSE_UP, this.update); - this.canvas.addEventListener(EventType.MOUSE_DOWN, this.onMouseDown); + this.mountEventListeners(); - const {imageData, size ,activeLabelType} = this.props; + const {imageData, activeLabelType} = this.props; + ContextManager.switchCtx(ContextType.EDITOR); + EditorActions.mountRenderEngines(activeLabelType); ImageLoadManager.addAndRun(this.loadImage(imageData)); - - this.resizeCanvas(size); - this.primaryRenderingEngine = new PrimaryEditorRenderEngine(this.canvas); - this.mountSupportRenderingEngine(activeLabelType); - this.fullCanvasRender(); } public componentWillUnmount(): void { - window.removeEventListener(EventType.MOUSE_MOVE, this.update); - window.removeEventListener(EventType.MOUSE_UP, this.update); - this.canvas.removeEventListener(EventType.MOUSE_DOWN, this.onMouseDown); + this.unmountEventListeners(); } - public componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { - if (prevProps.imageData.id !== this.props.imageData.id) { - ImageLoadManager.addAndRun(this.loadImage(this.props.imageData)); - } - if (prevProps.activeLabelType !== this.props.activeLabelType) { - this.swapSupportRenderingEngine(this.props.activeLabelType) - } - this.resizeCanvas(this.props.size); - this.calculateImageRect(this.state.image); - this.fullCanvasRender(); + public componentDidUpdate(prevProps: Readonly, prevState: Readonly<{}>, snapshot?: any): void { + const {imageData, activeLabelType} = this.props; + + prevProps.imageData.id !== imageData.id && ImageLoadManager.addAndRun(this.loadImage(imageData)); + prevProps.activeLabelType !== activeLabelType && EditorActions.swapSupportRenderingEngine(activeLabelType); + + this.updateModelAndRender() } // ================================================================================================================= // EVENT HANDLERS // ================================================================================================================= - private update = (event: MouseEvent) => { - const editorData: EditorData = this.buildEditorData(event); - this.mousePositionOnCanvas = CanvasUtil.getMousePositionOnCanvasFromEvent(event, this.canvas); - this.primaryRenderingEngine.update(editorData); - this.supportRenderingEngine && this.supportRenderingEngine.update(editorData); - !this.props.activePopupType && this.updateMousePositionIndicator(event); - this.fullCanvasRender(); - }; + private mountEventListeners() { + window.addEventListener(EventType.MOUSE_MOVE, this.update); + window.addEventListener(EventType.MOUSE_UP, this.update); + EditorModel.canvas.addEventListener(EventType.MOUSE_DOWN, this.update); + } - private onMouseDown = (event: MouseEvent) => { - this.register(); - this.update(event); - }; + private unmountEventListeners() { + window.removeEventListener(EventType.MOUSE_MOVE, this.update); + window.removeEventListener(EventType.MOUSE_UP, this.update); + EditorModel.canvas.removeEventListener(EventType.MOUSE_DOWN, this.update); + } // ================================================================================================================= // LOAD IMAGE @@ -118,11 +81,12 @@ class Editor extends React.Component { private loadImage = async (imageData: ImageData): Promise => { if (imageData.loadStatus) { - this.setState({image: ImageRepository.getById(imageData.id)}) + EditorActions.setActiveImage(ImageRepository.getById(imageData.id)); + this.updateModelAndRender() } else { - if (!this.isLoading) { - this.isLoading = true; + if (!EditorModel.isLoading) { + EditorActions.setLoadingStatus(true); const saveLoadedImagePartial = (image: HTMLImageElement) => this.saveLoadedImage(image, imageData); FileUtil.loadImage(imageData.fileData, saveLoadedImagePartial, this.handleLoadImageError); } @@ -133,211 +97,55 @@ class Editor extends React.Component { imageData.loadStatus = true; this.props.updateImageDataById(imageData.id, imageData); ImageRepository.store(imageData.id, image); - this.setState({image}); - this.isLoading = false; + EditorActions.setActiveImage(image); + EditorActions.setLoadingStatus(false); + this.updateModelAndRender() }; private handleLoadImageError = () => {}; - // ================================================================================================================= - // RENDERING - // ================================================================================================================= - - private fullCanvasRender() { - DrawUtil.clearCanvas(this.canvas); - this.primaryRenderingEngine.drawImage(this.state.image, this.imageRectOnCanvas); - if (!this.props.activePopupType) { - this.primaryRenderingEngine.render(this.buildEditorData()); - this.supportRenderingEngine && this.supportRenderingEngine.render(this.buildEditorData()); - } - } - - private updateMousePositionIndicator = (event: React.MouseEvent | MouseEvent) => { - const image = this.state.image; - - if (!image || !this.imageRectOnCanvas || !this.canvas) { - this.mousePositionIndicator.style.display = "none"; - this.cursor.style.display = "none"; - return; - } - - const mousePositionOnCanvas: IPoint = CanvasUtil.getMousePositionOnCanvasFromEvent(event, this.canvas); - const canvasRect: IRect = {x: 0, y: 0, ...CanvasUtil.getSize(this.canvas)}; - const isOverCanvas: boolean = RectUtil.isPointInside(canvasRect, mousePositionOnCanvas); - - if (!isOverCanvas) { - this.mousePositionIndicator.style.display = "none"; - this.cursor.style.display = "none"; - return; - } - - const isOverImage: boolean = RectUtil.isPointInside(this.imageRectOnCanvas, mousePositionOnCanvas); - - if (isOverImage) { - const scale = image.width / this.imageRectOnCanvas.width; - const x: number = Math.round((mousePositionOnCanvas.x - this.imageRectOnCanvas.x) * scale); - const y: number = Math.round((mousePositionOnCanvas.y - this.imageRectOnCanvas.y) * scale); - const text: string = "x: " + x + ", y: " + y; - - this.mousePositionIndicator.innerHTML = text; - this.mousePositionIndicator.style.left = (mousePositionOnCanvas.x + 15) + "px"; - this.mousePositionIndicator.style.top = (mousePositionOnCanvas.y + 15) + "px"; - this.mousePositionIndicator.style.display = "block"; - } else { - this.mousePositionIndicator.style.display = "none"; - } - - this.cursor.style.left = mousePositionOnCanvas.x + "px"; - this.cursor.style.top = mousePositionOnCanvas.y + "px"; - this.cursor.style.display = "block"; - }; - - // ================================================================================================================= - // RENDERING ENGINES - // ================================================================================================================= - - private swapSupportRenderingEngine = (activeLabelType: LabelType) => { - this.mountSupportRenderingEngine(activeLabelType); - }; - - private mountSupportRenderingEngine = (activeLabelType: LabelType) => { - switch (activeLabelType) { - case LabelType.RECTANGLE: - this.supportRenderingEngine = new RectRenderEngine(this.canvas); - break; - case LabelType.POINT: - this.supportRenderingEngine = new PointRenderEngine(this.canvas); - break; - case LabelType.POLYGON: - this.supportRenderingEngine = new PolygonRenderEngine(this.canvas); - break; - default: - this.supportRenderingEngine = null; - break; - } - }; - - // ================================================================================================================= - // CONTEXT - // ================================================================================================================= - - private register(): void { - const triggerAction = (event: KeyboardEvent) => { - const editorData: EditorData = this.buildEditorData(event); - this.primaryRenderingEngine.update(editorData); - this.supportRenderingEngine && this.supportRenderingEngine.update(editorData); - this.fullCanvasRender(); - }; - - ContextManager.switchCtx(Context.EDITOR, [ - { - keyCombo: ["Enter"], - action: triggerAction - }, - { - keyCombo: ["Escape"], - action: triggerAction - } - ]) - } - // ================================================================================================================= // HELPER METHODS // ================================================================================================================= - private buildEditorData(event?: Event): EditorData { - return { - mousePositionOnCanvas: this.mousePositionOnCanvas, - canvasSize: CanvasUtil.getSize(this.canvas), - activeImageScale: this.getImageScale(), - activeImageRectOnCanvas: this.imageRectOnCanvas, - event: event - } - } - - protected getImageScale(): number | null { - if (!this.state.image || !this.imageRectOnCanvas) - return null; - - return this.state.image.width / this.imageRectOnCanvas.width; - } - - private resizeCanvas = (newCanvasSize: ISize) => { - if (!!newCanvasSize && !!this.canvas) { - this.canvas.width = newCanvasSize.width; - this.canvas.height = newCanvasSize.height; - } + private updateModelAndRender = () => { + EditorActions.resizeCanvas(this.props.size); + EditorActions.calculateActiveImageCharacteristics(); + EditorActions.fullRender(); }; - private calculateImageRect = (image: HTMLImageElement) => { - if (!!image) { - const canvasPaddingWidth: number = Settings.CANVAS_PADDING_WIDTH_PX; - const imageRect: IRect = { x: 0, y: 0, width: image.width, height: image.height}; - const canvasRect: IRect = { - x: canvasPaddingWidth, - y: canvasPaddingWidth, - width: this.canvas.width - 2 * canvasPaddingWidth, - height: this.canvas.height - 2 * canvasPaddingWidth - }; - const imageRatio = RectUtil.getRatio(imageRect); - const imageRectOnCanvas = RectUtil.fitInsideRectWithRatio(canvasRect, imageRatio); - this.imageRectOnCanvas = imageRectOnCanvas; - } - }; - - private getCursorStyle = () => { - const cursorStyle = this.props.customCursorStyle; - return classNames( - "Cursor", { - "move": cursorStyle === CustomCursorStyle.MOVE, - "add": cursorStyle === CustomCursorStyle.ADD, - "resize": cursorStyle === CustomCursorStyle.RESIZE, - "close": cursorStyle === CustomCursorStyle.CLOSE, - "cancel": cursorStyle === CustomCursorStyle.CANCEL, - } - ); + private update = (event: MouseEvent) => { + const editorData: EditorData = EditorActions.getEditorData(event); + EditorModel.mousePositionOnCanvas = CanvasUtil.getMousePositionOnCanvasFromEvent(event, EditorModel.canvas); + EditorModel.primaryRenderingEngine.update(editorData); + EditorModel.supportRenderingEngine && EditorModel.supportRenderingEngine.update(editorData); + !this.props.activePopupType && EditorActions.updateMousePositionIndicator(event); + EditorActions.fullRender(); }; - private getIndicator = (): string => { - switch (this.props.customCursorStyle) { - case CustomCursorStyle.ADD: - return "ico/plus.png"; - case CustomCursorStyle.RESIZE: - return "ico/resize.png"; - case CustomCursorStyle.CLOSE: - return "ico/close.png"; - case CustomCursorStyle.MOVE: - return "ico/move.png"; - case CustomCursorStyle.CANCEL: - return "ico/cancel.png"; - default: - return null; - } - } - public render() { return (
this.canvas = ref} + ref={ref => EditorModel.canvas = ref} draggable={false} onContextMenu={(event: React.MouseEvent) => event.preventDefault()} />
this.mousePositionIndicator = ref} + ref={ref => EditorModel.mousePositionIndicator = ref} draggable={false} />
this.cursor = ref} + className={EditorUtil.getCursorStyle(this.props.customCursorStyle)} + ref={ref => EditorModel.cursor = ref} draggable={false} > {"indicator"}
diff --git a/src/views/EditorView/EditorContainer/EditorContainer.scss b/src/views/EditorView/EditorContainer/EditorContainer.scss index 62bd887c..8ffbe61d 100644 --- a/src/views/EditorView/EditorContainer/EditorContainer.scss +++ b/src/views/EditorView/EditorContainer/EditorContainer.scss @@ -1,6 +1,9 @@ +@import '../../../settings/Settings'; + .EditorContainer { align-self: stretch; - flex: 1; + flex: 0 0 calc(100% - #{$topNavigationBarHeight}); + height: calc(100% - #{$topNavigationBarHeight}); display: flex; flex-direction: row; diff --git a/src/views/EditorView/EditorContainer/EditorContainer.tsx b/src/views/EditorView/EditorContainer/EditorContainer.tsx index 04979f05..2d04b36e 100644 --- a/src/views/EditorView/EditorContainer/EditorContainer.tsx +++ b/src/views/EditorView/EditorContainer/EditorContainer.tsx @@ -1,6 +1,6 @@ import React, {useState} from 'react'; import {connect} from "react-redux"; -import {Direction} from "../../../data/Direction"; +import {Direction} from "../../../data/enums/Direction"; import {ISize} from "../../../interfaces/ISize"; import {Settings} from "../../../settings/Settings"; import {AppState} from "../../../store"; @@ -12,6 +12,8 @@ import {VerticalEditorButton} from "../VerticalEditorButton/VerticalEditorButton import './EditorContainer.scss'; import Editor from "../Editor/Editor"; import BottomNavigationBar from "../BottomNavigationBar/BottomNavigationBar"; +import {ContextManager} from "../../../logic/context/ContextManager"; +import {ContextType} from "../../../data/enums/ContextType"; interface IProps { windowSize: ISize; @@ -76,7 +78,9 @@ const EditorContainer: React.FC = ({windowSize, activeImageIndex, images renderCompanion={leftSideBarCompanionRender} renderContent={leftSideBarRender} /> -
+
ContextManager.switchCtx(ContextType.EDITOR)} + > { private dropdownOptionHeight: number = 30; + private dropdownOptionCount: number = 6; + private dropdownMargin: number = 4; private dropdownLabel: HTMLDivElement; private dropdown: HTMLDivElement; @@ -82,12 +84,17 @@ class LabelInputField extends React.Component { private getDropdownStyle = ():React.CSSProperties => { const clientRect = this.dropdownLabel.getBoundingClientRect(); - return { + const height: number = Math.min(this.props.options.length, this.dropdownOptionCount) * this.dropdownOptionHeight; + const style = { width: clientRect.width, - height: Math.min(this.props.options.length, 6) * this.dropdownOptionHeight, - top: clientRect.top + clientRect.height + 4, + height: height, left: clientRect.left - } + }; + + if (window.innerHeight * 2/3 < clientRect.top) + return Object.assign(style, {top: clientRect.top - this.dropdownMargin - height}); + else + return Object.assign(style, {top: clientRect.bottom + this.dropdownMargin}); }; private getDropdownOptions = () => { diff --git a/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.tsx b/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.tsx index 75405eb0..922875ed 100644 --- a/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.tsx +++ b/src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.tsx @@ -4,12 +4,12 @@ import {ImageData} from "../../../../store/editor/types"; import {updateActiveLabelId, updateActiveLabelType, updateImageDataById} from "../../../../store/editor/actionCreators"; import {AppState} from "../../../../store"; import {connect} from "react-redux"; -import {LabelType} from "../../../../data/LabelType"; -import {ProjectType} from "../../../../data/ProjectType"; +import {LabelType} from "../../../../data/enums/LabelType"; +import {ProjectType} from "../../../../data/enums/ProjectType"; import {ISize} from "../../../../interfaces/ISize"; import classNames from "classnames"; import * as _ from "lodash"; -import {ILabelToolkit, LabelToolkitData} from "../../../../data/LabelToolkitData"; +import {ILabelToolkit, LabelToolkitData} from "../../../../data/info/LabelToolkitData"; import {Settings} from "../../../../settings/Settings"; import RectLabelsList from "../RectLabelsList/RectLabelsList"; import PointLabelsList from "../PointLabelsList/PointLabelsList"; diff --git a/src/views/EditorView/SideNavigationBar/SideNavigationBar.tsx b/src/views/EditorView/SideNavigationBar/SideNavigationBar.tsx index adfc5d72..8617dadc 100644 --- a/src/views/EditorView/SideNavigationBar/SideNavigationBar.tsx +++ b/src/views/EditorView/SideNavigationBar/SideNavigationBar.tsx @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import './SideNavigationBar.scss'; -import {Direction} from "../../../data/Direction"; +import {Direction} from "../../../data/enums/Direction"; interface IProps { direction: Direction diff --git a/src/views/EditorView/StateBar/StateBar.tsx b/src/views/EditorView/StateBar/StateBar.tsx index 593b1f2c..3ab6fb31 100644 --- a/src/views/EditorView/StateBar/StateBar.tsx +++ b/src/views/EditorView/StateBar/StateBar.tsx @@ -3,7 +3,7 @@ import './StateBar.scss'; import {ImageData} from "../../../store/editor/types"; import {AppState} from "../../../store"; import {connect} from "react-redux"; -import {LabelType} from "../../../data/LabelType"; +import {LabelType} from "../../../data/enums/LabelType"; interface IProps { imagesData: ImageData[]; diff --git a/src/views/EditorView/TopNavigationBar/TopNavigationBar.scss b/src/views/EditorView/TopNavigationBar/TopNavigationBar.scss index 30bd025f..e610206b 100644 --- a/src/views/EditorView/TopNavigationBar/TopNavigationBar.scss +++ b/src/views/EditorView/TopNavigationBar/TopNavigationBar.scss @@ -2,11 +2,12 @@ .TopNavigationBar { align-self: stretch; - height: $topNavigationBarHeight; + min-height: $topNavigationBarHeight; background-color: $darkThemeFirstColor; box-shadow: 0 0 6px 0 rgba(0,0,0,.2); display: flex; + flex: 0 0 $topNavigationBarHeight; flex-direction: column; flex-wrap: nowrap; justify-content: flex-start; diff --git a/src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx b/src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx index b5ce575f..34a63062 100644 --- a/src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx +++ b/src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx @@ -2,7 +2,7 @@ import React from 'react'; import './TopNavigationBar.scss'; import StateBar from "../StateBar/StateBar"; import {UnderlineTextButton} from "../../Common/UnderlineTextButton/UnderlineTextButton"; -import {PopupWindowType} from "../../../data/PopupWindowType"; +import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {AppState} from "../../../store"; import {connect} from "react-redux"; import {updateActivePopupType} from "../../../store/general/actionCreators"; diff --git a/src/views/MainView/ImagesDropZone/ImagesDropZone.tsx b/src/views/MainView/ImagesDropZone/ImagesDropZone.tsx index 3c786abc..b609dd82 100644 --- a/src/views/MainView/ImagesDropZone/ImagesDropZone.tsx +++ b/src/views/MainView/ImagesDropZone/ImagesDropZone.tsx @@ -6,11 +6,11 @@ import {ImageData} from "../../../store/editor/types"; import {connect} from "react-redux"; import {addImageData, updateActiveImageIndex, updateProjectType} from "../../../store/editor/actionCreators"; import {AppState} from "../../../store"; -import {ProjectType} from "../../../data/ProjectType"; +import {ProjectType} from "../../../data/enums/ProjectType"; import {FileUtil} from "../../../utils/FileUtil"; -import {PopupWindowType} from "../../../data/PopupWindowType"; +import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {updateActivePopupType} from "../../../store/general/actionCreators"; -import {AcceptedFileType} from "../../../data/AcceptedFileType"; +import {AcceptedFileType} from "../../../data/enums/AcceptedFileType"; interface IProps { updateActiveImageIndex: (activeImageIndex: number) => any; diff --git a/src/views/MainView/MainView.tsx b/src/views/MainView/MainView.tsx index 2ac41ee4..0746e98e 100644 --- a/src/views/MainView/MainView.tsx +++ b/src/views/MainView/MainView.tsx @@ -4,8 +4,8 @@ import {TextButton} from "../Common/TextButton/TextButton"; import classNames from 'classnames'; import {ISize} from "../../interfaces/ISize"; import {ImageButton} from "../Common/ImageButton/ImageButton"; -import {ISocialMedia, SocialMediaData} from "../../data/SocialMediaData"; -import {EditorFeatureData, IEditorFeature} from "../../data/EditorFeatureData"; +import {ISocialMedia, SocialMediaData} from "../../data/info/SocialMediaData"; +import {EditorFeatureData, IEditorFeature} from "../../data/info/EditorFeatureData"; import {Tooltip} from "@material-ui/core"; import Fade from "@material-ui/core/Fade"; import withStyles from "@material-ui/core/styles/withStyles"; diff --git a/src/views/MobileMainView/MobileMainView.tsx b/src/views/MobileMainView/MobileMainView.tsx index 0f477cb0..f7f4fd72 100644 --- a/src/views/MobileMainView/MobileMainView.tsx +++ b/src/views/MobileMainView/MobileMainView.tsx @@ -5,8 +5,8 @@ import {ISize} from "../../interfaces/ISize"; import {AppState} from "../../store"; import {connect} from "react-redux"; import classNames from 'classnames' -import {EditorFeatureData, IEditorFeature} from "../../data/EditorFeatureData"; -import {ISocialMedia, SocialMediaData} from "../../data/SocialMediaData"; +import {EditorFeatureData, IEditorFeature} from "../../data/info/EditorFeatureData"; +import {ISocialMedia, SocialMediaData} from "../../data/info/SocialMediaData"; import {ImageButton} from "../Common/ImageButton/ImageButton"; interface IProps { diff --git a/src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx b/src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx index d7579ea2..139e3c41 100644 --- a/src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx +++ b/src/views/PopupView/ExitProjectPopup/ExitProjectPopup.tsx @@ -1,6 +1,6 @@ import React from 'react' import './ExitProjectPopup.scss' -import {PopupWindowType} from "../../../data/PopupWindowType"; +import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; import { updateActiveImageIndex, @@ -13,7 +13,7 @@ import { import {updateActivePopupType} from "../../../store/general/actionCreators"; import {AppState} from "../../../store"; import {connect} from "react-redux"; -import {ProjectType} from "../../../data/ProjectType"; +import {ProjectType} from "../../../data/enums/ProjectType"; import {ImageData} from "../../../store/editor/types"; interface IProps { diff --git a/src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.tsx b/src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.tsx index 66919ae2..f3e197ac 100644 --- a/src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.tsx +++ b/src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.tsx @@ -4,17 +4,17 @@ import {ImageData} from "../../../store/editor/types"; import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; import {AppState} from "../../../store"; import {connect} from "react-redux"; -import {PopupWindowType} from "../../../data/PopupWindowType"; +import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {updateActivePopupType} from "../../../store/general/actionCreators"; -import {ExportFormatType} from "../../../data/ExportFormatType"; +import {ExportFormatType} from "../../../data/enums/ExportFormatType"; import {RectLabelsExporter} from "../../../logic/export/RectLabelsExporter"; -import {LabelType} from "../../../data/LabelType"; +import {LabelType} from "../../../data/enums/LabelType"; import {ImageButton} from "../../Common/ImageButton/ImageButton"; import {IExportFormat} from "../../../interfaces/IExportFormat"; -import {RectExportFormatData} from "../../../data/RectExportFormatData"; -import {PointExportFormatData} from "../../../data/PointExportFormatData"; +import {RectExportFormatData} from "../../../data/export/RectExportFormatData"; +import {PointExportFormatData} from "../../../data/export/PointExportFormatData"; import {PointLabelsExporter} from "../../../logic/export/PointLabelsExport"; -import {PolygonExportFormatData} from "../../../data/PolygonExportFormatData"; +import {PolygonExportFormatData} from "../../../data/export/PolygonExportFormatData"; import {PolygonLabelsExporter} from "../../../logic/export/PolygonLabelsExporter"; interface IProps { diff --git a/src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.tsx b/src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.tsx index 6eb9ce8b..1390dd2e 100644 --- a/src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.tsx +++ b/src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.tsx @@ -1,6 +1,8 @@ -import React from 'react' +import React, {useEffect, useState} from 'react' import './GenericYesNoPopup.scss' import {TextButton} from "../../Common/TextButton/TextButton"; +import {ContextManager} from "../../../logic/context/ContextManager"; +import {ContextType} from "../../../data/enums/ContextType"; interface IProps { title: string; @@ -28,6 +30,15 @@ export const GenericYesNoPopup: React.FC = ( skipRejectButton, disableRejectButton }) => { + + const [status, setMountStatus] = useState(false); + useEffect(() => { + if (!status) { + ContextManager.switchCtx(ContextType.POPUP); + setMountStatus(true); + } + }, [status]); + return (
diff --git a/src/views/PopupView/InsertLabelNamesPopup/InsertLabelNamesPopup.tsx b/src/views/PopupView/InsertLabelNamesPopup/InsertLabelNamesPopup.tsx index 99612321..e7a8a84a 100644 --- a/src/views/PopupView/InsertLabelNamesPopup/InsertLabelNamesPopup.tsx +++ b/src/views/PopupView/InsertLabelNamesPopup/InsertLabelNamesPopup.tsx @@ -1,7 +1,7 @@ import React, {useState} from 'react' import './InsertLabelNamesPopup.scss' import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; -import {PopupWindowType} from "../../../data/PopupWindowType"; +import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {updateActiveLabelNameIndex, updateLabelNamesList} from "../../../store/editor/actionCreators"; import {updateActivePopupType} from "../../../store/general/actionCreators"; import {AppState} from "../../../store"; diff --git a/src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.tsx b/src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.tsx index 2f581c94..864de41b 100644 --- a/src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.tsx +++ b/src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.tsx @@ -4,11 +4,11 @@ import {AppState} from "../../../store"; import {connect} from "react-redux"; import {updateActiveLabelNameIndex, updateLabelNamesList} from "../../../store/editor/actionCreators"; import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; -import {PopupWindowType} from "../../../data/PopupWindowType"; +import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {updateActivePopupType} from "../../../store/general/actionCreators"; import {useDropzone} from "react-dropzone"; import {FileUtil} from "../../../utils/FileUtil"; -import {AcceptedFileType} from "../../../data/AcceptedFileType"; +import {AcceptedFileType} from "../../../data/enums/AcceptedFileType"; interface IProps { updateActiveLabelNameIndex: (activeLabelIndex: number) => any; diff --git a/src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.tsx b/src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.tsx index 0791eea1..fe76694f 100644 --- a/src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.tsx +++ b/src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.tsx @@ -4,12 +4,12 @@ import {AppState} from "../../../store"; import {connect} from "react-redux"; import {addImageData} from "../../../store/editor/actionCreators"; import {GenericYesNoPopup} from "../GenericYesNoPopup/GenericYesNoPopup"; -import {PopupWindowType} from "../../../data/PopupWindowType"; +import {PopupWindowType} from "../../../data/enums/PopupWindowType"; import {updateActivePopupType} from "../../../store/general/actionCreators"; import {useDropzone} from "react-dropzone"; import {FileUtil} from "../../../utils/FileUtil"; import {ImageData} from "../../../store/editor/types"; -import {AcceptedFileType} from "../../../data/AcceptedFileType"; +import {AcceptedFileType} from "../../../data/enums/AcceptedFileType"; interface IProps { updateActivePopupType: (activePopupType: PopupWindowType) => any; diff --git a/src/views/PopupView/PopupView.tsx b/src/views/PopupView/PopupView.tsx index 7111d0a6..c4d0087e 100644 --- a/src/views/PopupView/PopupView.tsx +++ b/src/views/PopupView/PopupView.tsx @@ -1,6 +1,6 @@ import React from 'react'; import './PopupView.scss'; -import {PopupWindowType} from "../../data/PopupWindowType"; +import {PopupWindowType} from "../../data/enums/PopupWindowType"; import {AppState} from "../../store"; import {connect} from "react-redux"; import LoadLabelsPopup from "./LoadLabelNamesPopup/LoadLabelNamesPopup"; diff --git a/tsconfig.json b/tsconfig.json index 6b3f40e6..e11d0404 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, - "isolatedModules": false, + "isolatedModules": true, "noEmit": true, "jsx": "preserve", "strictNullChecks": false,