PageSourceSearch

https://embed-goodfire-attr-graph.netlify.app/attribution_graph/gridsnap/init-gridsnap.js

js embed-goodfire-attr-graph.netlify.app collected 2026-10-03 10:41:44 UTC 5,440 bytes, 190 lines download raw bytes

1window.initGridsnap = function({
2  gridData = [],
3  maxX = null,
4  isFullScreenY = true,
5  pad = 10,
6  gridSizeY = 80,
7  isFillContainer,
8  sel = d3.select('.gridsnap'),
9  repositionFn = null,
10  serializedGrid = '',
11} = {}){
12  var gridsnap = {gridData, serializeGrid, deserializeGrid}
13
14  gridData.forEach((d, i) => {
15    d.next = {...d.cur}
16    d.class = d.class === undefined ? i : d.class
17  })
18  
19  var maxX = maxX || d3.max(gridData, d => d.cur.x + d.cur.w)
20  function calcgridSizeX(){ 
21    return (sel.node().offsetWidth)/maxX
22  }
23  var gridSizeX = calcgridSizeX()
24  
25  // TODO: bubble events first
26  function calcGridSizeY() {
27    if (!isFillContainer) return gridSizeY
28    return sel.node().offsetHeight/(d3.max(gridData, d => d.cur.y + d.cur.h) || 1)
29  }
30  
31  gridSizeY = calcGridSizeY()
32  
33  var resizeKey = 'resize.gridsnap' + serializedGrid
34  d3.select(window).on(resizeKey, util.throttle(() => {
35    var newX = calcgridSizeX()
36    var newY = calcGridSizeY()
37    if (newX == gridSizeX && newY == gridSizeY) return
38
39    gridSizeX = newX
40    gridSizeY = newY
41    renderPositions()
42    
43    
44    gridItemSel.each(d => d.resizeFn?.())
45  }, 500))
46
47  var gridsnapSel = sel.html('').append('div.gridsnap-container')
48
49  var gridItemSel = gridsnapSel.appendMany('div.grid-item', gridData)
50    .each(function(d){ d.sel = d3.select(this).append('div.grid-contents').classed(d.class, 1) })
51
52  gridItemSel.append('div.move-handle')
53    .text('✣')
54    .call(makeDragFn(false))
55  gridItemSel.append('div.resize-handle')
56    .text('↘')
57    .call(makeDragFn(true))
58
59  var previewSel = gridsnapSel.append('div.preview.grid-item')
60
61  function makeDragFn(isResize) {
62    return d3.drag()
63      .subject((ev, d) => ({
64        x: (d.cur.x + (isResize ? d.cur.w : 0))*gridSizeX,
65        y: (d.cur.y + (isResize ? d.cur.h : 0))*gridSizeY
66      }))
67      .container(function(){ return this.parentNode.parentNode })
68      .on('start', function(ev, d){
69        gridData.forEach(d => d.next = {...d.cur})
70        d.dragStart = {...d.cur}
71
72        gridsnapSel.classed('dragging', 1)
73        d3.select(this.parentNode).classed('dragging', 1)
74        previewSel.st({'display': ''})
75      })
76      .on('end', (ev, d) => {
77        gridData.forEach(d => d.cur = {...d.next})
78
79        gridsnapSel.classed('dragging', 0)
80        gridItemSel.classed('dragging', 0)
81        previewSel.st({'display': 'none'})
82        renderPositions()
83
84        if (isResize) d.resizeFn?.()
85      })
86      .on('drag', isResize ? resize : drag)
87
88    function drag(ev, d) {
89      d.cur.x = ev.x/gridSizeX
90      d.cur.y = ev.y/gridSizeY
91
92      pushGrid(d)
93      renderPositions(d)
94    }
95
96    function resize(ev, d) {
97      d.cur.x = d.dragStart.x
98      d.cur.y = d.dragStart.y
99
100      d.cur.w = ev.x/gridSizeX - d.dragStart.x
101      d.cur.h = ev.y/gridSizeY - d.dragStart.y
102      if (d.cur.w < 0) {
103        d.cur.x += d.cur.w
104        d.cur.w = d.dragStart.w - d.cur.w
105      }
106      if (d.cur.h < 0) {
107        d.cur.y += d.cur.h
108        d.cur.h = d.dragStart.h - d.cur.h
109      }
110
111      pushGrid(d)
112      renderPositions(d)
113    }
114  }
115
116
117  function pushGrid(active) {
118    if (active) active.next = snapToGrid(active.cur)
119
120    var sortedGridData = d3.sort(gridData, d => d != active)
121    sortedGridData = d3.sort(sortedGridData, d => d == active ? d.cur.y : d.next.y)
122
123    var topArray = d3.range(maxX).map(d => 0)
124    sortedGridData.forEach(d => {
125      var {x, y, w, h} = d.next
126      d.next.y = d3.max(d3.range(w), i => topArray[x + i])
127      d3.range(w).forEach(i => topArray[x + i] = d.next.y + h)
128    })
129
130    function snapToGrid({x, y, w, h}) {
131      var rv = {x: Math.max(0, Math.round(x)), y: Math.max(0, Math.round(y)), w: Math.max(1, Math.round(w)), h: Math.max(1, Math.round(h))}
132      if (rv.x + rv.w > maxX) rv.x = Math.max(0, maxX - rv.w)
133      return rv
134    }
135  }
136
137  function renderPositions(active){
138    gridItemSel.call(renderGridItem, 'next')
139
140    if (active){
141      gridItemSel.filter(d => d == active).call(renderGridItem, 'cur')
142      previewSel.datum(active).call(renderGridItem, 'next')
143    } else{
144      if (!isFillContainer){
145        var maxY = Math.max(maxY, d3.max(gridData, d => d.next.y + d.next.h))
146        gridsnapSel.st({height: Math.max(isFullScreenY ? window.innerHeight : 0, maxY*gridSizeY + pad
146) + 'px'})
147      }
148    }
149
150    repositionFn?.(gridsnap)
151
152    function renderGridItem(itemSel, key) {
153      itemSel
154        .translate(d => [d[key].x*gridSizeX + pad/2, d[key].y*gridSizeY + pad/2].map(Math.round))
155        .st({
156          width: d => Math.round(Math.max(0, d[key].w*gridSizeX - pad)), // negative sizes bug out
157          height: d => Math.round(Math.max(0, d[key].h*gridSizeY - pad))
158        })
159    }
160  }
161
162  function serializeGrid(){
163    return gridData.map(d => {
164      var {x, y, w, h} = d.cur
165      return `${d.class}${[x, y, w, h].map(d3.format('02d')).join('')}`
166    }).join('_')
167  }
168
169  function deserializeGrid(serializedGrid){
170    serializedGrid?.split('_').forEach(str => {
171      var match = str.match(/^([\w-]+)(\d{8})$/)
172      if (!match) return
173      var [_, className, coords] = match
174
175      var [x, y, w, h] = d3.range(4).map(i => +coords.substr(i*2, 2))
176      var gridItem = gridData.find(d => d.class == className)
177      if (gridItem) gridItem.next = {x, y, w, h}
178    })
179
180    pushGrid()
181    gridData.forEach(d => d.cur = {...d.next})
182    renderPositions()
183  }
184
185  deserializeGrid(serializedGrid)
186
187  return gridsnap
188}
189
190window.init?.()

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.