diff --git a/build/bin/textualize.app/Contents/MacOS/Textualize b/build/bin/textualize.app/Contents/MacOS/Textualize index 98df797..53a2342 100755 Binary files a/build/bin/textualize.app/Contents/MacOS/Textualize and b/build/bin/textualize.app/Contents/MacOS/Textualize differ diff --git a/core/Document/Document.go b/core/Document/Document.go index 8cd1980..be21ea5 100644 --- a/core/Document/Document.go +++ b/core/Document/Document.go @@ -6,4 +6,18 @@ type Entity struct { Name string Path string ProjectId string + Areas []Area +} + +type Area struct { + Id string + Name string + StartX int + StartY int + EndX int + EndY int +} + +func (e *Entity) AddArea(a Area) { + e.Areas = append(e.Areas, a) } diff --git a/core/Document/DocumentCollection.go b/core/Document/DocumentCollection.go index 061894f..5d7af9d 100644 --- a/core/Document/DocumentCollection.go +++ b/core/Document/DocumentCollection.go @@ -19,12 +19,12 @@ func (collection *DocumentCollection) AddDocument(document Entity) { collection.Documents = append(collection.Documents, document) } -func (collection *DocumentCollection) GetDocumentById(id string) Entity { - var foundDocument Entity +func (collection *DocumentCollection) GetDocumentById(id string) *Entity { + var foundDocument *Entity - for _, d := range collection.Documents { + for index, d := range collection.Documents { if d.Id == id { - foundDocument = d + foundDocument = &collection.Documents[index] break } } diff --git a/frontend/components/workspace/DocumentRenderer.tsx b/frontend/components/workspace/DocumentRenderer.tsx index 24f8083..306b3b2 100644 --- a/frontend/components/workspace/DocumentRenderer.tsx +++ b/frontend/components/workspace/DocumentRenderer.tsx @@ -1,7 +1,8 @@ 'use client' -import { useEffect, useRef } from "react" +import React, { useEffect, useRef, useState } from "react" import { useProject } from "../../context/Project/provider" +import { ipc } from "../../wailsjs/wailsjs/go/models" const loadImage = (path: string): Promise => { @@ -14,31 +15,171 @@ const loadImage = (path: string): Promise => { } const DocumentRenderer = () => { - const { getSelectedDocument } = useProject() - const canvasRef = useRef(null) + const { getSelectedDocument, requestAddArea } = useProject() + const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 }) + const selectedDocument = getSelectedDocument() + const areas = selectedDocument?.areas + const documentCanvas = useRef(null) + const areaCanvas = useRef(null) + const drawingCanvas = useRef(null) + + // console.log('selectedDocument: ', selectedDocument) + + let downClickX = 0 + let downClickY = 0 + let isMouseDown = false + + const applyCanvasSizes = (size: { width: number, height: number }) => { + const documentCanvasInstance = documentCanvas.current + if (!documentCanvasInstance) return + documentCanvasInstance.width = size.width + documentCanvasInstance.height = size.height + + const areaCanvasInstance = areaCanvas.current + if (!areaCanvasInstance) return + areaCanvasInstance.width = size.width + areaCanvasInstance.height = size.height + + const drawingCanvasInstance = drawingCanvas.current + if (!drawingCanvasInstance) return + drawingCanvasInstance.width = size.width + drawingCanvasInstance.height = size.height + } const applyDocumentToCanvas = async (path: string) => { const image = await loadImage(path) + applyCanvasSizes({ width: image.naturalWidth, height: image.naturalHeight }) - const canvas = canvasRef.current - if (!canvas) return - canvas.width = image.naturalWidth - canvas.height = image.naturalHeight + const documentCanvasInstance = documentCanvas.current + if (!documentCanvasInstance) return - const context = canvas.getContext('2d') + const context = documentCanvasInstance.getContext('2d') if (!context) return context.drawImage(image, 0, 0, image.width, image.height) + + if (areas) applyAreasToCanvas() + } + + const applyAreasToCanvas = () => { + if (!areas || !areas.length) return + const areaCanvasInstance = areaCanvas.current + if (!areaCanvasInstance) return + const context = areaCanvasInstance.getContext("2d") + if (!context) return + + context.clearRect(0, 0, areaCanvasInstance.width, areaCanvasInstance.height) + + areas.forEach(a => { + console.log('area: ', a) + const width = a.endX - a.startX + const height = a.endY - a.startY + const x = a.startX + const y = a.startY + console.log(`context.rect: `, x, y, width, height) + context.rect(x, y, width, height) + context.lineWidth = 2 + context.strokeStyle = '#dc8dec' + context.stroke() + }) + } + + const handleMouseDown = (e: React.MouseEvent) => { + const drawingCanvasInstance = drawingCanvas.current + if (!drawingCanvasInstance) return + + downClickX = e.nativeEvent.offsetX + downClickY = e.nativeEvent.offsetY + isMouseDown = true + } + + const handleMouseUp = async (e: React.MouseEvent) => { + const drawingCanvasInstance = drawingCanvas.current + if (!drawingCanvasInstance) return + + const mouseX = e.nativeEvent.offsetX + const mouseY = e.nativeEvent.offsetY + + let startX: number, endX: number + if (downClickX < mouseX) { + startX = downClickX + endX = mouseX + } else { + startX = mouseX + endX = downClickX + } + + let startY: number, endY: number + if (downClickY < mouseY) { + startY = downClickY + endY = mouseY + } else { + startY = mouseY + endY = downClickY + } + + if (selectedDocument?.id) + requestAddArea(selectedDocument.id, { startX, startY, endX, endY }).then(response => { + console.log('requestAddArea: ', response) + }).catch(err => console.log('err requestAddArea :', err)) + + const context = drawingCanvasInstance.getContext('2d') + context?.clearRect(0, 0, drawingCanvasInstance.width, drawingCanvasInstance.height) + isMouseDown = false + downClickX = 0 + downClickY = 0 + } + + const handleMouseMove = (e: React.MouseEvent) => { + const drawingCanvasInstance = drawingCanvas.current + if (!drawingCanvasInstance) return + + let mouseX = e.nativeEvent.offsetX + let mouseY = e.nativeEvent.offsetY + + if (isMouseDown) { + const context = drawingCanvasInstance.getContext('2d') + if (!context) return + + context.clearRect(0, 0, drawingCanvasInstance.width, drawingCanvasInstance.height) + context.beginPath() + const width = mouseX - downClickX + const height = mouseY - downClickY + context.rect(downClickX, downClickY, width, height) + context.strokeStyle = '#000' + context.lineWidth = 2 + context.stroke() + } } useEffect(() => { - const selectedDocument = getSelectedDocument() - const documentPath = selectedDocument?.path - if (documentPath) applyDocumentToCanvas(selectedDocument.path) - }, [getSelectedDocument]) + if (selectedDocument?.path) applyDocumentToCanvas(selectedDocument.path) + }, [selectedDocument?.id]) - return ( - - ) + useEffect(() => { + applyAreasToCanvas() + }, [areas, areas?.length]) + + return
+ + + +
} export default DocumentRenderer diff --git a/frontend/context/Project/provider.tsx b/frontend/context/Project/provider.tsx index 4932c56..a3f3851 100644 --- a/frontend/context/Project/provider.tsx +++ b/frontend/context/Project/provider.tsx @@ -1,8 +1,9 @@ import { createContext, ReactNode, useContext, useState } from 'react' -import { GetDocuments, RequestAddDocument, RequestAddDocumentGroup } from '../../wailsjs/wailsjs/go/ipc/Channel' +import { GetDocuments, RequestAddArea, RequestAddDocument, RequestAddDocumentGroup } from '../../wailsjs/wailsjs/go/ipc/Channel' import { ipc } from '../../wailsjs/wailsjs/go/models' -import { ProjectContextType, ProjectProps } from './types' +import { AddAreaProps, ProjectContextType, ProjectProps } from './types' import makeDefaultProject from './makeDefaultProject' +import { LogPrint } from '../../wailsjs/wailsjs/runtime/runtime' const ProjectContext = createContext(makeDefaultProject()) @@ -18,33 +19,41 @@ export function ProjectProvider({ children, projectProps }: Props) { const updateDocuments = async () => { GetDocuments().then(response => { - setDocuments(response.documents) - setGroups(response.groups) + if (response.documents.length) setDocuments(response.documents) + if (response.groups.length) setGroups(response.groups) Promise.resolve(response) }) } const requestAddDocument = async (groupId: string, documentName: string) => { const response = await RequestAddDocument(groupId, documentName) - if (response.id) updateDocuments() + if (response.id) await updateDocuments() return response } const requestAddDocumentGroup = async (groupName: string) => { const response = await RequestAddDocumentGroup(groupName) - if (response.id) updateDocuments() + if (response.id) await updateDocuments() + return response + } + + const requestAddArea = async (documentId: string, area: AddAreaProps): Promise => { + const response = await RequestAddArea(documentId, new ipc.Area(area)) + + if (response.id) await updateDocuments() return response } const getSelectedDocument = () => documents.find(d => d.id === selectedDocumentId) - if (!documents.length || !groups.length) updateDocuments() + if (!documents.length && !groups.length) updateDocuments() const value = { id: '', documents, groups, getSelectedDocument, + requestAddArea, requestAddDocument, requestAddDocumentGroup, selectedDocumentId, diff --git a/frontend/context/Project/types.ts b/frontend/context/Project/types.ts index 40da64c..b369b69 100644 --- a/frontend/context/Project/types.ts +++ b/frontend/context/Project/types.ts @@ -6,10 +6,19 @@ export type ProjectProps = { groups: ipc.Group[], } +export type AddAreaProps = { + name?: string, + startX: number, + startY: number, + endX: number, + endY: number +} + export type ProjectContextType = { getSelectedDocument: () => ipc.Document | undefined - requestAddDocument: (groupId: string, documentName: string) => Promise, - requestAddDocumentGroup: (groupName: string) => Promise, - selectedDocumentId: string, - setSelectedDocumentId: (id: string) => void, + requestAddArea: (documentId: string, area: AddAreaProps) => Promise + requestAddDocument: (groupId: string, documentName: string) => Promise + requestAddDocumentGroup: (groupName: string) => Promise + selectedDocumentId: string + setSelectedDocumentId: (id: string) => void } & ProjectProps \ No newline at end of file diff --git a/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts b/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts index 07d9c3d..5dd1063 100755 --- a/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts +++ b/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts @@ -4,6 +4,8 @@ import {ipc} from '../models'; export function GetDocuments():Promise; +export function RequestAddArea(arg1:string,arg2:ipc.Area):Promise; + export function RequestAddDocument(arg1:string,arg2:string):Promise; export function RequestAddDocumentGroup(arg1:string):Promise; diff --git a/frontend/wailsjs/wailsjs/go/ipc/Channel.js b/frontend/wailsjs/wailsjs/go/ipc/Channel.js index 36c3028..c5554c7 100755 --- a/frontend/wailsjs/wailsjs/go/ipc/Channel.js +++ b/frontend/wailsjs/wailsjs/go/ipc/Channel.js @@ -6,6 +6,10 @@ export function GetDocuments() { return window['go']['ipc']['Channel']['GetDocuments'](); } +export function RequestAddArea(arg1, arg2) { + return window['go']['ipc']['Channel']['RequestAddArea'](arg1, arg2); +} + export function RequestAddDocument(arg1, arg2) { return window['go']['ipc']['Channel']['RequestAddDocument'](arg1, arg2); } diff --git a/frontend/wailsjs/wailsjs/go/models.ts b/frontend/wailsjs/wailsjs/go/models.ts index 4e1aff5..2c474bc 100755 --- a/frontend/wailsjs/wailsjs/go/models.ts +++ b/frontend/wailsjs/wailsjs/go/models.ts @@ -1,11 +1,34 @@ export namespace ipc { + export class Area { + id: string; + name: string; + startX: number; + startY: number; + endX: number; + endY: number; + + static createFrom(source: any = {}) { + return new Area(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.name = source["name"]; + this.startX = source["startX"]; + this.startY = source["startY"]; + this.endX = source["endX"]; + this.endY = source["endY"]; + } + } export class Document { id: string; groupId: string; name: string; path: string; projectId: string; + areas: Area[]; static createFrom(source: any = {}) { return new Document(source); @@ -18,7 +41,26 @@ export namespace ipc { this.name = source["name"]; this.path = source["path"]; this.projectId = source["projectId"]; + this.areas = this.convertValues(source["areas"], Area); } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } } export class Group { id: string; diff --git a/ipc/Documents.go b/ipc/Documents.go index 4aa597f..f96d5e4 100644 --- a/ipc/Documents.go +++ b/ipc/Documents.go @@ -23,12 +23,25 @@ func (c *Channel) GetDocuments() GetDocumentsResponse { } for _, d := range documents { + jsonAreas := make([]Area, 0) + for _, a := range d.Areas { + jsonAreas = append(jsonAreas, Area{ + Id: a.Id, + Name: a.Name, + StartX: a.StartX, + StartY: a.StartY, + EndX: a.EndX, + EndY: a.EndY, + }) + } + jsonDocument := Document{ Id: d.Id, GroupId: d.GroupId, Name: d.Name, Path: d.Path, ProjectId: d.ProjectId, + Areas: jsonAreas, } response.Documents = append(response.Documents, jsonDocument) } @@ -87,7 +100,7 @@ func (c *Channel) RequestAddDocumentGroup(name string) Group { newGroup := document.Group{ Id: uuid.NewString(), Name: name, - ProjectId: "something else", + ProjectId: "something else", // TODO: change me } document.GetGroupCollection().AddDocumentGroup(newGroup) @@ -101,3 +114,26 @@ func (c *Channel) RequestAddDocumentGroup(name string) Group { return response } + +func (c *Channel) RequestAddArea(documentId string, area Area) Area { + runtime.LogDebug(app.GetInstance().Context, "RequestAddArea in go") + foundDocument := document.GetDocumentCollection().GetDocumentById(documentId) + + var id string + if area.Id == "" { + id = uuid.NewString() + } else { + id = area.Id + } + + newArea := document.Area{ + Id: id, + Name: area.Name, + StartX: area.StartX, + EndX: area.EndX, + StartY: area.StartY, + EndY: area.EndY, + } + foundDocument.AddArea(newArea) + return Area(newArea) +} diff --git a/ipc/JsonEntities.go b/ipc/JsonEntities.go index b7da0a9..cb8e3a5 100644 --- a/ipc/JsonEntities.go +++ b/ipc/JsonEntities.go @@ -6,6 +6,7 @@ type Document struct { Name string `json:"name"` Path string `json:"path"` ProjectId string `json:"projectId"` + Areas []Area `json:"areas"` } type DocumentCollection struct { @@ -25,3 +26,12 @@ type GroupCollection struct { Groups []Group `json:"groups"` ProjectId string `json:"projectId"` } + +type Area struct { + Id string `json:"id"` + Name string `json:"name"` + StartX int `json:"startX"` + StartY int `json:"startY"` + EndX int `json:"endX"` + EndY int `json:"endY"` +}