File size: 3,141 Bytes
cfaaa6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { createBrowserId } from './ids.js';
import { createComponentSubgraph, stripRuntimeFromNode } from './nodeRegistry.js';

export const COMPONENT_LIBRARY_KEY = 'nodes_ui_flow_component_library_v1';

function clonePlainData(value) {
  return JSON.parse(JSON.stringify(value));
}

function normalizeTemplate(template = {}) {
  return {
    id: typeof template.id === 'string' && template.id ? template.id : createBrowserId('component_template'),
    title:
      typeof template.title === 'string' && template.title.trim()
        ? template.title.trim()
        : 'Component',
    description: typeof template.description === 'string' ? template.description : '',
    subgraph:
      template.subgraph && typeof template.subgraph === 'object'
        ? clonePlainData(template.subgraph)
        : clonePlainData(createComponentSubgraph()),
    savedAt: typeof template.savedAt === 'number' ? template.savedAt : Date.now(),
  };
}

export function normalizeComponentTemplates(templates = []) {
  if (!Array.isArray(templates)) {
    return [];
  }

  return templates
    .map((template) => normalizeTemplate(template))
    .sort((left, right) => right.savedAt - left.savedAt);
}

export function mergeComponentTemplates(...sources) {
  const merged = [];
  const seen = new Set();

  sources
    .flatMap((source) => normalizeComponentTemplates(source))
    .forEach((template) => {
      if (seen.has(template.id)) {
        return;
      }

      seen.add(template.id);
      merged.push(template);
    });

  return merged;
}

export function loadComponentLibrary() {
  if (typeof window === 'undefined') {
    return [];
  }

  try {
    const raw = window.localStorage.getItem(COMPONENT_LIBRARY_KEY);
    if (!raw) {
      return [];
    }

    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) {
      return [];
    }

    return normalizeComponentTemplates(parsed);
  } catch (error) {
    console.error('Failed to load component library:', error);
    return [];
  }
}

export function persistComponentLibrary(templates) {
  if (typeof window === 'undefined') {
    return;
  }

  const normalized = normalizeComponentTemplates(templates);

  window.localStorage.setItem(COMPONENT_LIBRARY_KEY, JSON.stringify(normalized));
}

export function upsertComponentTemplateFromNode(templates, node) {
  const persistedNode = stripRuntimeFromNode(node);
  const nextTemplate = normalizeTemplate({
    id: persistedNode.data?.libraryId || createBrowserId('component_template'),
    title: persistedNode.data?.title,
    description: persistedNode.data?.description,
    subgraph: persistedNode.data?.subgraph,
    savedAt: Date.now(),
  });

  const nextTemplates = [
    nextTemplate,
    ...templates.filter((template) => template.id !== nextTemplate.id),
  ];

  return {
    template: nextTemplate,
    templates: nextTemplates,
  };
}

export function buildComponentNodeDataFromTemplate(template) {
  const normalized = normalizeTemplate(template);

  return clonePlainData({
    title: normalized.title,
    description: normalized.description,
    subgraph: normalized.subgraph,
    libraryId: normalized.id,
  });
}