beanbagdb-components 0.2.7 → 0.2.8

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/data.html DELETED
@@ -1,1086 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en" data-bs-theme="dark">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <!-- <script type="module" src="../dist/main.js"></script> -->
7
-
8
- <title>BBDB-Database</title>
9
- <link
10
- href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"
11
- rel="stylesheet"
12
- integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
13
- crossorigin="anonymous"
14
- />
15
- <link
16
- href="https://unpkg.com/tabulator-tables@6.3.1/dist/css/tabulator.min.css"
17
- rel="stylesheet"
18
- />
19
- <link
20
- href="https://unpkg.com/tabulator-tables@6.3.1/dist/css/tabulator_site_dark.min.css"
21
- rel="stylesheet"
22
- />
23
-
24
- <link rel="stylesheet" href="app.style.css" />
25
-
26
- <script
27
- type="text/javascript"
28
- src="https://unpkg.com/tabulator-tables@6.3.1/dist/js/tabulator.min.js"
29
- ></script>
30
-
31
- <script src="https://cdnjs.cloudflare.com/ajax/libs/split.js/1.6.0/split.min.js"></script>
32
-
33
- <script src="app.js"></script>
34
- <script src="local_cache.js"></script>
35
- <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
36
- <script type="module">
37
- import { BBDB } from "../dist/main.js";
38
- const { createApp, ref, onMounted, nextTick } = Vue;
39
- let DB;
40
- let DB_cache;
41
- let dataTable;
42
-
43
- function linkEditor(cell, onRendered, success, cancel, editorParams) {
44
- const editor = document.createElement("input");
45
- editor.type = "text";
46
- editor.value = cell.getValue() || "";
47
- editor.style.padding = "3px";
48
- editor.style.width = "100%";
49
- editor.style.boxSizing = "border-box";
50
-
51
- const oldValue = cell.getOldValue();
52
- const rowData = cell.getRow().getData();
53
- const rowId = rowData._id;
54
-
55
- console.log("Row ID:", rowId); // Debug: verify ID is correct
56
-
57
- onRendered(() => {
58
- editor.focus();
59
- editor.select();
60
- });
61
-
62
- function attemptCommit() {
63
- const newValue = editor.value.trim();
64
-
65
- if (newValue === oldValue) {
66
- success(newValue);
67
- return;
68
- }
69
-
70
- if (!/^[a-zA-Z0-9]+$/.test(newValue)) {
71
- cancel();
72
- return;
73
- }
74
-
75
- editor.disabled = true;
76
- editor.style.opacity = "0.6";
77
-
78
- DB.update({
79
- criteria: { _id: rowId },
80
- updates: {
81
- meta: {
82
- link: newValue,
83
- },
84
- },
85
- })
86
- .then((response) => {
87
- console.log(response);
88
- success(newValue);
89
- // if (!response.ok) throw new Error(`HTTP ${response.status}`);
90
- // return response.json();
91
- })
92
- // .then((data) => {
93
- // if (data.success) {
94
- // success(newValue);
95
- // } else {
96
- // throw new Error(data.error || "API rejected");
97
- // }
98
- // })
99
- .catch((error) => {
100
- console.error("Update failed:", error);
101
- //cell.restoreOldValue();
102
- cancel();
103
- showMessage("error", error.message);
104
- })
105
- .finally(() => {
106
- editor.disabled = false;
107
- editor.style.opacity = "1";
108
- });
109
- }
110
-
111
- editor.addEventListener("change", attemptCommit);
112
- editor.addEventListener("blur", attemptCommit);
113
- editor.addEventListener("keydown", (e) => {
114
- if (e.key === "Enter") attemptCommit();
115
- if (e.key === "Escape") cancel();
116
- });
117
-
118
- return editor;
119
- }
120
-
121
- function titleEditor(cell, onRendered, success, cancel, editorParams) {
122
- const editor = document.createElement("input");
123
- editor.type = "text";
124
- editor.value = cell.getValue() || "";
125
- editor.style.padding = "3px";
126
- editor.style.width = "100%";
127
- editor.style.boxSizing = "border-box";
128
-
129
- const oldValue = cell.getOldValue();
130
- const rowData = cell.getRow().getData();
131
- const rowId = rowData._id;
132
-
133
- console.log("Row ID:", rowId); // Debug: verify ID is correct
134
-
135
- onRendered(() => {
136
- editor.focus();
137
- editor.select();
138
- });
139
-
140
- function attemptCommit() {
141
- const newValue = editor.value.trim();
142
-
143
- if (newValue === oldValue) {
144
- success(newValue);
145
- return;
146
- }
147
-
148
- // Title validation: alphanumeric + spaces, dashes, underscores (1-100 chars)
149
- if (!/^[a-zA-Z0-9\s\-_]{1,100}$/.test(newValue)) {
150
- cancel();
151
- showMessage(
152
- "error",
153
- "Title must be 1-100 chars (letters, numbers, spaces, -, _)"
154
- );
155
- return;
156
- }
157
-
158
- editor.disabled = true;
159
- editor.style.opacity = "0.6";
160
-
161
- DB.update({
162
- criteria: { _id: rowId },
163
- updates: {
164
- meta: {
165
- title: newValue,
166
- },
167
- },
168
- })
169
- .then((response) => {
170
- console.log(response);
171
- success(newValue);
172
- })
173
- .catch((error) => {
174
- console.error("Update failed:", error);
175
- cancel();
176
- showMessage("error", error.message);
177
- })
178
- .finally(() => {
179
- editor.disabled = false;
180
- editor.style.opacity = "1";
181
- });
182
- }
183
-
184
- editor.addEventListener("change", attemptCommit);
185
- editor.addEventListener("blur", attemptCommit);
186
- editor.addEventListener("keydown", (e) => {
187
- if (e.key === "Enter") attemptCommit();
188
- if (e.key === "Escape") cancel();
189
- });
190
-
191
- return editor;
192
- }
193
-
194
- function tagsEditor(cell, onRendered, success, cancel, editorParams) {
195
- const editor = document.createElement("input");
196
- editor.type = "text";
197
- editor.value = (cell.getValue() || []).join(", ");
198
- editor.style.padding = "3px";
199
- editor.style.width = "100%";
200
- editor.style.boxSizing = "border-box";
201
- editor.placeholder = "Enter tags separated by commas";
202
-
203
- const oldValue = cell.getOldValue() || [];
204
- const rowData = cell.getRow().getData();
205
- const rowId = rowData._id;
206
-
207
- console.log("Row ID:", rowId);
208
-
209
- onRendered(() => {
210
- editor.focus();
211
- editor.select();
212
- });
213
-
214
- function attemptCommit() {
215
- const inputValue = editor.value.trim();
216
-
217
- if (
218
- inputValue ===
219
- (Array.isArray(oldValue) ? oldValue.join(", ") : oldValue)
220
- ) {
221
- success(oldValue);
222
- return;
223
- }
224
-
225
- const newTags = inputValue
226
- ? inputValue
227
- .split(",")
228
- .map((tag) => tag.trim())
229
- .filter((tag) => tag.length > 0)
230
- : [];
231
-
232
- if (newTags.length === 0 && inputValue !== "") {
233
- cancel();
234
- showMessage("error", "At least one tag required or clear all tags");
235
- return;
236
- }
237
-
238
- const invalidTag = newTags.find(
239
- (tag) => !/^[a-zA-Z0-9\-_\s]{1,50}$/.test(tag)
240
- );
241
- if (invalidTag) {
242
- cancel();
243
- showMessage(
244
- "error",
245
- `Invalid tag: "${invalidTag}". Use letters, numbers, -, _, spaces`
246
- );
247
- return;
248
- }
249
-
250
- editor.disabled = true;
251
- editor.style.opacity = "0.6";
252
-
253
- DB.update({
254
- criteria: { _id: rowId },
255
- updates: {
256
- meta: {
257
- tags: newTags,
258
- },
259
- },
260
- })
261
- .then((response) => {
262
- console.log("Tags updated:", newTags, response);
263
- success(newTags);
264
- })
265
- .catch((error) => {
266
- console.error("Update failed:", error);
267
- cell.restoreOldValue();
268
- cancel();
269
- showMessage("error", error.message);
270
- })
271
- .finally(() => {
272
- editor.disabled = false;
273
- editor.style.opacity = "1";
274
- });
275
- }
276
-
277
- editor.addEventListener("change", attemptCommit);
278
- editor.addEventListener("blur", attemptCommit);
279
- editor.addEventListener("keydown", (e) => {
280
- if (e.key === "Enter") attemptCommit();
281
- if (e.key === "Escape") cancel();
282
- });
283
-
284
- return editor;
285
- }
286
-
287
- async function search_ready(db_details, db) {
288
- //console.log(APP_CACHE);
289
-
290
- const fetch_schemas = async () => {
291
- let query = await db.search({
292
- selector: { schema: "schema" },
293
- fields: [],
294
- });
295
- let schemas = [];
296
-
297
- query?.docs.map((itm) => {
298
-
299
-
300
- let schema_fields = ["schema","_id","meta.tags","meta.link","meta.created_on","meta.title"]
301
- let col_names = []
302
- Object.keys(itm.data.schema.properties).map((ky) => {
303
- schema_fields.push(`data.${ky}`)
304
- col_names.push({
305
- field: `data_${ky}`,
306
- title: ky
307
- })
308
- });
309
-
310
-
311
- schemas.push({
312
- title: `${itm?.data?.title}`,
313
- columns: col_names,
314
- criteria: {
315
- selector:{
316
- schema: itm?.data?.name
317
- },
318
- fields:schema_fields
319
- },
320
- });
321
- });
322
- return schemas;
323
- };
324
-
325
- const fetch_tags = async () => {
326
- const query = await db.search({
327
- selector: {},
328
- limit: 2000,
329
- fields: ["meta.tags"],
330
- });
331
- const tagSet = new Set();
332
- query.docs.forEach((doc) => {
333
- if (Array.isArray(doc.meta?.tags)) {
334
- doc.meta.tags.forEach((tag) => tagSet.add(tag));
335
- }
336
- });
337
- return Array.from(tagSet).map((tag) => ({
338
- title: `#${tag}`,
339
- criteria: {
340
- "meta.tags": {
341
- $in: [tag],
342
- },
343
- },
344
- }));
345
- };
346
-
347
- // CACHE + STORE FOR REFRESH
348
- const schemaKey = `${db_details.name}_schemas`;
349
- const tagsKey = `${db_details.name}_tags`;
350
-
351
- const filters = {
352
- schema: await APP_CACHE.load(schemaKey, fetch_schemas),
353
- tags: await APP_CACHE.load(tagsKey, fetch_tags),
354
- };
355
-
356
- // STORE GLOBALS FOR REFRESH BUTTONS + VUE
357
- window.db_details = db_details;
358
- window.refreshFunctions = {
359
- [schemaKey]: fetch_schemas,
360
- [tagsKey]: fetch_tags,
361
- };
362
- window.filters = filters;
363
-
364
- console.log("Filters loaded:", filters);
365
- return filters;
366
- }
367
-
368
- document.addEventListener("DOMContentLoaded", async () => {
369
- try {
370
- const db_details = await initPage();
371
- DB = await BBDB(db_details.dbObj);
372
- await DB.ready();
373
-
374
- await search_ready(db_details, DB);
375
- //DB_cache = new JsonCache(db_details.name)
376
- //await DB_cache.init()
377
-
378
- //console.log(await DB_cache.keys())
379
-
380
- //console.log(DB);
381
- //console.log(db_details);
382
- //showMessage("success", `Loaded database: ${db_details.name}`);
383
-
384
- createApp({
385
- setup() {
386
- const tableRef = ref(null);
387
- const table = ref(null);
388
- const loading = ref(false);
389
- const recordCount = ref(0);
390
-
391
- // FILTERS REFS
392
- const schemas = ref([]);
393
- const tags = ref([]);
394
-
395
- // Transform records for table
396
- const transformRecords = (docs) => {
397
- console.log(docs)
398
- return docs.map((doc) => {
399
- const row = {
400
- schema: doc.schema || "unknown",
401
- _id: doc._id,
402
- _rev: doc._rev,
403
- };
404
- Object.keys(doc.meta).map((ky) => {
405
- row[`meta_${ky}`] = doc.meta[ky];
406
- });
407
-
408
- Object.keys(doc.data).map((ky) => {
409
- row[`data_${ky}`] = doc.data[ky];
410
- });
411
-
412
- // if (doc.data) {
413
- // Object.assign(row, doc.data);
414
- // }
415
- return row;
416
- });
417
- };
418
-
419
- const generateColumns = (additional=[]) => {
420
- let columns = [
421
- {
422
- title: "Title",
423
- field: "meta_title",
424
- sorter: "string",
425
- editor: titleEditor,
426
- },
427
- {
428
- title: "Schema",
429
- field: "schema",
430
- sorter: "string",
431
- },
432
- {
433
- title: "Link",
434
- field: "meta_link",
435
- sorter: "string",
436
- editor: linkEditor,
437
- },
438
- // {
439
- // title: "Document ID",
440
- // field: "_id",
441
- // sorter: "string",
442
- // },
443
- // {
444
- // title: "Revision ID",
445
- // field: "_rev",
446
- // sorter: "string",
447
- // },
448
- ...additional,
449
- {
450
- title: "Created On",
451
- field: "meta_created_on",
452
- formatter: function (cell) {
453
- const ts = Number(cell.getValue());
454
- if (!ts || isNaN(ts)) return "Invalid Date";
455
-
456
- // Adjust multiplier based on your epoch unit:
457
- // - Use *1000 if seconds → ms (common for Unix)
458
- // - Use *1 if already ms (JavaScript Date expects ms)
459
- const date = new Date(ts * 1000);
460
-
461
- // Format as YYYY-MM-DD HH:mm:ss (customize as needed)
462
- const yyyy = date.getFullYear();
463
- const mm = String(date.getMonth() + 1).padStart(2, "0");
464
- const dd = String(date.getDate()).padStart(2, "0");
465
- const hh = String(date.getHours()).padStart(2, "0");
466
- const min = String(date.getMinutes()).padStart(2, "0");
467
- const ss = String(date.getSeconds()).padStart(2, "0");
468
-
469
- return `${yyyy}-${mm}-${dd} ${hh}:${min}`;
470
- },
471
- },
472
- {
473
- title: "Tags",
474
- field: "meta_tags",
475
- //sorter: "array",
476
- editor: tagsEditor,
477
- formatter: function (cell) {
478
- const tags = cell.getValue() || [];
479
- return tags.length ? tags.join(", ") : "";
480
- },
481
- },
482
- {
483
- title: "Actions",
484
- field: "actions",
485
- width: 75,
486
- minWidth: 60,
487
- hozAlign: "center",
488
- headerSort: false,
489
- formatter: function (cell) {
490
- return "..."; // Empty cell, just trigger for cellClick
491
- },
492
- cellClick: function (e, cell) {
493
- const rowData = cell.getRow().getData();
494
- const menu = document.createElement("div");
495
- menu.className =
496
- "position-absolute bg-dark border rounded shadow p-2";
497
- menu.style.zIndex = "1000";
498
- menu.innerHTML = `
499
- <button class="btn btn-sm btn-outline-light w-100 mb-1 copy-id" data-id="${
500
- rowData._id
501
- }">Copy ID</button>
502
- ${
503
- rowData.meta_link
504
- ? `<button class="btn btn-sm btn-outline-light w-100 mb-1 copy-link" data-link="${rowData.meta_link}">Copy Link</button>`
505
- : ""
506
- }
507
- <button class="btn btn-sm btn-outline-light w-100 mb-1 related-btn" data-id="${
508
- rowData._id
509
- }"> Related docs</button>
510
- <button class="btn btn-sm btn-outline-light w-100 mb-1 download-btn" data-id="${
511
- rowData._id
512
- }">Download doc</button>
513
- <button class="btn btn-sm btn-primary w-100 edit-btn" data-id="${
514
- rowData._id
515
- }">Edit</button>
516
-
517
- <div class="dropdown-divider"></div>
518
-
519
- <button class="btn btn-sm btn-danger w-100 delete-btn" data-id="${
520
- rowData._id
521
- }">Delete</button>
522
- `;
523
-
524
- // Position menu near click
525
- const rect = cell.getElement().getBoundingClientRect();
526
- menu.style.left = rect.left + "px";
527
- menu.style.top = rect.bottom + 5 + "px";
528
-
529
- document.body.appendChild(menu);
530
-
531
- // Single click handlers
532
- menu
533
- .querySelector(".copy-id")
534
- ?.addEventListener("click", (e) => {
535
- navigator.clipboard.writeText(rowData._id);
536
- showMessage("success", `Copied ID: ${rowData._id}`);
537
- menu.remove();
538
- });
539
-
540
- menu
541
- .querySelector(".copy-link")
542
- ?.addEventListener("click", (e) => {
543
- navigator.clipboard.writeText(rowData.meta_link);
544
- showMessage("success", `Copied Link`);
545
- menu.remove();
546
- });
547
-
548
- menu
549
- .querySelector(".edit-btn")
550
- ?.addEventListener("click", (e) => {
551
- showMessage("info", `Edit: ${rowData._id}`);
552
- console.log("Edit:", rowData._id); // Load into pane 3 here
553
- menu.remove();
554
- });
555
-
556
- menu
557
- .querySelector(".related-btn")
558
- ?.addEventListener("click", () => {
559
- if (
560
- confirm(
561
- `Are you sure you want to delete:\n"${
562
- rowData.meta_title || "No title"
563
- }"\nID: ${rowData._id}?`
564
- )
565
- ) {
566
- cell.getRow().delete(); // Tabulator's built-in row delete
567
- showMessage("success", `Deleted: ${rowData._id}`);
568
- }
569
- menu.remove();
570
- });
571
-
572
- menu
573
- .querySelector(".delete-btn")
574
- ?.addEventListener("click", () => {
575
- if (
576
- confirm(
577
- `Are you sure you want to delete:\n"${
578
- rowData.meta_title || "No title"
579
- }"\nID: ${rowData._id}?`
580
- )
581
- ) {
582
- cell.getRow().delete();
583
- showMessage("success", `Deleted: ${rowData._id}`);
584
- }
585
- menu.remove();
586
- });
587
-
588
- // Remove menu on outside click
589
- setTimeout(() => {
590
- const removeMenu = (e) => {
591
- if (!menu.contains(e.target)) {
592
- menu.remove();
593
- document.removeEventListener("click", removeMenu);
594
- }
595
- };
596
- document.addEventListener("click", removeMenu);
597
- }, 100);
598
- },
599
- },
600
- ];
601
- return columns;
602
- };
603
-
604
- const loadData = async (criteria = {}) => {
605
- try {
606
- loading.value = true;
607
- const result = await DB.search({ selector: criteria });
608
- recordCount.value = result.docs.length;
609
-
610
- //const allFields = getAllFields(result.docs);
611
- //console.log(allFields)
612
- const tableData = transformRecords(result.docs);
613
- //console.log(tableData)
614
- const columns = generateColumns();
615
-
616
- if (table.value) {
617
- table.value.destroy();
618
- }
619
-
620
- await nextTick();
621
-
622
- table.value = new Tabulator(tableRef.value, {
623
- data: tableData,
624
- //autoColumns:true,
625
- columns: columns,
626
- layout: "fitColumns",
627
- responsiveLayout: "hide",
628
- pagination: true,
629
- paginationSize: 25,
630
- paginationSizeSelector: [10, 25, 50, 100],
631
- groupBy: "schema",
632
- groupStartOpen: true,
633
- download: true,
634
- downloadConfig: {
635
- csv: { downloadButton: true },
636
- excel: { downloadButton: true },
637
- },
638
- placeholder: "No data - click Search to load",
639
- });
640
- } catch (error) {
641
- console.error("Load failed:", error);
642
- } finally {
643
- loading.value = false;
644
- }
645
- };
646
-
647
- // FILTER CLICK HANDLERS
648
- const applyFilter = async (filter) => {
649
- console.log(filter);
650
- if (table.value) {
651
- loading.value = true;
652
- try {
653
- const result = await DB.search(filter.criteria );
654
- let newColumns = generateColumns(filter.columns)
655
- table.value.setColumns(newColumns)
656
- const tableData = transformRecords(result.docs);
657
- table.value.replaceData(tableData); // Refresh table
658
- recordCount.value = tableData.length;
659
- } catch (error) {
660
- console.error("Filter failed:", error);
661
- } finally {
662
- loading.value = false;
663
- }
664
- }
665
- };
666
-
667
- const refreshSchemas = async () => {
668
- const key = `${window.db_details.name}_schemas`;
669
- await APP_CACHE.refresh(key, window.refreshFunctions[key]);
670
- window.filters.schema = await APP_CACHE.get(key);
671
- schemas.value = window.filters.schema || [];
672
- };
673
-
674
- const refreshTags = async () => {
675
- const key = `${window.db_details.name}_tags`;
676
- await APP_CACHE.refresh(key, window.refreshFunctions[key]);
677
- window.filters.tags = await APP_CACHE.get(key);
678
- tags.value = window.filters.tags || [];
679
- };
680
-
681
- onMounted(async () => {
682
- loadData({});
683
-
684
- // Load filters into Vue
685
- await search_ready(db_details, DB);
686
- schemas.value = window.filters.schema || [];
687
- tags.value = window.filters.tags || [];
688
-
689
- // loadData({});
690
- Split(["#pane1", "#pane2", "#pane3"], {
691
- sizes: [0, 80, 20],
692
- minSize: [175, 300, 100], // Minimum sizes for each pane
693
- gutterSize: 2, // Size of the draggable gutter
694
- snapOffset: 30, // Snap to edge if dragged within 30px
695
- direction: "horizontal", // Explicitly set horizontal direction
696
- cursor: "col-resize", // Cursor style for dragging
697
- });
698
- });
699
-
700
- const jsonQuery = ref('{"selector":{},"limit":1000}');
701
- const queryResultCount = ref(0);
702
- const queryError = ref("");
703
- const jsonQueryValid = ref(true);
704
-
705
- // Add these methods to setup()
706
- const validateJsonQuery = () => {
707
- try {
708
- JSON.parse(jsonQuery.value);
709
- queryError.value = "";
710
- jsonQueryValid.value = true;
711
- showMessage("success", "Valid JSON");
712
- } catch (e) {
713
- queryError.value = `Invalid JSON: ${e.message}`;
714
- jsonQueryValid.value = false;
715
- }
716
- };
717
-
718
- const runJsonQuery = async () => {
719
- if (!jsonQueryValid.value) {
720
- showMessage("error", "Fix JSON first");
721
- return;
722
- }
723
-
724
- try {
725
- loading.value = true;
726
- const queryObj = JSON.parse(jsonQuery.value);
727
- const result = await DB.search(queryObj);
728
-
729
- queryResultCount.value = result.docs.length;
730
- queryError.value = "";
731
-
732
- // Update table if valid result
733
- if (result.docs && result.docs.length > 0) {
734
- const tableData = transformRecords(result.docs);
735
- if (table.value) {
736
- table.value.replaceData(tableData);
737
- recordCount.value = tableData.length;
738
- }
739
- }
740
-
741
- // showMessage('success', `${result.docs.length} docs loaded`);
742
- } catch (error) {
743
- queryError.value = `Query failed: ${error.message}`;
744
- console.error("Query error:", error);
745
- showMessage("error", error.message);
746
- } finally {
747
- loading.value = false;
748
- }
749
- };
750
-
751
- return {
752
- tableRef,
753
- table,
754
- loading,
755
- recordCount,
756
- loadData,
757
- schemas,
758
- tags,
759
- applyFilter,
760
- refreshSchemas,
761
- refreshTags,
762
- jsonQuery,
763
- queryResultCount,
764
- queryError,
765
- jsonQueryValid,
766
- validateJsonQuery,
767
- runJsonQuery,
768
- };
769
- },
770
- }).mount("#app");
771
- } catch (error) {
772
- showMessage("error", error.message);
773
- }
774
- });
775
- </script>
776
- </head>
777
- <body>
778
- <!-- REPLACE YOUR BODY CONTENT WITH THIS - Uses your existing Bootstrap 5 -->
779
- <div id="app" class="d-flex h-100 overflow-hidden">
780
- <!-- PANE 1: Query (10%) -->
781
- <div id="pane1" class="d-flex flex-column border-end">
782
- <nav class="navbar bg-body-tertiary">
783
- <div class="container-fluid">
784
- <a class="navbar-brand"> Search</a>
785
- <div class="d-flex">
786
- <!-- <button class="btn btn-sm">
787
- <svg
788
- xmlns="http://www.w3.org/2000/svg"
789
- width="16"
790
- height="16"
791
- fill="currentColor"
792
- class="bi bi-arrow-clockwise"
793
- viewBox="0 0 16 16"
794
- >
795
- <path
796
- fill-rule="evenodd"
797
- d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z"
798
- />
799
- <path
800
- d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466"
801
- />
802
- </svg>
803
- </button> -->
804
- <!-- <small class="badge bg-secondary">{{ recordCount }} records</small> -->
805
- <!-- <button class="btn btn-sm" @click="loadData">
806
- <svg
807
- xmlns="http://www.w3.org/2000/svg"
808
- width="16"
809
- height="16"
810
- fill="currentColor"
811
- class="bi bi-caret-right-fill"
812
- viewBox="0 0 16 16"
813
- >
814
- <path
815
- d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"
816
- />
817
- </svg>
818
- </button> -->
819
- </div>
820
- </div>
821
- </nav>
822
- <div>
823
- <div class="accordion accordion-flush" id="accordionFlushExample">
824
- <div class="accordion-item">
825
- <h2 class="accordion-header">
826
- <button
827
- class="accordion-button collapsed"
828
- type="button"
829
- data-bs-toggle="collapse"
830
- data-bs-target="#flush-collapseOne"
831
- aria-expanded="false"
832
- aria-controls="flush-collapseOne"
833
- >
834
- Schemas
835
- </button>
836
- </h2>
837
- <div
838
- id="flush-collapseOne"
839
- class="accordion-collapse collapse"
840
- data-bs-parent="#accordionFlushExample"
841
- >
842
- <div class="accordion-body p-0">
843
- <div
844
- v-if="schemas.length"
845
- class="list-group list-group-flush"
846
- >
847
- <a
848
- v-for="schema in schemas"
849
- :key="schema.criteria.schema"
850
- class="list-group-item list-group-item-action p-2 small"
851
- @click="applyFilter(schema)"
852
- style="cursor: pointer"
853
- >
854
- {{ schema.title }}
855
- </a>
856
- </div>
857
- <div v-else class="text-muted small p-2">
858
- Loading schemas...
859
- </div>
860
- <div class="border-top pt-1">
861
- <button
862
- class="btn btn-sm btn-outline-secondary m-2"
863
- @click="refreshSchemas"
864
- >
865
- Refresh Schemas
866
- </button>
867
- </div>
868
- </div>
869
- </div>
870
- </div>
871
- <div class="accordion-item">
872
- <h2 class="accordion-header">
873
- <button
874
- class="accordion-button collapsed"
875
- type="button"
876
- data-bs-toggle="collapse"
877
- data-bs-target="#flush-collapseTwo"
878
- aria-expanded="false"
879
- aria-controls="flush-collapseTwo"
880
- >
881
- JSON Query
882
- </button>
883
- </h2>
884
- <div
885
- id="flush-collapseTwo"
886
- class="accordion-collapse collapse"
887
- data-bs-parent="#accordionFlushExample"
888
- >
889
- <div class="accordion-body p-1">
890
- <!-- JSON Editor -->
891
- <div class="mb-2">
892
- <textarea
893
- v-model="jsonQuery"
894
- class="form-control form-control-sm"
895
- rows="6"
896
- placeholder='{ "selector": {}, "limit": 1000 }'
897
- ></textarea>
898
- </div>
899
-
900
- <!-- Validate + Run buttons -->
901
- <div class="d-flex gap-1 mb-2">
902
- <button
903
- class="btn btn-sm btn-outline-secondary flex-grow-1"
904
- @click="validateJsonQuery"
905
- >
906
- Validate
907
- </button>
908
- <button
909
- class="btn btn-sm btn-primary flex-grow-1"
910
- @click="runJsonQuery"
911
- :disabled="loading"
912
- >
913
- <span
914
- v-if="loading"
915
- class="spinner-border spinner-border-sm me-1"
916
- ></span>
917
- Run Query
918
- </button>
919
- </div>
920
-
921
- <!-- Error display -->
922
- <div
923
- v-if="queryError"
924
- class="alert alert-danger p-1 small mb-0"
925
- >
926
- {{ queryError }}
927
- </div>
928
- </div>
929
- </div>
930
- </div>
931
-
932
- <!-- <div class="accordion-item">
933
- <h2 class="accordion-header">
934
- <button
935
- class="accordion-button collapsed"
936
- type="button"
937
- data-bs-toggle="collapse"
938
- data-bs-target="#flush-collapseThree"
939
- aria-expanded="false"
940
- aria-controls="flush-collapseThree"
941
- >
942
- Custom Scripts
943
- </button>
944
- </h2>
945
- <div
946
- id="flush-collapseThree"
947
- class="accordion-collapse collapse"
948
- data-bs-parent="#accordionFlushExample"
949
- >
950
- <div class="accordion-body">
951
-
952
- </div>
953
- </div>
954
- </div> -->
955
-
956
- <div class="accordion-item">
957
- <h2 class="accordion-header">
958
- <button
959
- class="accordion-button collapsed"
960
- type="button"
961
- data-bs-toggle="collapse"
962
- data-bs-target="#flush-collapseTags"
963
- >
964
- Tags
965
- </button>
966
- </h2>
967
- <div
968
- id="flush-collapseTags"
969
- class="accordion-collapse collapse"
970
- data-bs-parent="#accordionFlushExample"
971
- >
972
- <div class="accordion-body p-0">
973
- <div
974
- v-if="tags.length"
975
- class="list-group list-group-flush max-h-200px overflow-auto"
976
- >
977
- <a
978
- v-for="tag in tags"
979
- :key="tag.criteria['meta.tags']"
980
- class="list-group-item list-group-item-action p-2 small"
981
- @click="applyFilter(tag)"
982
- style="cursor: pointer"
983
- >
984
- {{ tag.title }}
985
- </a>
986
- </div>
987
- <div v-else class="text-muted small p-2">Loading tags...</div>
988
- <div class="border-top pt-1">
989
- <button
990
- class="btn btn-sm btn-outline-secondary m-2"
991
- @click="refreshTags"
992
- >
993
- Refresh Tags
994
- </button>
995
- </div>
996
- </div>
997
- </div>
998
- </div>
999
- </div>
1000
- <!-- <button
1001
- class="btn btn-primary btn-lg px-4 py-2 shadow"
1002
- @click="loadData"
1003
- >
1004
- <span
1005
- v-if="loading"
1006
- class="spinner-border spinner-border-sm me-2"
1007
- ></span>
1008
- {{ loading ? 'Loading...' : 'Search' }}
1009
- </button> -->
1010
- </div>
1011
- </div>
1012
-
1013
- <!-- PANE 2: Results (65%) -->
1014
- <div id="pane2" class="flex-grow-1 d-flex flex-column border-end">
1015
- <nav class="navbar bg-body-tertiary">
1016
- <div class="container-fluid">
1017
- <a class="navbar-brand"> Results</a>
1018
- <div class="d-flex">
1019
- <small class="badge bg-secondary"
1020
- >{{ recordCount }} records</small
1021
- >
1022
- </div>
1023
- </div>
1024
- </nav>
1025
- <div ref="tableRef" class="flex-grow-1 tabulator overflow-hidden"></div>
1026
- </div>
1027
-
1028
- <!-- PANE 3: Editor (25%) -->
1029
- <div id="pane3" class="d-flex flex-column" style="min-width: 280px">
1030
- <nav class="navbar bg-body-tertiary">
1031
- <div class="container-fluid">
1032
- <a class="navbar-brand"> Doc</a>
1033
- <div class="d-flex">
1034
- <input
1035
- class="form-control me-2 form-control-sm"
1036
- type="search"
1037
- placeholder="Search"
1038
- aria-label="Search"
1039
- />
1040
- <button class="btn btn-sm">
1041
- <svg
1042
- xmlns="http://www.w3.org/2000/svg"
1043
- width="15"
1044
- height="15"
1045
- fill="currentColor"
1046
- class="bi bi-search"
1047
- viewBox="0 0 16 16"
1048
- >
1049
- <path
1050
- d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"
1051
- />
1052
- </svg>
1053
- </button>
1054
- </div>
1055
- </div>
1056
- </nav>
1057
- <!-- <div class="p-3 border-bottom bg-dark-subtle">
1058
- <h6 class="mb-0 fw-bold text-white small">Editor</h6>
1059
- </div> -->
1060
-
1061
- <!-- <div
1062
- class="flex-grow-1 p-4 d-flex align-items-center justify-content-center"
1063
- >
1064
- <div
1065
- class="card border-0 shadow-sm w-100 h-100 d-flex align-items-center justify-content-center"
1066
- >
1067
-
1068
- <div class="text-center text-muted px-3">
1069
- <div class="h4 mb-2"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="currentColor" class="bi bi-file-text" viewBox="0 0 16 16">
1070
- <path d="M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5M5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z"/>
1071
- <path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1"/>
1072
- </svg></div>
1073
- <div class="fw-medium mb-1">Select a doc</div>
1074
- <small>to start editing</small>
1075
- </div>
1076
- </div>
1077
- </div> -->
1078
- </div>
1079
- </div>
1080
- <script
1081
- src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
1082
- integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
1083
- crossorigin="anonymous"
1084
- ></script>
1085
- </body>
1086
- </html>