package ultidraw import ( "encoding/json" "strings" "time" ) // UltiDrawDoc is the on-disk format for Excalidraw drawings in UltiDrive. type UltiDrawDoc struct { Type string `json:"type"` Version int `json:"version"` Source string `json:"source,omitempty"` Elements json.RawMessage `json:"elements"` AppState json.RawMessage `json:"appState,omitempty"` Files json.RawMessage `json:"files,omitempty"` YjsState string `json:"yjsState,omitempty"` UpdatedAt string `json:"updatedAt,omitempty"` } func ParseUltiDrawDoc(raw []byte) (UltiDrawDoc, error) { var doc UltiDrawDoc if err := json.Unmarshal(raw, &doc); err != nil { return UltiDrawDoc{}, err } if doc.Type == "" { doc.Type = "excalidraw" } if doc.Version == 0 { doc.Version = 2 } return doc, nil } func NewUltiDrawDoc(elements, appState, files json.RawMessage) UltiDrawDoc { if len(elements) == 0 { elements = json.RawMessage("[]") } if len(appState) == 0 { appState = json.RawMessage(`{"gridSize":null,"viewBackgroundColor":"#ffffff"}`) } if len(files) == 0 { files = json.RawMessage("{}") } return UltiDrawDoc{ Type: "excalidraw", Version: 2, Source: "https://ultidrive", Elements: elements, AppState: appState, Files: files, } } func (d UltiDrawDoc) Marshal() ([]byte, error) { d.UpdatedAt = time.Now().UTC().Format(time.RFC3339) return json.Marshal(d) } func ApplyUltiDrawPatch(existing UltiDrawDoc, raw []byte) (UltiDrawDoc, error) { var patch map[string]json.RawMessage if err := json.Unmarshal(raw, &patch); err != nil { return UltiDrawDoc{}, err } doc := existing if doc.Type == "" { doc = NewUltiDrawDoc(nil, nil, nil) } if v, ok := patch["elements"]; ok { doc.Elements = v } if v, ok := patch["appState"]; ok { doc.AppState = v } if v, ok := patch["files"]; ok { doc.Files = v } if v, ok := patch["yjsState"]; ok { doc.YjsState = strings.Trim(string(v), `"`) } return doc, nil } func isEmptyElements(elements json.RawMessage) bool { if len(elements) == 0 { return true } s := strings.TrimSpace(string(elements)) return s == "" || s == "[]" || s == "null" }