import { QuantumElementCreationOptions , QuantumElementType , QuantumElement , JsonType } from './document-element' ; import { Vector2 } from '../vectors' ; import { readonly , shallowReactive , shallowRef , ref , watch , reactive } from 'vue' ; import arrayUtils from '../array-utils' ; import { ScopeElementType } from './elements/scope-element' ; import type { ScopeElement } from './elements/scope-element' ; import { ExpressionElementType } from './elements/expression-element' ; import { watchImmediate } from '../reactivity-utils' ; import { deserializeOptions , DocumentOptions , serializeOptions } from './document-options' ; import pkg from '../../../package.json' ; type SerializedDocument = { /**
     * Used so that we can distinguish between different document versions/formats.
     */ version : string ; options : JsonType ; elements : JsonType [ ] ; } ; export type QuantumDocumentElementTypes < T extends readonly QuantumElementType [ ] = readonly QuantumElementType [ ] > = { [ key in T [ number ] [ 'typeName' ] ] : T [ number ] ; } & { [ 'scope-element' ] : typeof ScopeElementType ; } & { [ 'expression-element' ] : typeof ExpressionElementType ; } ; type GetQuantumElement < Type > = Type extends QuantumElementType < infer X > ? X : never ; /**
 * A top level document, containing a list of elements.
 */ export interface UseQuantumDocument < TElements extends QuantumDocumentElementTypes < readonly QuantumElementType [ ] > > { /**
     * Document options
     */ readonly options : DocumentOptions ; /**
     * Which elements the document contains
     */ readonly elementTypes : Readonly < TElements > ; /**
     * Shallow reactive elements array
     */ readonly elements : ReadonlyArray < QuantumElement > ; param typeName param options createElement < T extends keyof TElements > ( typeName , options ) : GetQuantumElement < TElements [ T ] > ; param element deleteElement ( element ) : void ; param position getElementAt ( position ) : QuantumElement | undefined ; param id param type getElementById < T extends keyof TElements > ( id , typeName ? ) : GetQuantumElement < TElements [ T ] > | undefined ; param type /**
     * Gets the selected elements
     */ getSelection ( ) : QuantumElement [ ] ; param elements setSelection ( ... elements ) : void ; /**
     * Sets the element focus
     */ setFocus ( element ? ) : void ; /**
     * Serialize a document
     */ serializeDocument ( ) : JsonType ; /**
     * Deserialize a document's elements
     */ deserializeDocument ( serializedData ) : void ; /**
     * Move specified Elements by specified distance
     */ moveElements ( elements , delta ) : void ; /**
     * Move selected Elements by specified distance
     */ moveSelectedElements ( delta , limit ? ) : void ; } /**
 * Keeps track of the element positions in an ordered list. Also takes care of setting the element-scopes.
 *
 * There is no "element-inserted" callback, instead elements can wait until their scope stops being `undefined`
 */ function useElementList ( ) { const elements = shallowReactive < QuantumElement [ ] > ( [ ] ) ; /** Watches an element's position. Returns a function to stop the watcher. */ function watchElement ( element ) { const stopWatcher = watchImmediate ( element . position , ( value ) => { let { index } = arrayUtils . getBinaryInsertIndex ( elements , ( a ) => a . position . value . compareTo ( value ) ) ; if ( arrayUtils . at ( elements , index ) === element ) { return ; } const prev = arrayUtils . at ( elements , index - 1 ) ; if ( prev ?. typeName == ScopeElementType . typeName ) { element . setScope ( prev as ScopeElement ) ; } else { element . setScope ( prev ?. scope . value ) ; } arrayUtils . remove ( elements , element ) ; elements . splice ( index , 0 , element ) ; } ) ; return ( ) => { stopWatcher ( ) ; element . setScope ( undefined ) ; arrayUtils . remove ( elements , element ) ; } ; } /** Gets an element at a given position, useful for when the user clicks somewhere in the document. */ function getElementAt ( position ) { const posX = position . x ; const posY = position . y ; for ( let i = elements . length - 1 ; i >= 0 ; i -- ) { let element = elements [ i ] ; let x = element . position . value . x ; let y = element . position . value . y ; if ( y <= posY && posY <= y + element . size . value . y && x <= posX && posX <= x + element . size . value . x ) { return element ; } } return undefined ; } return { elements , watchElement , getElementAt , } ; } function useElementSelection ( ) { const selectedElements = shallowReactive < Set < QuantumElement > > ( new Set ( ) ) ; function watchElement ( element ) { const stopHandle = watchImmediate ( element . selected , ( value ) => { if ( value ) { selectedElements . add ( element ) ; } else { selectedElements . delete ( element ) ; } } ) ; return ( ) => { element . selected . value = false ; stopHandle ( ) ; } ; } function setSelection ( ... elements ) { selectedElements . forEach ( ( e ) => e . setSelected ( false ) ) ; elements . forEach ( ( e ) => e . setSelected ( true ) ) ; } return { selectedElements , setSelection , watchElement , } ; } function useElementFocus ( ) { const focusedElement = shallowRef < QuantumElement > ( ) ; function watchElement ( element ) { const stopHandle = watchImmediate ( element . focused , ( value ) => { if ( value ) { if ( focusedElement . value ?. focused ) { focusedElement . value . setFocused ( false ) ; } focusedElement . value = element ; } else { if ( focusedElement . value == element ) { focusedElement . value = undefined ; } } } ) ; return ( ) => { element . focused . value = false ; stopHandle ( ) ; } ; } function setFocus ( element ? ) { if ( element ) { element . setFocused ( true ) ; } else { focusedElement . value ?. setFocused ( false ) ; } } return { focusedElement , watchElement , setFocus , } ; } param elementTypes export function useDocument < TElements extends QuantumDocumentElementTypes < readonly QuantumElementType [ ] > > ( elementTypes ) { const options = reactive < DocumentOptions > ( { gridCellSize : readonly ( new Vector2 ( 20 , 20 ) ) , paperStyle : 'standard' , paperSize : 'A4' , } ) ; const elementRemoveCallbacks = new Map < string , ( ) => void > ( ) ; const elementList = useElementList ( ) ; const elementSelection = useElementSelection ( ) ; const elementFocus = useElementFocus ( ) ; const rootScope = createElement ( ScopeElementType . typeName , { position : Vector2 . zero , size : Vector2 . zero , } ) ; function addElement < T extends QuantumElement > ( element ) { let stopHandles = [ elementList . watchElement ( element ) , elementSelection . watchElement ( element ) , elementFocus . watchElement ( element ) ] ; elementRemoveCallbacks . set ( element . id , ( ) => { stopHandles . forEach ( ( stopHandle ) => stopHandle ( ) ) ; } ) ; return element ; } function createElement < T extends keyof TElements > ( typeName , options ) { let elementType = elementTypes [ typeName ] ; if ( ! elementType ) throw new Error ( `Unknown element type ${ typeName }` ) ; const element = new elementType . elementType ( options ) ; addElement ( element ) ; return element as any ; } function deleteElement ( element ) { let removeCallback = elementRemoveCallbacks . get ( element . id ) ; if ( removeCallback ) { removeCallback ( ) ; elementRemoveCallbacks . delete ( element . id ) ; } } function getElementById < T extends keyof TElements > ( id , typeName ? ) { let element = elementList . elements . find ( ( e ) => e . id == id ) ; if ( element && typeName && element . typeName != typeName ) { throw new Error ( `Wrong type, passed ${ typeName } but element has ${ element . typeName }` ) ; } return element as any ; } function getElementsByType < T extends keyof TElements > ( typeName ) { let elements = elementList . elements . filter ( ( e ) => e . typeName == typeName ) ; return elements as any [ ] ; } function serializeDocument ( ) { let serializedData = { version : pkg . version , options : serializeOptions ( options ) , elements : [ ] , } ; elementList . elements . forEach ( ( element ) => { let elementType = elementTypes [ element . typeName ] ; serializedData . elements . push ( elementType . serializeElement ( element ) ) ; } ) ; return serializedData ; } function deserializeDocument ( serializedData ) { if ( serializedData ?. options ) { const deserializedOptions = deserializeOptions ( serializedData ?. options ) ; options . gridCellSize = deserializedOptions . gridCellSize ?? options . gridCellSize ; options . paperStyle = deserializedOptions . paperStyle ?? options . paperStyle ; options . paperSize = deserializedOptions . paperSize ?? options . paperSize ; } serializedData ?. elements ?. forEach ( ( elementData ) => { let elementType = elementTypes [ ( elementData as any ) . typeName ] ; if ( ! elementType ) { console . warn ( 'Element is missing its type' , elementData ) ; } const { element , onAddedCallback } = elementType . deserializeElement ( elementData ) ; addElement ( element ) ; onAddedCallback ( ) ; } ) ; } function moveElements ( elements , delta , limit ? ) { let limited = false ; elements . forEach ( ( element ) => { let newPos = element ?. position . value . add ( delta ) ; if ( limit ) { if ( newPos . x < 0 || newPos . y < 0 || limit . subtract ( newPos . add ( element . size . value ) ) . x < 0 || limit . subtract ( newPos . add ( element . size . value ) ) . y < 0 ) { limited = true ; } } } ) ; if ( limited ) return ; elements . forEach ( ( element ) => { let newPos = element ?. position . value . add ( delta ) ; if ( newPos ) element ?. setPosition ( newPos ) ; } ) ; } function moveSelectedElements ( delta , limit ? ) { moveElements ( elementSelection . selectedElements , delta , limit ) ; } return { options , elementTypes : elementTypes , elements : elementList . elements , createElement , deleteElement , getElementAt : elementList . getElementAt , getElementById , getSelection : ( ) => [ ... elementSelection . selectedElements ] , setSelection : elementSelection . setSelection , setFocus : elementFocus . setFocus , moveElements , moveSelectedElements , serializeDocument , deserializeDocument , } ; }