matterviz 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DraggablePanel.svelte +73 -57
- package/dist/FilePicker.svelte +263 -0
- package/dist/FilePicker.svelte.d.ts +15 -0
- package/dist/Icon.svelte +7 -3
- package/dist/SettingsSection.svelte +116 -0
- package/dist/SettingsSection.svelte.d.ts +11 -0
- package/dist/api/mp.d.ts +6 -0
- package/dist/api/mp.js +22 -0
- package/dist/api/optimade.d.ts +43 -0
- package/dist/api/optimade.js +133 -0
- package/dist/app.css +15 -3
- package/dist/composition/BarChart.svelte +1 -1
- package/dist/composition/BubbleChart.svelte +1 -1
- package/dist/composition/Composition.svelte +1 -4
- package/dist/composition/PieChart.svelte +1 -1
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/parse.d.ts +9 -9
- package/dist/composition/parse.js +119 -214
- package/dist/element/ElementHeading.svelte +2 -2
- package/dist/element/ElementHeading.svelte.d.ts +1 -0
- package/dist/element/ElementPhoto.svelte +3 -3
- package/dist/element/ElementStats.svelte +3 -4
- package/dist/element/ElementStats.svelte.d.ts +1 -1
- package/dist/element/ElementTile.svelte +3 -3
- package/dist/element/ElementTile.svelte.d.ts +2 -2
- package/dist/element/Nucleus.svelte +0 -9
- package/dist/icons.d.ts +34 -8
- package/dist/icons.js +65 -8
- package/dist/index.d.ts +12 -18
- package/dist/index.js +3 -0
- package/dist/io/decompress.d.ts +6 -2
- package/dist/io/decompress.js +27 -27
- package/dist/io/export.d.ts +0 -6
- package/dist/io/export.js +18 -149
- package/dist/{mp-api.d.ts → io/fetch.d.ts} +0 -2
- package/dist/{mp-api.js → io/fetch.js} +2 -8
- package/dist/io/index.d.ts +0 -2
- package/dist/io/index.js +63 -63
- package/dist/labels.d.ts +3 -3
- package/dist/labels.js +1 -1
- package/dist/math.d.ts +3 -1
- package/dist/math.js +25 -21
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +3 -3
- package/dist/periodic-table/TableInset.svelte +2 -2
- package/dist/plot/ColorBar.svelte +2 -4
- package/dist/plot/Histogram.svelte +212 -128
- package/dist/plot/Histogram.svelte.d.ts +13 -1
- package/dist/plot/HistogramControls.svelte +203 -93
- package/dist/plot/HistogramControls.svelte.d.ts +6 -1
- package/dist/plot/Line.svelte +1 -1
- package/dist/plot/Line.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +11 -13
- package/dist/plot/ScatterPlot.svelte +16 -13
- package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +221 -198
- package/dist/plot/ScatterPlotControls.svelte.d.ts +2 -2
- package/dist/plot/index.js +3 -4
- package/dist/settings.d.ts +142 -0
- package/dist/settings.js +617 -0
- package/dist/state.svelte.d.ts +2 -2
- package/dist/structure/Bond.svelte +50 -9
- package/dist/structure/Bond.svelte.d.ts +10 -2
- package/dist/structure/CanvasTooltip.svelte +25 -0
- package/dist/structure/CanvasTooltip.svelte.d.ts +10 -0
- package/dist/structure/Lattice.svelte +30 -7
- package/dist/structure/Lattice.svelte.d.ts +3 -2
- package/dist/structure/Structure.svelte +176 -101
- package/dist/structure/Structure.svelte.d.ts +15 -3
- package/dist/structure/StructureControls.svelte +650 -342
- package/dist/structure/StructureControls.svelte.d.ts +4 -10
- package/dist/structure/StructureInfoPanel.svelte +7 -83
- package/dist/structure/StructureScene.svelte +203 -139
- package/dist/structure/StructureScene.svelte.d.ts +16 -6
- package/dist/structure/Vector.svelte +2 -2
- package/dist/structure/bonding.d.ts +22 -9
- package/dist/structure/bonding.js +224 -112
- package/dist/structure/export.d.ts +10 -0
- package/dist/structure/export.js +339 -0
- package/dist/structure/index.d.ts +27 -25
- package/dist/structure/index.js +2 -22
- package/dist/{io → structure}/parse.d.ts +9 -2
- package/dist/{io → structure}/parse.js +587 -262
- package/dist/structure/pbc.js +55 -36
- package/dist/structure/supercell.d.ts +8 -0
- package/dist/structure/supercell.js +159 -0
- package/dist/theme/ThemeControl.svelte +9 -8
- package/dist/theme/themes.js +2 -2
- package/dist/trajectory/Trajectory.svelte +363 -170
- package/dist/trajectory/Trajectory.svelte.d.ts +16 -13
- package/dist/trajectory/TrajectoryInfoPanel.svelte +150 -155
- package/dist/trajectory/index.d.ts +34 -6
- package/dist/trajectory/index.js +102 -33
- package/dist/trajectory/parse.d.ts +35 -6
- package/dist/trajectory/parse.js +776 -522
- package/dist/trajectory/plotting.d.ts +13 -8
- package/dist/trajectory/plotting.js +316 -280
- package/package.json +21 -19
- package/readme.md +30 -18
|
@@ -78,14 +78,32 @@ function handle_click_outside(event) {
|
|
|
78
78
|
const is_inside_panel = panel_div &&
|
|
79
79
|
(target === panel_div || panel_div.contains(target));
|
|
80
80
|
if (!is_toggle_button && !is_inside_panel && !has_been_dragged &&
|
|
81
|
-
!currently_dragging)
|
|
81
|
+
!currently_dragging)
|
|
82
82
|
close_panel();
|
|
83
|
-
}
|
|
84
83
|
}
|
|
85
84
|
// Button click handler
|
|
86
|
-
|
|
85
|
+
const handle_button_click = (action) => (event) => {
|
|
87
86
|
event.stopPropagation();
|
|
88
87
|
action();
|
|
88
|
+
};
|
|
89
|
+
// Debounced resize handler for better performance
|
|
90
|
+
let resize_timeout = $state(undefined);
|
|
91
|
+
function handle_resize() {
|
|
92
|
+
if (!show || has_been_dragged || currently_dragging)
|
|
93
|
+
return;
|
|
94
|
+
if (resize_timeout)
|
|
95
|
+
clearTimeout(resize_timeout);
|
|
96
|
+
const current_timeout = setTimeout(() => {
|
|
97
|
+
if (resize_timeout !== current_timeout)
|
|
98
|
+
return;
|
|
99
|
+
if (show && toggle_panel_btn && !has_been_dragged && panel_div) {
|
|
100
|
+
const pos = calculate_position();
|
|
101
|
+
initial_position = pos;
|
|
102
|
+
panel_div.style.left = pos.left;
|
|
103
|
+
panel_div.style.top = pos.top;
|
|
104
|
+
}
|
|
105
|
+
}, 50); // Debounce resize events
|
|
106
|
+
resize_timeout = current_timeout;
|
|
89
107
|
}
|
|
90
108
|
// Position panel when shown
|
|
91
109
|
$effect(() => {
|
|
@@ -97,20 +115,19 @@ $effect(() => {
|
|
|
97
115
|
left: pos.left,
|
|
98
116
|
top: pos.top,
|
|
99
117
|
right: `auto`,
|
|
100
|
-
bottom: `auto`,
|
|
101
118
|
});
|
|
102
119
|
}
|
|
103
120
|
}
|
|
104
121
|
});
|
|
105
122
|
</script>
|
|
106
123
|
|
|
107
|
-
<svelte:window onkeydown={on_keydown} />
|
|
124
|
+
<svelte:window onkeydown={on_keydown} onresize={handle_resize} />
|
|
108
125
|
<svelte:document onclick={handle_click_outside} />
|
|
109
126
|
|
|
110
127
|
{#if show_panel}
|
|
111
128
|
<button
|
|
112
129
|
bind:this={toggle_panel_btn}
|
|
113
|
-
onclick={
|
|
130
|
+
onclick={handle_button_click(custom_toggle || toggle_panel)}
|
|
114
131
|
aria-expanded={show}
|
|
115
132
|
aria-controls="draggable-panel"
|
|
116
133
|
{...toggle_props}
|
|
@@ -137,32 +154,30 @@ $effect(() => {
|
|
|
137
154
|
{...panel_props}
|
|
138
155
|
class="draggable-panel {show ? `panel-open` : ``} {panel_props.class ?? ``}"
|
|
139
156
|
>
|
|
140
|
-
<div class="
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
/>
|
|
165
|
-
</div>
|
|
157
|
+
<div class="control-buttons">
|
|
158
|
+
{#if show_control_buttons}
|
|
159
|
+
<button
|
|
160
|
+
class="reset-button"
|
|
161
|
+
onclick={handle_button_click(reset_position)}
|
|
162
|
+
title="Reset panel position"
|
|
163
|
+
aria-label="Reset panel position"
|
|
164
|
+
>
|
|
165
|
+
<Icon icon="Reset" style="width: 1.25em; height: 1.25em" />
|
|
166
|
+
</button>
|
|
167
|
+
<button
|
|
168
|
+
class="close-button"
|
|
169
|
+
onclick={handle_button_click(close_panel)}
|
|
170
|
+
title="Close panel"
|
|
171
|
+
aria-label="Close panel"
|
|
172
|
+
>
|
|
173
|
+
<Icon icon="Cross" style="width: 1.25em; height: 1.25em" />
|
|
174
|
+
</button>
|
|
175
|
+
{/if}
|
|
176
|
+
<Icon
|
|
177
|
+
icon="DragIndicator"
|
|
178
|
+
class="drag-handle"
|
|
179
|
+
style="width: 1.25em; height: 1.25em"
|
|
180
|
+
/>
|
|
166
181
|
</div>
|
|
167
182
|
|
|
168
183
|
{@render children()}
|
|
@@ -178,7 +193,7 @@ $effect(() => {
|
|
|
178
193
|
border-radius: var(--panel-toggle-border-radius, 3pt);
|
|
179
194
|
background-color: transparent;
|
|
180
195
|
transition: background-color 0.2s;
|
|
181
|
-
font-size: clamp(1em,
|
|
196
|
+
font-size: var(--panel-toggle-font-size, clamp(1.1em, calc(1cqw + 1cqh), 1.4em));
|
|
182
197
|
}
|
|
183
198
|
button.panel-toggle:hover {
|
|
184
199
|
background-color: color-mix(in srgb, currentColor 8%, transparent);
|
|
@@ -190,7 +205,7 @@ $effect(() => {
|
|
|
190
205
|
border-radius: 6px;
|
|
191
206
|
padding: var(--panel-padding, 1ex);
|
|
192
207
|
box-sizing: border-box;
|
|
193
|
-
z-index: 10;
|
|
208
|
+
z-index: var(--panel-z-index, 10);
|
|
194
209
|
display: grid;
|
|
195
210
|
gap: 4pt;
|
|
196
211
|
text-align: left;
|
|
@@ -198,7 +213,8 @@ $effect(() => {
|
|
|
198
213
|
transition: opacity 0.3s, background-color 0.3s, border-color 0.3s, box-shadow 0.3s;
|
|
199
214
|
width: 28em;
|
|
200
215
|
max-width: 90cqw;
|
|
201
|
-
overflow:
|
|
216
|
+
overflow-x: hidden;
|
|
217
|
+
overflow-y: auto;
|
|
202
218
|
max-height: calc(100vh - 3em);
|
|
203
219
|
pointer-events: auto;
|
|
204
220
|
}
|
|
@@ -210,14 +226,14 @@ $effect(() => {
|
|
|
210
226
|
}
|
|
211
227
|
/* Panel content styling */
|
|
212
228
|
.draggable-panel :global(h4) {
|
|
213
|
-
margin:
|
|
214
|
-
font-size: 0.
|
|
229
|
+
margin: 2pt 0;
|
|
230
|
+
font-size: 0.95em;
|
|
215
231
|
}
|
|
216
232
|
.draggable-panel :global(hr) {
|
|
217
233
|
border: none;
|
|
218
234
|
background: var(--panel-hr-bg, rgba(255, 255, 255, 0.1));
|
|
219
|
-
margin: 0;
|
|
220
|
-
height:
|
|
235
|
+
margin: 4pt 0;
|
|
236
|
+
height: 1px;
|
|
221
237
|
}
|
|
222
238
|
.draggable-panel :global(label) {
|
|
223
239
|
display: flex;
|
|
@@ -227,6 +243,7 @@ $effect(() => {
|
|
|
227
243
|
.draggable-panel :global(input[type='text']) {
|
|
228
244
|
flex: 1;
|
|
229
245
|
padding: 4px 6px;
|
|
246
|
+
margin: var(--panel-input-margin, 0 0 0 5pt);
|
|
230
247
|
}
|
|
231
248
|
.draggable-panel :global(input[type='text'].invalid) {
|
|
232
249
|
border-color: var(--error-color, #ff6b6b);
|
|
@@ -237,11 +254,9 @@ $effect(() => {
|
|
|
237
254
|
box-shadow: 0 0 0 2px rgba(255, 107, 107, 0.2);
|
|
238
255
|
}
|
|
239
256
|
.draggable-panel :global(input[type='range']) {
|
|
240
|
-
margin-left:
|
|
257
|
+
margin-left: 4pt;
|
|
241
258
|
width: 100px;
|
|
242
259
|
flex-shrink: 0;
|
|
243
|
-
}
|
|
244
|
-
.draggable-panel :global(input[type='range']) {
|
|
245
260
|
flex: 1;
|
|
246
261
|
min-width: 60px;
|
|
247
262
|
}
|
|
@@ -282,27 +297,28 @@ $effect(() => {
|
|
|
282
297
|
gap: 8pt;
|
|
283
298
|
align-items: center;
|
|
284
299
|
}
|
|
285
|
-
.draggable-panel :global(.panel-
|
|
300
|
+
.draggable-panel :global(.panel-grid) {
|
|
301
|
+
display: grid;
|
|
302
|
+
gap: 8pt;
|
|
303
|
+
align-items: center;
|
|
304
|
+
}
|
|
305
|
+
.draggable-panel :global(label:has(input[type='range'])) {
|
|
286
306
|
flex: 1;
|
|
287
307
|
}
|
|
288
|
-
|
|
289
|
-
.draggable-panel .panel-header {
|
|
308
|
+
.draggable-panel .control-buttons {
|
|
290
309
|
display: flex;
|
|
291
310
|
justify-content: flex-end;
|
|
292
311
|
align-items: center;
|
|
293
|
-
position:
|
|
294
|
-
top:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
z-index: 10;
|
|
299
|
-
pointer-events: none; /* Allow events to pass through to children */
|
|
300
|
-
}
|
|
301
|
-
.draggable-panel .control-buttons {
|
|
302
|
-
display: flex;
|
|
312
|
+
position: sticky;
|
|
313
|
+
top: 0;
|
|
314
|
+
right: 0;
|
|
315
|
+
height: 0;
|
|
316
|
+
/* Cancel the 12 pt top/bottom padding without relying on width-based percentages */
|
|
303
317
|
gap: 5px;
|
|
304
|
-
|
|
305
|
-
|
|
318
|
+
padding: 12pt 3pt;
|
|
319
|
+
margin-bottom: calc(-2 * 12pt);
|
|
320
|
+
box-sizing: border-box;
|
|
321
|
+
justify-self: end;
|
|
306
322
|
}
|
|
307
323
|
.draggable-panel :global(.drag-handle) {
|
|
308
324
|
width: 1.3em;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
<script lang="ts">let { files, active_files = [], show_category_filters = false, category_labels = {}, on_drag_start, on_drag_end, type_mapper, file_type_colors = {
|
|
2
|
+
cif: `rgba(100, 149, 237, 0.8)`,
|
|
3
|
+
xyz: `rgba(50, 205, 50, 0.8)`,
|
|
4
|
+
extxyz: `rgba(50, 205, 50, 0.8)`,
|
|
5
|
+
poscar: `rgba(255, 140, 0, 0.8)`,
|
|
6
|
+
json: `rgba(138, 43, 226, 0.8)`,
|
|
7
|
+
traj: `rgba(255, 192, 203, 0.8)`,
|
|
8
|
+
hdf5: `rgba(255, 69, 0, 0.8)`,
|
|
9
|
+
gz: `rgba(169, 169, 169, 0.8)`,
|
|
10
|
+
md: `rgba(255, 215, 0, 0.8)`,
|
|
11
|
+
yaml: `rgba(255, 0, 255, 0.8)`,
|
|
12
|
+
xdatcar: `rgba(255, 215, 0, 0.8)`,
|
|
13
|
+
}, ...rest } = $props();
|
|
14
|
+
let active_category_filter = $state(null);
|
|
15
|
+
let active_type_filter = $state(null);
|
|
16
|
+
// Helper function to get the base file type (removing .gz extension)
|
|
17
|
+
const get_base_file_type = (filename) => {
|
|
18
|
+
// Use custom type mapper if provided
|
|
19
|
+
if (type_mapper)
|
|
20
|
+
return type_mapper(filename);
|
|
21
|
+
let base_name = filename.toLowerCase();
|
|
22
|
+
// Remove .gz extension if present
|
|
23
|
+
if (base_name.endsWith(`.gz`))
|
|
24
|
+
base_name = base_name.slice(0, -3);
|
|
25
|
+
return base_name.split(`.`).pop() || `file`;
|
|
26
|
+
};
|
|
27
|
+
// Filter files based on active filters
|
|
28
|
+
let filtered_files = $derived(files.filter((file) => {
|
|
29
|
+
if (active_category_filter && file.category) {
|
|
30
|
+
return file.category === active_category_filter;
|
|
31
|
+
}
|
|
32
|
+
if (active_type_filter) {
|
|
33
|
+
const normalized_type = get_base_file_type(file.name);
|
|
34
|
+
return normalized_type === active_type_filter;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}));
|
|
38
|
+
const toggle_filter = (kind, filter) => {
|
|
39
|
+
if (kind === `category`) {
|
|
40
|
+
active_category_filter = active_category_filter === filter ? null : filter;
|
|
41
|
+
active_type_filter = null;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
active_type_filter = active_type_filter === filter ? null : filter;
|
|
45
|
+
active_category_filter = null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const handle_drag_start = (file) => (event) => {
|
|
49
|
+
const file_url = file.url || file.name; // Get the URL to drag (falling back to name)
|
|
50
|
+
const payload = JSON.stringify({
|
|
51
|
+
name: file.name,
|
|
52
|
+
url: file_url,
|
|
53
|
+
type: file.type || get_base_file_type(file.name),
|
|
54
|
+
category: file.category,
|
|
55
|
+
});
|
|
56
|
+
// Set file data as JSON for applications that can handle it
|
|
57
|
+
event.dataTransfer?.setData(`application/json`, payload);
|
|
58
|
+
// Also set plain text as fallback for external applications
|
|
59
|
+
event.dataTransfer?.setData(`text/plain`, file_url);
|
|
60
|
+
on_drag_start?.(file, event);
|
|
61
|
+
};
|
|
62
|
+
// Get unique file types for format filters
|
|
63
|
+
let uniq_formats = $derived([...new Set(files.map((file) => get_base_file_type(file.name)))].sort());
|
|
64
|
+
// Get unique category types for category filters
|
|
65
|
+
let uniq_categories = $derived(show_category_filters
|
|
66
|
+
? [...new Set(files.map((file) => file.category))].sort().filter(Boolean)
|
|
67
|
+
: []);
|
|
68
|
+
export {};
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<div class="file-picker" {...rest}>
|
|
72
|
+
<div class="legend">
|
|
73
|
+
{#if show_category_filters}
|
|
74
|
+
{#each uniq_categories as category (category)}
|
|
75
|
+
{@const is_active = active_category_filter === category}
|
|
76
|
+
<span
|
|
77
|
+
class="legend-item"
|
|
78
|
+
class:active={is_active}
|
|
79
|
+
onclick={() => category && toggle_filter(`category`, category)}
|
|
80
|
+
onkeydown={(evt) =>
|
|
81
|
+
(evt.key === `Enter` || evt.key === ` `) &&
|
|
82
|
+
category &&
|
|
83
|
+
toggle_filter(`category`, category)}
|
|
84
|
+
role="button"
|
|
85
|
+
tabindex="0"
|
|
86
|
+
title="Filter to show only {category}"
|
|
87
|
+
>
|
|
88
|
+
{(category && category_labels[category]) || category}
|
|
89
|
+
</span>
|
|
90
|
+
{/each}
|
|
91
|
+
{#if uniq_categories.length > 0 && uniq_formats.length > 0} {/if}
|
|
92
|
+
{/if}
|
|
93
|
+
|
|
94
|
+
{#each uniq_formats as format (format)}
|
|
95
|
+
{@const is_active = active_type_filter === format}
|
|
96
|
+
<span
|
|
97
|
+
class="legend-item format-item"
|
|
98
|
+
class:active={is_active}
|
|
99
|
+
onclick={() => toggle_filter(`type`, format)}
|
|
100
|
+
onkeydown={(evt) =>
|
|
101
|
+
(evt.key === `Enter` || evt.key === ` `) && toggle_filter(`type`, format)}
|
|
102
|
+
role="button"
|
|
103
|
+
tabindex="0"
|
|
104
|
+
title="Filter to show only {format.toUpperCase()} files"
|
|
105
|
+
>
|
|
106
|
+
<span
|
|
107
|
+
class="format-circle"
|
|
108
|
+
style:background-color={file_type_colors[format]}
|
|
109
|
+
></span> {format.toUpperCase()}
|
|
110
|
+
</span>
|
|
111
|
+
{/each}
|
|
112
|
+
|
|
113
|
+
{#if active_category_filter || active_type_filter}
|
|
114
|
+
<button
|
|
115
|
+
title="Clear all filters"
|
|
116
|
+
class="clear-filter"
|
|
117
|
+
onclick={() => {
|
|
118
|
+
active_category_filter = null
|
|
119
|
+
active_type_filter = null
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
✕
|
|
123
|
+
</button>
|
|
124
|
+
{/if}
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{#each filtered_files as file (file.name)}
|
|
128
|
+
{@const base_type = get_base_file_type(file.name)}
|
|
129
|
+
{@const is_compressed = file.name.toLowerCase().endsWith(`.gz`)}
|
|
130
|
+
<div
|
|
131
|
+
class="file-item"
|
|
132
|
+
class:active={active_files.includes(file.name)}
|
|
133
|
+
class:compressed={is_compressed}
|
|
134
|
+
style:background-color={file_type_colors[base_type]?.replace(`0.8`, `0.08`)}
|
|
135
|
+
draggable="true"
|
|
136
|
+
ondragstart={handle_drag_start(file)}
|
|
137
|
+
ondragend={() => on_drag_end?.()}
|
|
138
|
+
role="button"
|
|
139
|
+
tabindex="0"
|
|
140
|
+
title="Drag this {base_type.toUpperCase()} file"
|
|
141
|
+
>
|
|
142
|
+
<div class="drag-handle">
|
|
143
|
+
<div class="drag-bar"></div>
|
|
144
|
+
<div class="drag-bar"></div>
|
|
145
|
+
<div class="drag-bar"></div>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="file-name">
|
|
148
|
+
{file.name}{file.category ? `\u00A0${file.category}` : ``}
|
|
149
|
+
{#if is_compressed}<span class="compression-indicator">📦</span>{/if}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
{/each}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<style>
|
|
156
|
+
.file-picker {
|
|
157
|
+
display: flex;
|
|
158
|
+
flex-wrap: wrap;
|
|
159
|
+
gap: 0.5em;
|
|
160
|
+
flex: 1;
|
|
161
|
+
align-content: start;
|
|
162
|
+
}
|
|
163
|
+
.legend {
|
|
164
|
+
width: 100%;
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
gap: 0.8em;
|
|
168
|
+
font-size: 0.6em;
|
|
169
|
+
opacity: 0.8;
|
|
170
|
+
margin: 0 0 0.5em;
|
|
171
|
+
}
|
|
172
|
+
.legend-item {
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
padding: 0.2em 0.4em;
|
|
175
|
+
border-radius: 3px;
|
|
176
|
+
transition: all 0.2s ease;
|
|
177
|
+
border: 1px solid transparent;
|
|
178
|
+
}
|
|
179
|
+
.legend-item:hover {
|
|
180
|
+
opacity: 1;
|
|
181
|
+
background: rgba(255, 255, 255, 0.1);
|
|
182
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
183
|
+
}
|
|
184
|
+
.legend-item.active {
|
|
185
|
+
opacity: 1;
|
|
186
|
+
background: rgba(255, 255, 255, 0.2);
|
|
187
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
188
|
+
font-weight: bold;
|
|
189
|
+
}
|
|
190
|
+
.clear-filter {
|
|
191
|
+
background-color: var(--btn-bg);
|
|
192
|
+
border-radius: 50%;
|
|
193
|
+
display: flex;
|
|
194
|
+
place-content: center;
|
|
195
|
+
}
|
|
196
|
+
.clear-filter:hover {
|
|
197
|
+
background-color: var(--btn-hover-bg);
|
|
198
|
+
}
|
|
199
|
+
.format-item {
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: 0.3em;
|
|
203
|
+
}
|
|
204
|
+
.format-circle {
|
|
205
|
+
width: 8px;
|
|
206
|
+
height: 8px;
|
|
207
|
+
border-radius: 50%;
|
|
208
|
+
display: inline-block;
|
|
209
|
+
}
|
|
210
|
+
.file-item {
|
|
211
|
+
display: flex;
|
|
212
|
+
align-items: center;
|
|
213
|
+
padding: 4pt 8pt;
|
|
214
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
215
|
+
border-radius: 20px;
|
|
216
|
+
cursor: grab;
|
|
217
|
+
background: rgba(255, 255, 255, 0.1);
|
|
218
|
+
transition: all 0.2s ease;
|
|
219
|
+
gap: 0.5em;
|
|
220
|
+
}
|
|
221
|
+
.file-item.active {
|
|
222
|
+
border-color: var(--success-color, #00ff00);
|
|
223
|
+
background: rgba(0, 255, 0, 0.15);
|
|
224
|
+
box-shadow: 0 0 8px rgba(0, 255, 0, 0.3);
|
|
225
|
+
}
|
|
226
|
+
.file-item:active {
|
|
227
|
+
cursor: grabbing;
|
|
228
|
+
}
|
|
229
|
+
.file-item:hover {
|
|
230
|
+
border-color: var(--accent-color, #007acc);
|
|
231
|
+
background: rgba(0, 122, 204, 0.2);
|
|
232
|
+
filter: brightness(1.1);
|
|
233
|
+
}
|
|
234
|
+
.drag-handle {
|
|
235
|
+
display: flex;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
gap: 2px;
|
|
238
|
+
opacity: 0.6;
|
|
239
|
+
}
|
|
240
|
+
.drag-bar {
|
|
241
|
+
width: 12px;
|
|
242
|
+
height: 2px;
|
|
243
|
+
background: currentColor;
|
|
244
|
+
border-radius: 1px;
|
|
245
|
+
}
|
|
246
|
+
.file-name {
|
|
247
|
+
font-size: 0.7em;
|
|
248
|
+
line-height: 1.1;
|
|
249
|
+
white-space: pre-line;
|
|
250
|
+
}
|
|
251
|
+
.compression-indicator {
|
|
252
|
+
opacity: 0.7;
|
|
253
|
+
font-size: 0.8em;
|
|
254
|
+
margin-left: 0.2em;
|
|
255
|
+
}
|
|
256
|
+
.file-item.compressed {
|
|
257
|
+
border-style: dashed;
|
|
258
|
+
opacity: 0.9;
|
|
259
|
+
}
|
|
260
|
+
.file-item.compressed:hover {
|
|
261
|
+
opacity: 1;
|
|
262
|
+
}
|
|
263
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FileInfo } from './';
|
|
2
|
+
interface Props {
|
|
3
|
+
files: FileInfo[];
|
|
4
|
+
active_files?: string[];
|
|
5
|
+
show_category_filters?: boolean;
|
|
6
|
+
category_labels?: Record<string, string>;
|
|
7
|
+
on_drag_start?: (file: FileInfo, event: DragEvent) => void;
|
|
8
|
+
on_drag_end?: () => void;
|
|
9
|
+
type_mapper?: (filename: string) => string;
|
|
10
|
+
file_type_colors?: Record<string, string>;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
declare const FilePicker: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type FilePicker = ReturnType<typeof FilePicker>;
|
|
15
|
+
export default FilePicker;
|
package/dist/Icon.svelte
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">import { icon_data } from './icons';
|
|
2
2
|
let { icon, ...rest } = $props();
|
|
3
|
-
const
|
|
3
|
+
const { path, ...svg_props } = $derived.by(() => {
|
|
4
4
|
if (!(icon in icon_data)) {
|
|
5
5
|
console.error(`Icon '${icon}' not found`);
|
|
6
6
|
return icon_data.Alert; // fallback
|
|
@@ -9,8 +9,12 @@ const data = $derived.by(() => {
|
|
|
9
9
|
});
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
<svg
|
|
13
|
-
|
|
12
|
+
<svg fill="currentColor" {...svg_props} {...rest}>
|
|
13
|
+
{#if path.trim().startsWith(`<`)}
|
|
14
|
+
{@html path}
|
|
15
|
+
{:else}
|
|
16
|
+
<path d={path} />
|
|
17
|
+
{/if}
|
|
14
18
|
</svg>
|
|
15
19
|
|
|
16
20
|
<style>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<script lang="ts">import { Icon } from './';
|
|
2
|
+
let { title, current_values, children, on_reset = () => { }, ...rest } = $props();
|
|
3
|
+
// Create a deep copy of current_values on mount to use as reference values
|
|
4
|
+
function deep_copy(obj) {
|
|
5
|
+
if (obj === null || typeof obj !== `object`)
|
|
6
|
+
return obj;
|
|
7
|
+
if (obj instanceof Date)
|
|
8
|
+
return new Date(obj.getTime());
|
|
9
|
+
if (obj instanceof RegExp)
|
|
10
|
+
return new RegExp(obj);
|
|
11
|
+
if (Array.isArray(obj)) {
|
|
12
|
+
return obj.map((item) => typeof item === `object` && item !== null ? deep_copy(item) : item);
|
|
13
|
+
}
|
|
14
|
+
const copy = {};
|
|
15
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
16
|
+
copy[key] = typeof value === `object` && value !== null
|
|
17
|
+
? deep_copy(value)
|
|
18
|
+
: value;
|
|
19
|
+
}
|
|
20
|
+
return copy;
|
|
21
|
+
}
|
|
22
|
+
const reference_values = deep_copy(current_values);
|
|
23
|
+
// Check if any values have changed from reference values
|
|
24
|
+
let has_changes = $derived.by(() => {
|
|
25
|
+
for (const [key, reference_value] of Object.entries(reference_values)) {
|
|
26
|
+
const current_value = current_values[key];
|
|
27
|
+
// Deep comparison for arrays
|
|
28
|
+
if (Array.isArray(reference_value) && Array.isArray(current_value)) {
|
|
29
|
+
if (reference_value.length !== current_value.length)
|
|
30
|
+
return true;
|
|
31
|
+
if (reference_value.some((val, idx) => {
|
|
32
|
+
const curr_val = current_value[idx];
|
|
33
|
+
// Handle nested objects/arrays in arrays
|
|
34
|
+
if (typeof val === `object` && val !== null &&
|
|
35
|
+
typeof curr_val === `object` && curr_val !== null)
|
|
36
|
+
return JSON.stringify(val) !== JSON.stringify(curr_val); // Quick deep comparison fallback
|
|
37
|
+
return val !== curr_val;
|
|
38
|
+
})) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Handle undefined/null comparisons properly
|
|
44
|
+
if (reference_value === undefined && current_value === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
if (reference_value === null && current_value === null)
|
|
47
|
+
continue;
|
|
48
|
+
// Basic comparison for primitives
|
|
49
|
+
if (current_value !== reference_value) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
});
|
|
55
|
+
function handle_reset(event) {
|
|
56
|
+
event.stopPropagation();
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
on_reset();
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<h4>
|
|
63
|
+
{title}
|
|
64
|
+
|
|
65
|
+
{#if has_changes}
|
|
66
|
+
<button
|
|
67
|
+
class="reset-button"
|
|
68
|
+
onclick={handle_reset}
|
|
69
|
+
title="Reset {title.toLowerCase()} to defaults"
|
|
70
|
+
aria-label="Reset {title.toLowerCase()} to defaults"
|
|
71
|
+
>
|
|
72
|
+
<Icon icon="Reset" style="width: 0.9em; height: 0.9em" />
|
|
73
|
+
Reset
|
|
74
|
+
</button>
|
|
75
|
+
{/if}
|
|
76
|
+
</h4>
|
|
77
|
+
<section {...rest}>
|
|
78
|
+
{@render children()}
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<style>
|
|
82
|
+
h4 {
|
|
83
|
+
margin: 0;
|
|
84
|
+
position: relative;
|
|
85
|
+
}
|
|
86
|
+
.reset-button {
|
|
87
|
+
position: absolute;
|
|
88
|
+
top: 0;
|
|
89
|
+
right: 0;
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 2pt;
|
|
93
|
+
padding: var(--reset-btn-padding, 1pt 4pt);
|
|
94
|
+
font-size: 0.65em;
|
|
95
|
+
border-radius: var(--reset-btn-border-radius, 2pt);
|
|
96
|
+
background: var(--btn-bg, rgba(0, 0, 0, 0.1));
|
|
97
|
+
color: var(--text-color-muted, #6b7280);
|
|
98
|
+
border: 1px solid var(--border-color, #d1d5db);
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
z-index: 5;
|
|
101
|
+
transition: all 0.15s ease;
|
|
102
|
+
box-shadow: none;
|
|
103
|
+
opacity: 0.7;
|
|
104
|
+
}
|
|
105
|
+
.reset-button:hover {
|
|
106
|
+
background: var(--btn-hover-bg, rgba(0, 0, 0, 0.2));
|
|
107
|
+
color: var(--text-color, #374151);
|
|
108
|
+
opacity: 1;
|
|
109
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
110
|
+
}
|
|
111
|
+
.reset-button.standalone {
|
|
112
|
+
position: absolute;
|
|
113
|
+
top: -8pt;
|
|
114
|
+
right: -8pt;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
title: string;
|
|
4
|
+
current_values: Record<string, unknown>;
|
|
5
|
+
children: Snippet<[]>;
|
|
6
|
+
on_reset?: () => void;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
declare const SettingsSection: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type SettingsSection = ReturnType<typeof SettingsSection>;
|
|
11
|
+
export default SettingsSection;
|
package/dist/api/mp.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const mp_bucket = "https://materialsproject-build.s3.amazonaws.com/collections/2022-10-28";
|
|
2
|
+
export declare function fetch_material_data<T extends Record<string, unknown>>(material_id: string, bucket?: string): Promise<{
|
|
3
|
+
summary: T | null;
|
|
4
|
+
similarity: T | null;
|
|
5
|
+
robocrys: T | null;
|
|
6
|
+
}>;
|
package/dist/api/mp.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fetch_zipped } from '../io/fetch';
|
|
2
|
+
// TODO update to get MP details pages working again
|
|
3
|
+
export const mp_bucket = `https://materialsproject-build.s3.amazonaws.com/collections/2022-10-28`;
|
|
4
|
+
// Fetch all material data in parallel
|
|
5
|
+
export async function fetch_material_data(material_id, bucket = mp_bucket) {
|
|
6
|
+
try {
|
|
7
|
+
const results = await Promise.allSettled([
|
|
8
|
+
fetch_zipped(`${bucket}/summary/${material_id}.json.gz`),
|
|
9
|
+
fetch_zipped(`${bucket}/similarity/${material_id}.json.gz`),
|
|
10
|
+
fetch_zipped(`${bucket}/robocrys/${material_id}.json.gz`),
|
|
11
|
+
]);
|
|
12
|
+
return {
|
|
13
|
+
summary: results[0].status === `fulfilled` ? results[0].value : null,
|
|
14
|
+
similarity: results[1].status === `fulfilled` ? results[1].value : null,
|
|
15
|
+
robocrys: results[2].status === `fulfilled` ? results[2].value : null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
console.error(`Failed to fetch material data:`, err);
|
|
20
|
+
return { summary: null, similarity: null, robocrys: null };
|
|
21
|
+
}
|
|
22
|
+
}
|