From 7b7735c6afe9931f8e392500027042e625b5713b Mon Sep 17 00:00:00 2001 From: Joshua Shoemaker Date: Wed, 26 Jul 2023 08:56:25 -0500 Subject: [PATCH] feat: make new connections --- core/ContextGroup/ContextGroupCollection.go | 112 +++++++++++ entities/ContextGroup.go | 184 +++++++++--------- .../ContextConnections/ConnectionLines.tsx | 71 +++++++ .../ContextConnections/ConnectionPoints.tsx | 23 ++- .../CurrentDrawingConnection.tsx | 13 +- .../ContextConnections/index.tsx | 6 +- .../createContextGroupProviderMethods.ts | 38 ++++ .../createUserMarkdownProviderMethods.ts | 2 +- .../context/Project/makeDefaultProject.ts | 5 +- frontend/context/Project/provider.tsx | 8 +- frontend/context/Project/types.ts | 3 + frontend/useCases/saveData.ts | 15 +- frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts | 6 +- frontend/wailsjs/wailsjs/go/ipc/Channel.js | 12 +- frontend/wailsjs/wailsjs/go/models.ts | 18 ++ ipc/ContextGroup.go | 50 +++-- ipc/Documents.go | 11 +- ipc/Session.go | 7 + storage/Local/ContextGroupDriver.go | 22 +++ storage/Storage.go | 2 + 20 files changed, 480 insertions(+), 128 deletions(-) create mode 100644 core/ContextGroup/ContextGroupCollection.go create mode 100644 frontend/components/DocumentCanvas/ContextConnections/ConnectionLines.tsx create mode 100644 frontend/context/Project/createContextGroupProviderMethods.ts create mode 100644 storage/Local/ContextGroupDriver.go diff --git a/core/ContextGroup/ContextGroupCollection.go b/core/ContextGroup/ContextGroupCollection.go new file mode 100644 index 0000000..57d17fd --- /dev/null +++ b/core/ContextGroup/ContextGroupCollection.go @@ -0,0 +1,112 @@ +package contextGroup + +import ( + "fmt" + "textualize/entities" +) + +type ContextGroupCollection struct { + Groups []entities.LinkedAreaList +} + +var contextGroupCollectionInstance *ContextGroupCollection + +func GetContextGroupCollection() *ContextGroupCollection { + if contextGroupCollectionInstance == nil { + contextGroupCollectionInstance = &ContextGroupCollection{} + } + return contextGroupCollectionInstance +} + +func SetContextGroupCollection(collection ContextGroupCollection) *ContextGroupCollection { + contextGroupCollectionInstance = &collection + return contextGroupCollectionInstance +} + +func SetContextGroupCollectionBySerialized(serialized []entities.SerializedLinkedProcessedArea) *ContextGroupCollection { + newInstance := ContextGroupCollection{} + + newInstance.Groups = append(newInstance.Groups, entities.DeserializeLinkedAreaList(serialized)) + + SetContextGroupCollection(newInstance) + return &newInstance +} + +func (collection *ContextGroupCollection) FindGroupById(id string) (*entities.LinkedAreaList, error) { + found := false + var foundGroup *entities.LinkedAreaList = nil + for _, group := range collection.Groups { + if group.Id == id { + found = true + foundGroup = &group + } + } + if !found { + return nil, fmt.Errorf("ContextGroupCollection.FindGroupById: Group with id %s not found", id) + } + return foundGroup, nil +} + +func (collection *ContextGroupCollection) FindGroupByLinkedProcessedAreaId(id string) (*entities.LinkedAreaList, error) { + found := false + var foundGroup *entities.LinkedAreaList = nil + for _, group := range collection.Groups { + for n := group.First(); n != nil && !found; n = n.GetNext() { + if n.Area.Id == id { + found = true + foundGroup = &group + } + } + } + if !found { + return nil, fmt.Errorf("ContextGroupCollection.FindGroupByLinkedProcessedAreaId: Group with LinkedProcessedArea.Id %s not found", id) + } + return foundGroup, nil +} + +func (collection *ContextGroupCollection) ConnectProcessedAreas(ancestorNode entities.ProcessedArea, descendantNode entities.ProcessedArea) bool { + ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorNode.Id) + descendantGroup, _ := collection.FindGroupByLinkedProcessedAreaId(descendantNode.Id) + + isAncestorInAnyInGroup := ancestorGroup != nil + isDescendantInAnyInGroup := descendantGroup != nil + isEitherInAnyInGroup := isAncestorInAnyInGroup || isDescendantInAnyInGroup + areBothInAnyInGroup := isAncestorInAnyInGroup && isDescendantInAnyInGroup + areBothInSameGroup := false + if areBothInAnyInGroup { + areBothInSameGroup = ancestorGroup.Id == descendantGroup.Id + } + + if areBothInSameGroup { + return true + } + + if !isEitherInAnyInGroup { + collection.createNewGroupAndConnectNodes(ancestorNode, descendantNode) + return true + } + + if isAncestorInAnyInGroup && !isDescendantInAnyInGroup { + ancestorGroup.InsertAfter(ancestorNode.Id, descendantNode) + return true + } + + if !isAncestorInAnyInGroup && isDescendantInAnyInGroup { + descendantGroup.InsertBefore(descendantNode.Id, ancestorNode) + return true + } + + return false +} + +func (collection *ContextGroupCollection) createNewGroupAndConnectNodes(ancestorNode entities.ProcessedArea, descendantNode entities.ProcessedArea) { + newGroup := entities.LinkedAreaList{ + Id: ancestorNode.Id, + DocumentId: ancestorNode.DocumentId, + Head: &entities.LinkedProcessedArea{Area: ancestorNode}, + Tail: &entities.LinkedProcessedArea{Area: descendantNode}, + } + newGroup.Head.Next = newGroup.Tail + newGroup.Tail.Previous = newGroup.Head + collection.Groups = append(collection.Groups, newGroup) +} diff --git a/entities/ContextGroup.go b/entities/ContextGroup.go index 1526091..a103af9 100644 --- a/entities/ContextGroup.go +++ b/entities/ContextGroup.go @@ -3,8 +3,6 @@ package entities import ( "errors" "fmt" - - "github.com/google/uuid" ) type IndependentTranslatedWord struct { @@ -15,43 +13,57 @@ type IndependentTranslatedWord struct { type LinkedProcessedArea struct { Area ProcessedArea - previous *LinkedProcessedArea - next *LinkedProcessedArea + Previous *LinkedProcessedArea + Next *LinkedProcessedArea +} + +type SerializedLinkedProcessedArea struct { + AreaId string `json:"areaId"` + PreviousId string `json:"previousId"` + NextId string `json:"nextId"` +} + +type ContextGroupCollection struct { + Groups []LinkedAreaList } type LinkedAreaList struct { - head *LinkedProcessedArea - tail *LinkedProcessedArea + Id string + DocumentId string + TranslationText string + Head *LinkedProcessedArea + Tail *LinkedProcessedArea } func (l *LinkedAreaList) First() *LinkedProcessedArea { - return l.head + return l.Head } -func (linkedProcessedWord *LinkedProcessedArea) Next() *LinkedProcessedArea { - return linkedProcessedWord.next +func (linkedProcessedWord *LinkedProcessedArea) GetNext() *LinkedProcessedArea { + return linkedProcessedWord.Next } -func (linkedProcessedWord *LinkedProcessedArea) Prev() *LinkedProcessedArea { - return linkedProcessedWord.previous +func (linkedProcessedWord *LinkedProcessedArea) GetPrevious() *LinkedProcessedArea { + return linkedProcessedWord.Previous } // Create new node with value func (l *LinkedAreaList) Push(processedArea ProcessedArea) *LinkedAreaList { n := &LinkedProcessedArea{Area: processedArea} - if l.head == nil { - l.head = n // First node + if l.Head == nil { + l.Head = n // First node } else { - l.tail.next = n // Add after prev last node - n.previous = l.tail // Link back to prev last node + l.Tail.Next = n // Add after prev last node + n.Previous = l.Tail // Link back to prev last node } - l.tail = n // reset tail to newly added node + l.Tail = n // reset tail to newly added node return l } + func (l *LinkedAreaList) Find(id string) *LinkedProcessedArea { found := false var ret *LinkedProcessedArea = nil - for n := l.First(); n != nil && !found; n = n.Next() { + for n := l.First(); n != nil && !found; n = n.GetNext() { if n.Area.Id == id { found = true ret = n @@ -59,16 +71,17 @@ func (l *LinkedAreaList) Find(id string) *LinkedProcessedArea { } return ret } + func (l *LinkedAreaList) Delete(id string) bool { success := false node2del := l.Find(id) if node2del != nil { fmt.Println("Delete - FOUND: ", id) - prev_node := node2del.previous - next_node := node2del.next + prev_node := node2del.Previous + next_node := node2del.Next // Remove this node - prev_node.next = node2del.next - next_node.previous = node2del.previous + prev_node.Next = node2del.Next + next_node.Previous = node2del.Previous success = true } return success @@ -78,86 +91,83 @@ var errEmpty = errors.New("ERROR - List is empty") // Pop last item from list func (l *LinkedAreaList) Pop() (processedArea ProcessedArea, err error) { - if l.tail == nil { + if l.Tail == nil { err = errEmpty } else { - processedArea = l.tail.Area - l.tail = l.tail.previous - if l.tail == nil { - l.head = nil + processedArea = l.Tail.Area + l.Tail = l.Tail.Previous + if l.Tail == nil { + l.Head = nil } } return processedArea, err } -type ContextGroup struct { // TODO: possibly remove this and expand the LinkedAreaList struct instead - Id string - DocumentId string - LinkedAreaList LinkedAreaList - TranslationText string -} - -type ContextGroupCollection struct { // TODO: these methods should live in core not entitites - Groups []ContextGroup -} - -var contextGroupCollectionInstance *ContextGroupCollection - -func GetContextGroupCollection() *ContextGroupCollection { - if contextGroupCollectionInstance == nil { - contextGroupCollectionInstance = &ContextGroupCollection{} - } - - return contextGroupCollectionInstance -} - -func SetContextGroupCollection(collection ContextGroupCollection) *ContextGroupCollection { - contextGroupCollectionInstance = &collection - return contextGroupCollectionInstance -} - -func (collection *ContextGroupCollection) FindContextGroupByNodeId(id string) *ContextGroup { - var foundContextGroup *ContextGroup - for i, g := range collection.Groups { - if g.LinkedAreaList.Find(id) != nil { - foundContextGroup = &collection.Groups[i] - break +func (l *LinkedAreaList) InsertAfter(id string, processedArea ProcessedArea) bool { + found := false + for n := l.First(); n != nil && !found; n = n.GetNext() { + if n.Area.Id == id { + found = true + newNode := &LinkedProcessedArea{Area: processedArea} + newNode.Next = n.Next + newNode.Previous = n + n.Next = newNode } } - - return foundContextGroup + return found } -func (collection *ContextGroupCollection) CreateContextGroupFromProcessedArea(area ProcessedArea) bool { - fmt.Println("CreateContextGroupFromProcessedArea") - - newLinkedAreaList := LinkedAreaList{} - newLinkedAreaList.Push(area) - - newContextGroup := ContextGroup{ - Id: uuid.NewString(), - DocumentId: area.DocumentId, - LinkedAreaList: newLinkedAreaList, +func (l *LinkedAreaList) InsertBefore(id string, processedArea ProcessedArea) bool { + found := false + for n := l.First(); n != nil && !found; n = n.GetNext() { + if n.Area.Id == id { + found = true + newNode := &LinkedProcessedArea{Area: processedArea} + newNode.Next = n + newNode.Previous = n.Previous + n.Previous = newNode + } } - - collection.Groups = append(collection.Groups, newContextGroup) - return true + return found } -// TODO: completely rework this linked list and the collection -func (collection *ContextGroupCollection) ConnectAreaAsTailToNode(tailArea ProcessedArea, headArea ProcessedArea) bool { - headNodeContextGroup := collection.FindContextGroupByNodeId(headArea.Id) +func (l *LinkedAreaList) Serialize() []SerializedLinkedProcessedArea { + var serialized []SerializedLinkedProcessedArea + for n := l.First(); n != nil; n = n.GetNext() { + areaId := n.Area.Id + previousId := "" + if n.Previous != nil { + previousId = n.Previous.Area.Id + } + nextId := "" + if n.Next != nil { + nextId = n.Next.Area.Id + } - if headNodeContextGroup == nil { - collection.CreateContextGroupFromProcessedArea(headArea) - headNodeContextGroup = collection.FindContextGroupByNodeId(headArea.Id) + serialized = append(serialized, SerializedLinkedProcessedArea{ + AreaId: areaId, + PreviousId: previousId, + NextId: nextId, + }) } - - headNode := headNodeContextGroup.LinkedAreaList.Find(headArea.Id) - headNode.next = &LinkedProcessedArea{ - Area: tailArea, - previous: headNode, - } - - return true + return serialized +} + +func DeserializeLinkedAreaList(serialized []SerializedLinkedProcessedArea) LinkedAreaList { + linkedAreaList := LinkedAreaList{} + for _, serializedLinkedProcessedArea := range serialized { + linkedAreaList.Push(ProcessedArea{ + Id: serializedLinkedProcessedArea.AreaId, + }) + } + for _, serializedLinkedProcessedArea := range serialized { + linkedProcessedArea := linkedAreaList.Find(serializedLinkedProcessedArea.AreaId) + if serializedLinkedProcessedArea.PreviousId != "" { + linkedProcessedArea.Previous = linkedAreaList.Find(serializedLinkedProcessedArea.PreviousId) + } + if serializedLinkedProcessedArea.NextId != "" { + linkedProcessedArea.Next = linkedAreaList.Find(serializedLinkedProcessedArea.NextId) + } + } + return linkedAreaList } diff --git a/frontend/components/DocumentCanvas/ContextConnections/ConnectionLines.tsx b/frontend/components/DocumentCanvas/ContextConnections/ConnectionLines.tsx new file mode 100644 index 0000000..0b7c94e --- /dev/null +++ b/frontend/components/DocumentCanvas/ContextConnections/ConnectionLines.tsx @@ -0,0 +1,71 @@ +'use client' + +import React from 'react' +import { Group, Line } from 'react-konva' +import { useProject } from '../../../context/Project/provider' +import { useStage } from '../context/provider' + +const ConnectionLines = () => { + const { scale } = useStage() + const { getSelectedDocument, contextGroups } = useProject() + const areas = getSelectedDocument()?.areas || [] + + const renderLines = () => { + console.log('contextGroups', contextGroups) + if (!contextGroups?.length) return <> + + const linesAlreadyRendered = new Set() + const lines = contextGroups.map((contextGroup) => { + const currentArea = areas.find(a => a.id === contextGroup.areaId) + const nextArea = areas.find(a => a.id === contextGroup.nextId) + if (!currentArea || !nextArea) return + + if (linesAlreadyRendered.has(`${contextGroup.areaId}-${contextGroup.nextId}`)) return + if (linesAlreadyRendered.has(`${contextGroup.nextId}-${contextGroup.areaId}`)) return + + const startingPoint = { + x: ((currentArea.startX + currentArea.endX) * scale) / 2, + y: currentArea.startY * scale + } + + const startingTensionPoint = { + x: (startingPoint.x + (nextArea.startX * scale)) / 2, + y: startingPoint.y, + } + + const endingPoint = { + x: ((nextArea.startX + nextArea.endX) * scale) / 2, + y: nextArea.endY * scale + } + + const endingTensionPoint = { + x: (startingPoint.x + (nextArea.startX * scale)) / 2, + y: endingPoint.y, + } + + linesAlreadyRendered.add(`${contextGroup.areaId}-${contextGroup.nextId}`) + linesAlreadyRendered.add(`${contextGroup.nextId}-${contextGroup.areaId}`) + + return + }) + return lines.filter(l => !!l) + } + + return {renderLines()} +} + +export default ConnectionLines diff --git a/frontend/components/DocumentCanvas/ContextConnections/ConnectionPoints.tsx b/frontend/components/DocumentCanvas/ContextConnections/ConnectionPoints.tsx index 45de5a4..ae8415d 100644 --- a/frontend/components/DocumentCanvas/ContextConnections/ConnectionPoints.tsx +++ b/frontend/components/DocumentCanvas/ContextConnections/ConnectionPoints.tsx @@ -4,14 +4,14 @@ import { Circle, Group } from 'react-konva' import { useStage } from '../context/provider' import { entities } from '../../../wailsjs/wailsjs/go/models' import { KonvaEventObject } from 'konva/lib/Node' -import { RequestConnectAreaAsTailToNode } from '../../../wailsjs/wailsjs/go/ipc/Channel' +import { useProject } from '../../../context/Project/provider' type Props = { areas: entities.Area[] } const ConnectionPoints = (props: Props) => { const { isLinkAreaContextsVisible, scale, startingContextConnection, setStartingContextConnection } = useStage() + const { requestConnectProcessedAreas } = useProject() - - const handleContextAreaMouseDown = (e: KonvaEventObject) => { + const handleContextAreaMouseDown = async (e: KonvaEventObject) => { e.cancelBubble = true const clickedConnectionPoint = { isHead: e.currentTarget.attrs.isHead, @@ -24,10 +24,15 @@ const ConnectionPoints = (props: Props) => { || clickedConnectionPoint.areaId === startingContextConnection.areaId) return setStartingContextConnection(null) - console.log('connected points', startingContextConnection, clickedConnectionPoint) - const headId = clickedConnectionPoint.isHead ? clickedConnectionPoint.areaId : startingContextConnection.areaId - const tailId = !clickedConnectionPoint.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId - RequestConnectAreaAsTailToNode(headId, tailId).then(res => console.log(res)).catch(err => console.warn(err)) + const headId = startingContextConnection.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId + const tailId = !startingContextConnection.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId + setStartingContextConnection(null) + + try { + await requestConnectProcessedAreas(headId, tailId) + } catch (err) { + console.warn('RequestConnectProcessedAreas', err) + } } const renderConnectingPointsForArea = (a: entities.Area) => { @@ -36,7 +41,7 @@ const ConnectionPoints = (props: Props) => { const headConnector = { />) } - return + return {connectorsToRender} } diff --git a/frontend/components/DocumentCanvas/ContextConnections/CurrentDrawingConnection.tsx b/frontend/components/DocumentCanvas/ContextConnections/CurrentDrawingConnection.tsx index 731e70b..0dd1568 100644 --- a/frontend/components/DocumentCanvas/ContextConnections/CurrentDrawingConnection.tsx +++ b/frontend/components/DocumentCanvas/ContextConnections/CurrentDrawingConnection.tsx @@ -2,18 +2,19 @@ import React from 'react' import { Line } from 'react-konva' -import { StartingContextConnection } from '../context/types' -import { entities } from '../../../wailsjs/wailsjs/go/models' import { Coordinates } from '../types' +import { useStage } from '../context/provider' +import { useProject } from '../../../context/Project/provider' type CurrentDrawingConnectionProps = { - startingContextConnection: StartingContextConnection | null - areas: entities.Area[], - scale: number, endDrawingPosition: Coordinates | null } const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => { - const { startingContextConnection, areas, scale, endDrawingPosition } = props + const { endDrawingPosition } = props + const { startingContextConnection, scale } = useStage() + const { getSelectedDocument } = useProject() + const areas = getSelectedDocument()?.areas || [] + if (!startingContextConnection || !endDrawingPosition) return <> const { areaId, isHead } = startingContextConnection diff --git a/frontend/components/DocumentCanvas/ContextConnections/index.tsx b/frontend/components/DocumentCanvas/ContextConnections/index.tsx index cfeb424..3d9320d 100644 --- a/frontend/components/DocumentCanvas/ContextConnections/index.tsx +++ b/frontend/components/DocumentCanvas/ContextConnections/index.tsx @@ -8,10 +8,11 @@ import Konva from 'konva' import { Coordinates } from '../types' import CurrentDrawingConnection from './CurrentDrawingConnection' import ConnectionPoints from './ConnectionPoints' +import ConnectionLines from './ConnectionLines' const ContextConnections = () => { const { getSelectedDocument } = useProject() - const { isLinkAreaContextsVisible, startingContextConnection, setStartingContextConnection, scale } = useStage() + const { isLinkAreaContextsVisible, startingContextConnection, scale } = useStage() const areas = getSelectedDocument()?.areas || [] const [endDrawingPosition, setEndDrawingPosition] = useState(null) @@ -34,7 +35,8 @@ const ContextConnections = () => { return - + + } diff --git a/frontend/context/Project/createContextGroupProviderMethods.ts b/frontend/context/Project/createContextGroupProviderMethods.ts new file mode 100644 index 0000000..d5d0045 --- /dev/null +++ b/frontend/context/Project/createContextGroupProviderMethods.ts @@ -0,0 +1,38 @@ +import { saveContextGroups } from '../../useCases/saveData' +import { RequestConnectProcessedAreas, GetSerializedContextGroups } from '../../wailsjs/wailsjs/go/ipc/Channel' +import { entities } from '../../wailsjs/wailsjs/go/models' + + +type Dependencies = { updateDocuments: Function } + +const createContextGroupProviderMethods = (dependencies?: Dependencies) => { + + const requestConnectProcessedAreas = async (headId: string, tailId: string) => { + let wasSuccessful = false + try { + wasSuccessful = await RequestConnectProcessedAreas(headId, tailId) + await saveContextGroups() + } catch (err) { + console.error(err) + } + dependencies?.updateDocuments() + return wasSuccessful + } + + const getSerializedContextGroups = async () => { + let response: entities.SerializedLinkedProcessedArea[] = [] + try { + response = await GetSerializedContextGroups() + } catch (err) { + console.error(err) + } + return response + } + + return { + requestConnectProcessedAreas, + getSerializedContextGroups, + } +} + +export default createContextGroupProviderMethods diff --git a/frontend/context/Project/createUserMarkdownProviderMethods.ts b/frontend/context/Project/createUserMarkdownProviderMethods.ts index c77fb19..b601e9d 100644 --- a/frontend/context/Project/createUserMarkdownProviderMethods.ts +++ b/frontend/context/Project/createUserMarkdownProviderMethods.ts @@ -1,6 +1,6 @@ import { saveUserProcessedMarkdown } from '../../useCases/saveData' import { GetUserMarkdownByDocumentId, RequestUpdateDocumentUserMarkdown } from '../../wailsjs/wailsjs/go/ipc/Channel' -import { ipc, entities } from '../../wailsjs/wailsjs/go/models' +import { entities } from '../../wailsjs/wailsjs/go/models' type Dependencies = {} diff --git a/frontend/context/Project/makeDefaultProject.ts b/frontend/context/Project/makeDefaultProject.ts index 4e2cb7e..108b796 100644 --- a/frontend/context/Project/makeDefaultProject.ts +++ b/frontend/context/Project/makeDefaultProject.ts @@ -5,12 +5,13 @@ const makeDefaultProject = (): ProjectContextType => ({ id: '', documents: [] as entities.Document[], groups: [] as entities.Group[], + contextGroups: [] as entities.SerializedLinkedProcessedArea[], selectedAreaId: '', selectedDocumentId: '', getSelectedDocument: () => new entities.Document(), getAreaById: (areaId) => undefined, getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new entities.ProcessedArea()]), - requestAddProcessedArea: (processesArea) => Promise.resolve(new entities.ProcessedArea()), + requestAddProcessedArea: (processesArea) => Promise.resolve(false), requestAddArea: (documentId, area) => Promise.resolve(new entities.Area()), requestUpdateArea: (updatedArea) => Promise.resolve(false), requestDeleteAreaById: (areaId) => Promise.resolve(false), @@ -33,6 +34,8 @@ const makeDefaultProject = (): ProjectContextType => ({ requestUpdateProcessedWordById: (wordId, newTestValue) => Promise.resolve(false), getProcessedAreaById: (areaId) => Promise.resolve(undefined), requestUpdateProcessedArea: updatedProcessedArea => Promise.resolve(false), + requestConnectProcessedAreas: (headId, tailId) => Promise.resolve(false), + getSerializedContextGroups: () => Promise.resolve([]), }) export default makeDefaultProject diff --git a/frontend/context/Project/provider.tsx b/frontend/context/Project/provider.tsx index 6c678e4..8c8e40c 100644 --- a/frontend/context/Project/provider.tsx +++ b/frontend/context/Project/provider.tsx @@ -10,6 +10,7 @@ import createAreaProviderMethods from './createAreaProviderMethods' import createDocumentProviderMethods from './createDocumentMethods' import createSessionProviderMethods from './createSessionProviderMethods' import createUserMarkdownProviderMethods from './createUserMarkdownProviderMethods' +import createContextGroupProviderMethods from './createContextGroupProviderMethods' const ProjectContext = createContext(makeDefaultProject()) @@ -21,15 +22,17 @@ type Props = { children: ReactNode, projectProps: ProjectProps } export function ProjectProvider({ children, projectProps }: Props) { const [documents, setDocuments] = useState(projectProps.documents) const [groups, setGroups] = useState(projectProps.groups) + const [contextGroups, setContextGroups] = useState(projectProps.contextGroups) const [selectedAreaId, setSelectedAreaId] = useState('') const [selectedDocumentId, setSelectedDocumentId] = useState('') const [currentSession, setCurrentSession] = useState(new entities.Session()) const updateDocuments = async () => { const response = await GetDocuments() - const { documents, groups } = response + const { documents, groups, contextGroups } = response setDocuments(documents) setGroups(groups) + setContextGroups(contextGroups) return response } @@ -43,6 +46,7 @@ export function ProjectProvider({ children, projectProps }: Props) { const areaMethods = createAreaProviderMethods({ documents, updateDocuments, selectedDocumentId }) const sessionMethods = createSessionProviderMethods({ updateSession, updateDocuments }) const userMarkDownMethods = createUserMarkdownProviderMethods() + const contextGroupMethods = createContextGroupProviderMethods({ updateDocuments }) useEffect(() => { @@ -60,6 +64,7 @@ export function ProjectProvider({ children, projectProps }: Props) { id: '', documents, groups, + contextGroups, selectedAreaId, setSelectedAreaId, selectedDocumentId, @@ -69,6 +74,7 @@ export function ProjectProvider({ children, projectProps }: Props) { ...documentMethods, ...sessionMethods, ...userMarkDownMethods, + ...contextGroupMethods, } return diff --git a/frontend/context/Project/types.ts b/frontend/context/Project/types.ts index 9dca7a2..f6b3426 100644 --- a/frontend/context/Project/types.ts +++ b/frontend/context/Project/types.ts @@ -4,6 +4,7 @@ export type ProjectProps = { id: string, documents: entities.Document[], groups: entities.Group[], + contextGroups: entities.SerializedLinkedProcessedArea[], } export type AddAreaProps = { @@ -65,4 +66,6 @@ export type ProjectContextType = { requestUpdateProcessedWordById: (wordId: string, newTextValue: string) => Promise getProcessedAreaById: (areaId: string) => Promise requestUpdateProcessedArea: (updatedProcessedArea: entities.ProcessedArea) => Promise + requestConnectProcessedAreas: (headId: string, tailId: string) => Promise + getSerializedContextGroups: () => Promise } & ProjectProps \ No newline at end of file diff --git a/frontend/useCases/saveData.ts b/frontend/useCases/saveData.ts index 06f44b1..9a9f45c 100644 --- a/frontend/useCases/saveData.ts +++ b/frontend/useCases/saveData.ts @@ -1,4 +1,7 @@ -import { RequestSaveDocumentCollection, RequestSaveGroupCollection, RequestSaveLocalUserProcessedMarkdownCollection, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel' +import { RequestSaveDocumentCollection, RequestSaveGroupCollection, + RequestSaveLocalUserProcessedMarkdownCollection, + RequestSaveProcessedTextCollection, RequestSaveContextGroupCollection +} from '../wailsjs/wailsjs/go/ipc/Channel' const saveDocuments = async () => { try { @@ -36,9 +39,19 @@ const saveUserProcessedMarkdown = async () => { } } +const saveContextGroups = async () => { + try { + const sucessfulSave = await RequestSaveContextGroupCollection() + if (!sucessfulSave) console.error('Could not save ContextGroupCollection') + } catch (err) { + console.error('Could not save ContextGroupCollection: ', err) + } +} + export { saveDocuments, saveGroups, saveProcessedText, saveUserProcessedMarkdown, + saveContextGroups, } \ 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 86df433..1dca2b1 100755 --- a/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts +++ b/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts @@ -23,6 +23,8 @@ export function GetProcessedAreasByDocumentId(arg1:string):Promise; +export function GetSerializedContextGroups():Promise>; + export function GetSupportedLanguages():Promise>; export function GetUserMarkdownByDocumentId(arg1:string):Promise; @@ -43,7 +45,7 @@ export function RequestChangeSessionProjectByName(arg1:string):Promise; export function RequestChooseUserAvatar():Promise; -export function RequestConnectAreaAsTailToNode(arg1:string,arg2:string):Promise; +export function RequestConnectProcessedAreas(arg1:string,arg2:string):Promise; export function RequestDeleteAreaById(arg1:string):Promise; @@ -51,6 +53,8 @@ export function RequestDeleteDocumentAndChildren(arg1:string):Promise; export function RequestDeleteProcessedAreaById(arg1:string):Promise; +export function RequestSaveContextGroupCollection():Promise; + export function RequestSaveDocumentCollection():Promise; export function RequestSaveGroupCollection():Promise; diff --git a/frontend/wailsjs/wailsjs/go/ipc/Channel.js b/frontend/wailsjs/wailsjs/go/ipc/Channel.js index 0cff44e..ec6a7b2 100755 --- a/frontend/wailsjs/wailsjs/go/ipc/Channel.js +++ b/frontend/wailsjs/wailsjs/go/ipc/Channel.js @@ -42,6 +42,10 @@ export function GetProjectByName(arg1) { return window['go']['ipc']['Channel']['GetProjectByName'](arg1); } +export function GetSerializedContextGroups() { + return window['go']['ipc']['Channel']['GetSerializedContextGroups'](); +} + export function GetSupportedLanguages() { return window['go']['ipc']['Channel']['GetSupportedLanguages'](); } @@ -82,8 +86,8 @@ export function RequestChooseUserAvatar() { return window['go']['ipc']['Channel']['RequestChooseUserAvatar'](); } -export function RequestConnectAreaAsTailToNode(arg1, arg2) { - return window['go']['ipc']['Channel']['RequestConnectAreaAsTailToNode'](arg1, arg2); +export function RequestConnectProcessedAreas(arg1, arg2) { + return window['go']['ipc']['Channel']['RequestConnectProcessedAreas'](arg1, arg2); } export function RequestDeleteAreaById(arg1) { @@ -98,6 +102,10 @@ export function RequestDeleteProcessedAreaById(arg1) { return window['go']['ipc']['Channel']['RequestDeleteProcessedAreaById'](arg1); } +export function RequestSaveContextGroupCollection() { + return window['go']['ipc']['Channel']['RequestSaveContextGroupCollection'](); +} + export function RequestSaveDocumentCollection() { return window['go']['ipc']['Channel']['RequestSaveDocumentCollection'](); } diff --git a/frontend/wailsjs/wailsjs/go/models.ts b/frontend/wailsjs/wailsjs/go/models.ts index a70c55d..f01900b 100755 --- a/frontend/wailsjs/wailsjs/go/models.ts +++ b/frontend/wailsjs/wailsjs/go/models.ts @@ -422,6 +422,22 @@ export namespace entities { } } + export class SerializedLinkedProcessedArea { + areaId: string; + previousId: string; + nextId: string; + + static createFrom(source: any = {}) { + return new SerializedLinkedProcessedArea(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.areaId = source["areaId"]; + this.previousId = source["previousId"]; + this.nextId = source["nextId"]; + } + } export class Session { project: Project; organization: Organization; @@ -481,6 +497,7 @@ export namespace ipc { export class GetDocumentsResponse { documents: entities.Document[]; groups: entities.Group[]; + contextGroups: entities.SerializedLinkedProcessedArea[]; static createFrom(source: any = {}) { return new GetDocumentsResponse(source); @@ -490,6 +507,7 @@ export namespace ipc { if ('string' === typeof source) source = JSON.parse(source); this.documents = this.convertValues(source["documents"], entities.Document); this.groups = this.convertValues(source["groups"], entities.Group); + this.contextGroups = this.convertValues(source["contextGroups"], entities.SerializedLinkedProcessedArea); } convertValues(a: any, classs: any, asMap: boolean = false): any { diff --git a/ipc/ContextGroup.go b/ipc/ContextGroup.go index 0af4db2..d38b407 100644 --- a/ipc/ContextGroup.go +++ b/ipc/ContextGroup.go @@ -1,22 +1,46 @@ package ipc import ( + contextGroup "textualize/core/ContextGroup" document "textualize/core/Document" "textualize/entities" + "textualize/storage" ) -func (c *Channel) RequestConnectAreaAsTailToNode(tailId string, headId string) bool { - processedAreaOfTail := document.GetProcessedAreaCollection().GetAreaById(tailId) - if processedAreaOfTail == nil { - return false +func (c *Channel) RequestConnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool { + processedAreaCollection := document.GetProcessedAreaCollection() + + ancestorArea := processedAreaCollection.GetAreaById(ancestorAreaId) + descendantArea := processedAreaCollection.GetAreaById(descendantAreaId) + + wasSuccessfulConnect := contextGroup.GetContextGroupCollection().ConnectProcessedAreas(*ancestorArea, *descendantArea) + if wasSuccessfulConnect { + wasSuccessfulWrite := c.RequestSaveContextGroupCollection() + return wasSuccessfulWrite } - - processedAreaOfHead := document.GetProcessedAreaCollection().GetAreaById(headId) - if processedAreaOfHead == nil { - return false - } - - entities.GetContextGroupCollection().ConnectAreaAsTailToNode(*processedAreaOfTail, *processedAreaOfHead) - - return true + return false +} + +func (c *Channel) GetSerializedContextGroups() []entities.SerializedLinkedProcessedArea { + contextGroupCollection := contextGroup.GetContextGroupCollection() + + serializedContextGroups := make([]entities.SerializedLinkedProcessedArea, 0) + for _, group := range contextGroupCollection.Groups { + serializedContextGroups = append(serializedContextGroups, group.Serialize()...) + } + + return serializedContextGroups +} + +func (c *Channel) RequestSaveContextGroupCollection() bool { + contextGroupCollection := contextGroup.GetContextGroupCollection() + projectName := c.GetCurrentSession().Project.Name + + serializedContextGroups := make([]entities.SerializedLinkedProcessedArea, 0) + for _, group := range contextGroupCollection.Groups { + serializedContextGroups = append(serializedContextGroups, group.Serialize()...) + } + + successfulWrite := storage.GetDriver().WriteContextGroupCollection(serializedContextGroups, projectName) + return successfulWrite } diff --git a/ipc/Documents.go b/ipc/Documents.go index 7087bb5..57d56c5 100644 --- a/ipc/Documents.go +++ b/ipc/Documents.go @@ -14,8 +14,9 @@ import ( ) type GetDocumentsResponse struct { - Documents []entities.Document `json:"documents"` - Groups []entities.Group `json:"groups"` + Documents []entities.Document `json:"documents"` + Groups []entities.Group `json:"groups"` + ContextGroups []entities.SerializedLinkedProcessedArea `json:"contextGroups"` } func (c *Channel) GetDocumentById(id string) entities.Document { @@ -26,10 +27,12 @@ func (c *Channel) GetDocumentById(id string) entities.Document { func (c *Channel) GetDocuments() GetDocumentsResponse { documents := document.GetDocumentCollection().Documents groups := document.GetGroupCollection().Groups + contextGroups := c.GetSerializedContextGroups() response := GetDocumentsResponse{ - Groups: make([]entities.Group, 0), - Documents: make([]entities.Document, 0), + Groups: make([]entities.Group, 0), + Documents: make([]entities.Document, 0), + ContextGroups: contextGroups, } for _, d := range documents { diff --git a/ipc/Session.go b/ipc/Session.go index 2b06d75..eb5a344 100644 --- a/ipc/Session.go +++ b/ipc/Session.go @@ -3,6 +3,7 @@ package ipc import ( app "textualize/core/App" consts "textualize/core/Consts" + contextGroup "textualize/core/ContextGroup" document "textualize/core/Document" session "textualize/core/Session" "textualize/entities" @@ -144,6 +145,7 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool { session.GetInstance().Project = foundProject + // Documents localDocumentCollection := storageDriver.ReadDocumentCollection(projectName) documentCount := len(localDocumentCollection.Documents) readableDocuments := make([]document.Entity, documentCount) @@ -155,6 +157,7 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool { ProjectId: foundProject.Id, }) + // Groups localGroupsCollection := storageDriver.ReadGroupCollection(projectName) groupCount := len(localGroupsCollection.Groups) readableGroups := make([]entities.Group, groupCount) @@ -167,6 +170,10 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool { Groups: readableGroups, }) + // Context Groups + localSerializedContextGroups := storageDriver.ReadContextGroupCollection(projectName) + contextGroup.SetContextGroupCollectionBySerialized(localSerializedContextGroups) + // Processed Texts localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName) areaCount := len(localProcessedAreaCollection.Areas) diff --git a/storage/Local/ContextGroupDriver.go b/storage/Local/ContextGroupDriver.go new file mode 100644 index 0000000..7f14fb2 --- /dev/null +++ b/storage/Local/ContextGroupDriver.go @@ -0,0 +1,22 @@ +package storage + +import ( + "encoding/json" + "textualize/entities" +) + +func (d LocalDriver) WriteContextGroupCollection(serializedContextGroups []entities.SerializedLinkedProcessedArea, projectName string) bool { + jsonData, _ := json.MarshalIndent(serializedContextGroups, "", " ") + writeError := WriteDataToAppDir(jsonData, "/projects/"+projectName+"/", "ContextGroups.json") + return writeError == nil +} + +func (d LocalDriver) ReadContextGroupCollection(projectName string) []entities.SerializedLinkedProcessedArea { + contextGroupCollectionData := make([]entities.SerializedLinkedProcessedArea, 0) + readError := AssignFileDataToStruct("/projects/"+projectName+"/ContextGroups.json", &contextGroupCollectionData) + if readError != nil { + return make([]entities.SerializedLinkedProcessedArea, 0) + } + + return contextGroupCollectionData +} diff --git a/storage/Storage.go b/storage/Storage.go index d31a7a4..bc96a82 100644 --- a/storage/Storage.go +++ b/storage/Storage.go @@ -19,6 +19,8 @@ type Driver interface { ReadProcessedTextCollection(string) entities.ProcessedTextCollection WriteProcessedUserMarkdownCollection(entities.ProcessedUserMarkdownCollection, string) bool ReadProcessedUserMarkdownCollection(string) entities.ProcessedUserMarkdownCollection + WriteContextGroupCollection([]entities.SerializedLinkedProcessedArea, string) bool + ReadContextGroupCollection(string) []entities.SerializedLinkedProcessedArea } var driverInstance Driver