quar 1.2.3 → 1.2.5

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Quar Studio – Quick UI for Mongoose Models
2
2
 
3
3
  <p align="center">
4
- <img src="./public/assets/icon.png" alt="logo" width="200px"/>
4
+ <img src="./assets/icon.png" alt="logo" width="200px"/>
5
5
  </p>
6
6
 
7
7
  Instantly spin up a web-based editor for your Mongoose models.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quar",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "This will load all Mongoose models from the folder and start a local web UI to Create, view, update, and delete documents.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -20,5 +20,13 @@
20
20
  "mongoose": "^8.14.1",
21
21
  "open": "^10.1.2",
22
22
  "zare": "^2.1.1"
23
- }
24
- }
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/IsmailBinMujeeb/quar-studio.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/IsmailBinMujeeb/quar-studio/issues"
30
+ },
31
+ "homepage": "https://github.com/IsmailBinMujeeb/quar-studio#readme"
32
+ }
Binary file
@@ -0,0 +1,155 @@
1
+ async function toggleInsertTab() {
2
+ const modelName = content.dataset?.modelName;
3
+ if (!modelName) {
4
+ showModal('error', 'Error Occurred!', 'No model selected.');
5
+ return;
6
+ }
7
+
8
+ const insertTab = document.querySelector('.insert-tab');
9
+ insertTab.classList.toggle('active');
10
+
11
+ if (insertTab.innerHTML.trim() !== '') return;
12
+
13
+ try {
14
+ const res = await fetch(`/schema/${modelName}`);
15
+ if (!res.ok) throw new Error();
16
+
17
+ const schema = await res.json();
18
+
19
+ const form = document.createElement('form');
20
+ form.id = 'insert-form';
21
+
22
+ form.addEventListener('submit', async (e) => {
23
+ e.preventDefault();
24
+
25
+ const formData = new FormData(e.target);
26
+ const data = Object.fromEntries(formData.entries());
27
+
28
+ const checkboxes = e.target.querySelectorAll('input[type="checkbox"]');
29
+ checkboxes.forEach(cb => {
30
+ data[cb.name] = cb.checked;
31
+ });
32
+
33
+ const textareas = e.target.querySelectorAll('textarea');
34
+ textareas.forEach(async tarea => {
35
+
36
+ data[tarea.name] = JSON.parse(tarea.value)
37
+
38
+ })
39
+
40
+ const res = await fetch(`/insert/${modelName}`, {
41
+ method: 'POST',
42
+ headers: { 'Content-Type': 'application/json' },
43
+ body: JSON.stringify(data)
44
+ });
45
+
46
+ if (!res.ok) {
47
+ const error = await res.json();
48
+ showModal('error', 'Error Occurred!', error.error || 'Something went wrong, please try again.');
49
+ return;
50
+ }
51
+
52
+ loadDocuments();
53
+ });
54
+
55
+
56
+ for (const key in schema) {
57
+
58
+ if (/\.\$\*$/.test(key)) continue;
59
+ const field = schema[key];
60
+ let inputType = null;
61
+
62
+ const label = document.createElement('label');
63
+ label.innerHTML = `${key}:`;
64
+ label.className = 'label';
65
+ label.htmlFor = key;
66
+
67
+ if (field.type === 'ObjectId') {
68
+ const idRes = await fetch(`/id/${field.ref}`);
69
+ if (!idRes.ok) {
70
+ const error = await idRes.json();
71
+ showModal('error', 'Error Occurred!', error.error || 'Something went wrong, please try again.');
72
+ return;
73
+ };
74
+
75
+ const ids = await idRes.json();
76
+ const select = document.createElement('select');
77
+ select.id = key;
78
+ select.name = key;
79
+ select.className = 'input';
80
+
81
+ ids.forEach(id => {
82
+ const option = document.createElement('option');
83
+ option.value = id;
84
+ option.innerText = id;
85
+ select.appendChild(option);
86
+ });
87
+
88
+ form.append(label, select);
89
+ continue;
90
+ }
91
+
92
+ if (field.type === 'String') inputType = 'text';
93
+ else if (field.type === 'Number') inputType = 'number';
94
+ else if (field.type === 'Boolean') inputType = 'checkbox';
95
+ else if (field.type === 'Date') inputType = 'date';
96
+ else if (field.type === 'Array' || field.type === "Map" || field.type === "Object" || field.type == "Mixed" || field.type === "Buffer") {
97
+ const input = document.createElement('textarea');
98
+
99
+ input.placeholder = field.type;
100
+ input.id = key;
101
+ input.name = key;
102
+ input.className = 'input';
103
+ field.type === "Array" ? input.textContent = "[ ]" : input.textContent = "{ }";
104
+ input.required = isRequired(field.require);
105
+ if (field.default !== undefined) input.value = field.default;
106
+
107
+ form.append(label, input);
108
+ continue;
109
+ };
110
+
111
+ if (field.enum) {
112
+ const select = document.createElement('select');
113
+ select.id = key;
114
+ select.name = key;
115
+ select.className = 'input';
116
+
117
+ field.enum.forEach(id => {
118
+ const option = document.createElement('option');
119
+ option.value = id;
120
+ option.innerText = id;
121
+ select.appendChild(option);
122
+ });
123
+
124
+ form.append(label, select);
125
+ continue;
126
+ }
127
+
128
+ const input = document.createElement('input');
129
+ input.type = inputType || 'text';
130
+ input.placeholder = field.type;
131
+ input.id = key;
132
+ input.name = key;
133
+ input.className = 'input';
134
+ input.required = isRequired(field.require);
135
+ if (field.default !== undefined) input.value = field.default;
136
+
137
+ form.append(label, input);
138
+ }
139
+
140
+ const submitBtn = document.createElement('button');
141
+ submitBtn.type = 'submit';
142
+ submitBtn.innerHTML = 'Submit';
143
+ submitBtn.className = 'btn';
144
+ form.appendChild(submitBtn);
145
+
146
+ insertTab.appendChild(form);
147
+
148
+ } catch (error) {
149
+ showModal('error', 'Error Occurred!', error.message || 'Something went wrong, please try again.');
150
+ }
151
+ }
152
+
153
+ function isRequired(require) {
154
+ return Array.isArray(require) ? require[0] : require;
155
+ }
@@ -0,0 +1,16 @@
1
+ document.addEventListener("keydown", (e) => {
2
+
3
+ if (e.ctrlKey && e.key === 'o') {
4
+ e.preventDefault();
5
+ toggleInsertTab();
6
+ } else if (e.ctrlKey && e.key == "r") {
7
+ e.preventDefault()
8
+ loadDocuments();
9
+ } else if (e.ctrlKey && e.key == ".") {
10
+ e.preventDefault()
11
+ gotoNextPage();
12
+ } else if (e.ctrlKey && e.key == ",") {
13
+ e.preventDefault()
14
+ gotoPreviousPage();
15
+ }
16
+ })
@@ -0,0 +1,365 @@
1
+ const tabsContainer = document.getElementById("tabs");
2
+ const content = document.getElementById("content");
3
+ let currentModelName = "";
4
+ let loadedDocuments = [];
5
+ const openTabs = {};
6
+
7
+ function openModel(modelName) {
8
+
9
+ if (!openTabs[modelName]) {
10
+ // Create a new tab
11
+ const tab = document.createElement("div");
12
+ tab.className = "tab";
13
+ tab.id = "tab-" + modelName;
14
+ tab.draggable = true;
15
+ tab.innerHTML = modelName + '<span class="close" onclick="closeTab(event, \'' + modelName + '\')">×</span>';
16
+ tab.onclick = () => activateTab(modelName);
17
+ tabsContainer.appendChild(tab);
18
+
19
+ tab.addEventListener("dragstart", dragStart);
20
+ tab.addEventListener("dragover", dragOver);
21
+ tab.addEventListener("drop", drop);
22
+ tab.addEventListener("dragend", dragEnd);
23
+
24
+ openTabs[modelName] = tab;
25
+ }
26
+
27
+
28
+ activateTab(modelName);
29
+ }
30
+
31
+ async function activateTab(modelName) {
32
+
33
+ // Prevent if tab is already active
34
+ if (content.dataset?.modelName == modelName) return
35
+
36
+ content.dataset.modelName = modelName;
37
+ document.getElementById("page-value").innerText = 1;
38
+ document.querySelectorAll(".operations .btn")?.forEach( btn => btn.disabled = false )
39
+ document.querySelector(".operations select").disabled = false;
40
+
41
+ const insertTab = document.querySelector('.insert-tab');
42
+ insertTab.classList.remove('active');
43
+ insertTab.innerHTML = "";
44
+ // Remove active class from all tabs
45
+ document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
46
+
47
+ // Set current tab active
48
+ const currentTab = document.getElementById("tab-" + modelName);
49
+ currentTab.classList.add("active");
50
+
51
+ // Update content
52
+ await loadDocuments();
53
+ }
54
+
55
+ function closeTab(event, modelName) {
56
+ event.stopPropagation(); // prevent tab click
57
+ const tab = document.getElementById("tab-" + modelName);
58
+ tab.remove();
59
+ delete openTabs[modelName];
60
+
61
+ // Clear content if that tab was active and check if other tabs are open to be active
62
+ if (tab.classList.contains("active")) {
63
+ content.innerHTML = "";
64
+ const keys = Object.keys(openTabs);
65
+ const modelName = keys[keys.length - 1] || null;
66
+ if (modelName) activateTab(modelName);
67
+ else disableAllOptions();
68
+ }
69
+ }
70
+
71
+ function disableAllOptions() {
72
+ content.dataset.modelName = '';
73
+
74
+ document.querySelectorAll(".operations .btn")?.forEach( btn => btn.disabled = true );
75
+ document.getElementById("document-count").innerText = 0;
76
+ document.querySelector(".operations select").disabled = true;
77
+ }
78
+
79
+ async function loadDocuments() {
80
+
81
+ content.innerHTML = "";
82
+ const limit = document.getElementById("limit").value;
83
+ const page = document.getElementById("page-value").innerText;
84
+ const modelName = content.dataset?.modelName;
85
+
86
+ if (!modelName) {
87
+ showModal('error', 'Error Occurred!', 'No model selected.');
88
+ return;
89
+ }
90
+
91
+ const response = await fetch(`/models/${modelName}?limit=${limit}&page=${page}`);
92
+ if (response.status !== 200) {
93
+ showModal('error', 'Error Occurred!', 'Something went wrong, please try again.');
94
+ return;
95
+ }
96
+ const data = await response.json();
97
+
98
+ document.getElementById("document-count").innerText = data.count;
99
+ document.getElementById("page-value").dataset.totalPages = data.totalPages;
100
+ document.getElementById(`${modelName}-doc-count`).innerText = data.totalCount;
101
+
102
+ if (Number(page) >= Number(data.totalPages)) document.getElementById("next-page").disabled = true;
103
+ else document.getElementById("next-page").disabled = false;
104
+
105
+ if (Number(page) <= 1) document.getElementById("previous-page").disabled = true;
106
+ else document.getElementById("previous-page").disabled = false;
107
+
108
+ currentModelName = modelName;
109
+ loadedDocuments = data.documents;
110
+ // Render each array element as its own tree
111
+ data.documents.forEach((doc, index) => {
112
+ const wrapper = document.createElement('div');
113
+ const divDocument = document.createElement('div');
114
+ divDocument.innerHTML += Object.entries(doc).map(([key, value]) => renderData(key, value, index)).join('');
115
+
116
+ divDocument.classList.add('document');
117
+
118
+ wrapper.appendChild(divDocument);
119
+ wrapper.innerHTML += `<div class="document-actions"><button class="updateDocument" onclick="updateDocument(${index})">Update</button><button class="deleteDocument" onclick="deleteDoc('${modelName}', '${doc._id}')">Delete</button></div>`;
120
+
121
+ content.appendChild(wrapper);
122
+ });
123
+ }
124
+
125
+ function toggleEditMode(id) {
126
+ const htmlData = document.getElementById(`${id}-html-data`);
127
+ const jsonData = document.getElementById(`${id}-json-data`);
128
+
129
+ htmlData.classList.toggle('hidden');
130
+ jsonData.classList.toggle('hidden');
131
+
132
+ }
133
+
134
+ function gotoNextPage() {
135
+ const page = document.getElementById("page-value");
136
+ page.innerText = Number(page.innerText) + 1;
137
+
138
+ if (Number(page.innerText) > Number(page.dataset.totalPages)) {
139
+ page.innerText = Number(page.dataset.totalPages);
140
+ return;
141
+ }
142
+
143
+ loadDocuments();
144
+ }
145
+
146
+ function gotoPreviousPage() {
147
+ const page = document.getElementById("page-value").innerText;
148
+ document.getElementById("page-value").innerText = Number(page) - 1;
149
+
150
+ if (Number(page) - 1 < 1) {
151
+ document.getElementById("page-value").innerText = 1;
152
+ return;
153
+ }
154
+
155
+ loadDocuments();
156
+ }
157
+
158
+ function renderData(key, value, index, level = "root.", parentDataType = "object") {
159
+ const type = typeof value;
160
+ if (value === null) return `<div class="field"><span class="data-key">${key}</span>: <span class="null" data-level="${level}" data-index="${index}">null</span><span class="data-type">(null)</span></div>`;
161
+
162
+ if (Array.isArray(value)) {
163
+ return `
164
+ <details class="field">
165
+ <summary ><span class="data-key">${key}</span><span class="data-type"> (Array - ${value.length})</span></summary>
166
+ <div class="data-value">
167
+ ${value.map((v, i) => renderData(`[${i}]`, v, index, level + key, "array")).join('')}
168
+ </div>
169
+ </details>
170
+ `;
171
+ } else if (type === 'object') {
172
+ return `
173
+ <details class="field">
174
+ <summary><span class="data-key">${key}</span><span class="data-type"> (Object)</span></summary>
175
+ <div class="data-value">
176
+ ${Object.entries(value).map(([k, v]) => renderData(k, v, index, level + key, "object")).join('')}
177
+ </div>
178
+ </details>
179
+ `;
180
+ } else if (typeof value === 'string' && /^[a-f\d]{24}$/i.test(value)) {
181
+
182
+ return `<div class="field"><span class="data-key">${key}</span>: <span class="ObjectId">${value}</span></div>`;
183
+ } else {
184
+ return `<div class="field"><span class="data-key">${key}</span>: <span class="${type}" data-level="${level}" data-index="${index}" data-parent-data-type="${parentDataType}" contenteditable >${value}</span></div>`;
185
+ }
186
+ }
187
+
188
+ // Handle collapsibles
189
+ document.addEventListener('click', function (e) {
190
+ if (e.target.classList.contains('collapsible')) {
191
+ e.target.classList.toggle("caret-down");
192
+ const nested = e.target.nextElementSibling;
193
+ if (nested) {
194
+ nested.classList.toggle("active");
195
+ }
196
+ }
197
+ });
198
+
199
+ function setNestedValue(obj, path, value, parentDataType) {
200
+ const keys = path.split(".");
201
+ let current = obj;
202
+
203
+ keys.forEach((key, index) => {
204
+ if (index === keys.length - 1) {
205
+ const arrayIndexMatch = key.match(/\[(\d+)\]/);
206
+ if (arrayIndexMatch) {
207
+ const idx = parseInt(arrayIndexMatch[1], 10);
208
+ current[idx] = value;
209
+ } else {
210
+ current[key] = value;
211
+ }
212
+
213
+ } else {
214
+ if (parentDataType === "array" && !Array.isArray(current[key])) {
215
+ current[key] = [];
216
+ } else if (parentDataType === "object" && typeof current[key] !== "object") {
217
+ current[key] = {};
218
+ }
219
+
220
+ current = current[key]; // Go deeper
221
+ }
222
+ });
223
+ }
224
+
225
+ async function updateDocument(index) {
226
+
227
+ const confirmUpdate = confirm("Are you sure you want to update this document?");
228
+ if (!confirmUpdate) return;
229
+
230
+ const doc = loadedDocuments[index];
231
+ const id = doc._id;
232
+
233
+ let updatedDoc = {};
234
+ const values = document.querySelectorAll(`[data-index="${index}"]`);
235
+
236
+ values.forEach(el => {
237
+ const key = el.previousElementSibling?.textContent?.trim();
238
+ const level = el.dataset.level;
239
+
240
+ if (key) {
241
+ if (level && level !== "root") {
242
+ // Build the full path
243
+ const fullPath = `${level.replace("root.", "")}.${key}`;
244
+ setNestedValue(updatedDoc, fullPath, el.textContent, el.dataset.parentDataType);
245
+ } else {
246
+ updatedDoc[key] = el.textContent;
247
+ }
248
+ }
249
+ });
250
+
251
+ updatedDoc = { ...updatedDoc, ...updatedDoc[""] }
252
+
253
+ const res = await fetch(`/update/${currentModelName}/${id}`, {
254
+ method: "PUT",
255
+ headers: { "Content-Type": "application/json" },
256
+ body: JSON.stringify(updatedDoc),
257
+ });
258
+
259
+ if (res.ok) {
260
+ showModal('info', 'Document Updated', 'Document updated successfully.');
261
+ loadDocuments();
262
+ } else {
263
+ const data = await res.json();
264
+ showModal('error', 'Update Failed', data.error || 'Update failed, please try again.');
265
+ }
266
+ }
267
+
268
+ async function deleteDoc(modelName, id) {
269
+ const confirmDelete = confirm("Are you sure you want to delete this document?");
270
+ if (!confirmDelete) return;
271
+
272
+ const res = await fetch(`/delete/${modelName}/${id}`, {
273
+ method: "DELETE"
274
+ });
275
+
276
+ if (res.ok) {
277
+ showModal('info', 'Document Deleted', 'Document deleted successfully.');
278
+ openModel(modelName);
279
+ const data = await res.json();
280
+ document.getElementById(`${modelName}-doc-count`).innerText = data.count || 0;
281
+ loadDocuments()
282
+ } else {
283
+ showModal('error', 'Delete Failed', await res.json().error || 'Delete failed, please try again.');
284
+ }
285
+ }
286
+
287
+ async function deleteAllDocs() {
288
+ try {
289
+ const confirmDelete = confirm("Are you sure you want to delete all documents?");
290
+ if (!confirmDelete) return;
291
+
292
+ const modelName = content.dataset?.modelName;
293
+ if (!modelName) {
294
+ showModal('error', 'Error Occurred!', 'No model selected.');
295
+ return;
296
+ }
297
+
298
+ const res = await fetch(`/delete-all/${modelName}`, { method: "DELETE" });
299
+ if (res.ok) {
300
+ showModal('info', 'All Documents Deleted', 'All documents deleted successfully.');
301
+ loadDocuments();
302
+ } else {
303
+ showModal('error', 'Delete Failed', await res.json().error || 'Delete failed, please try again.');
304
+ }
305
+ } catch (error) {
306
+ showModal('error', 'Delete Failed', error.message || 'Delete failed, please try again.');
307
+ }
308
+ }
309
+
310
+ function dragStart(e) {
311
+ dragSrc = this;
312
+ e.dataTransfer.effectAllowed = "move";
313
+ this.classList.add("dragging");
314
+ }
315
+
316
+ function dragOver(e) {
317
+ e.preventDefault();
318
+ e.dataTransfer.dropEffect = "move";
319
+ }
320
+
321
+ function drop(e) {
322
+ e.preventDefault();
323
+ if (dragSrc !== this) {
324
+ // Swap positions
325
+ const draggedIndex = Array.from(tabsContainer.children).indexOf(dragSrc);
326
+ const targetIndex = Array.from(tabsContainer.children).indexOf(this);
327
+
328
+ if (draggedIndex < targetIndex) {
329
+ tabsContainer.insertBefore(dragSrc, this.nextSibling);
330
+ } else {
331
+ tabsContainer.insertBefore(dragSrc, this);
332
+ }
333
+ }
334
+ }
335
+
336
+ function dragEnd() {
337
+ this.classList.remove("dragging");
338
+ }
339
+
340
+ async function refreshSideBar() {
341
+ try {
342
+ const modelWrapper = document.querySelector('.model-wrapper');
343
+
344
+ const res = await fetch("/models");
345
+
346
+ if (!res.ok) {
347
+ const error = await res.json();
348
+ showModal('error', 'Error Occurred!', error.error || 'Something went wrong, please try again.');
349
+ return;
350
+ }
351
+
352
+ modelWrapper.innerHTML = "";
353
+ const data = await res.json();
354
+
355
+ data.models.forEach(model => {
356
+ const modelDiv = document.createElement('div');
357
+ modelDiv.classList.add('model');
358
+ modelDiv.onclick = () => openModel(model.name);
359
+ modelDiv.innerHTML = `${model.name} <span id="${model.name}-doc-count">${model.count}</span>`;
360
+ modelWrapper.appendChild(modelDiv);
361
+ });
362
+ } catch (error) {
363
+ showModal('error', 'Error Occurred!', error.message || 'Something went wrong, please try again.');
364
+ }
365
+ }
@@ -0,0 +1,26 @@
1
+ function showModal(type, title, message) {
2
+ const popup = document.getElementById('popup');
3
+ const box = document.getElementById('popupBox');
4
+ const titleEl = document.getElementById('popupTitle');
5
+ const msgEl = document.getElementById('popupMessage');
6
+
7
+ box.className = 'popup-box';
8
+ if (type === 'warning') box.classList.add('warning');
9
+ else if (type === 'info') box.classList.add('info');
10
+
11
+ titleEl.textContent = title;
12
+ msgEl.textContent = message;
13
+
14
+ popup.style.display = 'flex';
15
+ }
16
+
17
+ function closePopup() {
18
+ document.getElementById('popup').style.display = 'none';
19
+ }
20
+
21
+ window.onclick = function (e) {
22
+ const popup = document.getElementById('popup');
23
+ if (e.target === popup) {
24
+ closePopup();
25
+ }
26
+ }
@@ -0,0 +1,74 @@
1
+ @import './variables.css';
2
+
3
+ .insert-tab {
4
+ width: 40%;
5
+ max-width: 400px;
6
+ height: 100%;
7
+ background-color: var(--bg-color);
8
+ position: fixed;
9
+ top: 0;
10
+ right: -100%;
11
+ z-index: 1000;
12
+ overflow-y: auto;
13
+ padding: 20px;
14
+ border-left: 2px solid var(--muted-color);
15
+ transition: right 0.3s ease-in-out;
16
+ }
17
+
18
+ .insert-tab.active {
19
+ right: 0;
20
+ }
21
+
22
+ #insert-form {
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: 10px;
26
+ }
27
+
28
+ #insert-form label {
29
+ font-size: 16px;
30
+ font-weight: 600;
31
+ color: var(--text-color);
32
+ }
33
+
34
+ #insert-form input,
35
+ select,
36
+ textarea {
37
+ width: 100%;
38
+ padding: 10px;
39
+ border: 1px solid var(--muted-color);
40
+ border-radius: 5px;
41
+ background-color: var(--bg-color);
42
+ color: var(--text-color);
43
+ }
44
+
45
+ #insert-form textarea {
46
+ resize: vertical;
47
+ min-height: fit-content;
48
+ }
49
+
50
+ #insert-form input[type="checkbox"] {
51
+ width: 16px;
52
+ height: 16px;
53
+ accent-color: var(--green-color);
54
+ background-color: var(--bg-color);
55
+ border: 1px solid var(--muted-color);
56
+ border-radius: 10px;
57
+ cursor: pointer;
58
+ }
59
+
60
+ #insert-form .btn {
61
+ background: transparent;
62
+ color: var(--text-color);
63
+ border: none;
64
+ padding: 5px 10px;
65
+ border: 1px solid var(--muted-color);
66
+ border-radius: 5px;
67
+ transition: background 0.1s ease-in-out;
68
+ cursor: pointer;
69
+
70
+ &:hover {
71
+ background: var(--blue-color);
72
+ color: var(--bg-color);
73
+ }
74
+ }
@@ -0,0 +1,71 @@
1
+ @import './variables.css';
2
+
3
+ .popup-overlay {
4
+ display: none;
5
+ position: fixed;
6
+ top: 0;
7
+ left: 0;
8
+ width: 100vw;
9
+ height: 100vh;
10
+ background: rgba(0, 0, 0, 0.5);
11
+ justify-content: center;
12
+ align-items: center;
13
+ z-index: 9999;
14
+ }
15
+
16
+ .popup-box {
17
+ background: var(--bg-color);
18
+ padding: 20px 25px;
19
+ border-radius: 10px;
20
+ max-width: 400px;
21
+ text-align: center;
22
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
23
+ animation: slideIn 0.4s ease;
24
+ position: relative;
25
+ }
26
+
27
+ .popup-box h2 {
28
+ margin-top: 0;
29
+ color: var(--red-color);
30
+ }
31
+
32
+ .popup-box.warning h2 {
33
+ color: var(--yellow-color);
34
+ }
35
+
36
+ .popup-box.info h2 {
37
+ color: var(--green-color);
38
+ }
39
+
40
+ .popup-box p {
41
+ margin: 10px 0 20px;
42
+ }
43
+
44
+ .close-btn {
45
+ background: var(--red-color);
46
+ color: white;
47
+ border: none;
48
+ padding: 8px 16px;
49
+ border-radius: 5px;
50
+ cursor: pointer;
51
+ }
52
+
53
+ .popup-box.warning .close-btn {
54
+ background: var(--yellow-color);
55
+ }
56
+
57
+ .popup-box.info .close-btn {
58
+ background: var(--green-color);
59
+ }
60
+
61
+ @keyframes slideIn {
62
+ from {
63
+ transform: translateY(-30px);
64
+ opacity: 0;
65
+ }
66
+
67
+ to {
68
+ transform: translateY(0);
69
+ opacity: 1;
70
+ }
71
+ }
@@ -0,0 +1,342 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ body {
10
+ font-family: 'Inter', sans-serif;
11
+ font-size: 14px;
12
+ display: flex;
13
+ background: var(--bg-color);
14
+ color: #E0E0E0;
15
+ }
16
+
17
+ .sidebar {
18
+ position: fixed;
19
+ top: 0;
20
+ width: 15vw;
21
+ background: #282b30;
22
+ border-right: 1px solid var(--muted-color);
23
+ height: 100vh;
24
+ }
25
+
26
+ .sidebar-header {
27
+ font-size: 13px;
28
+ font-weight: 600;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: space-between;
32
+ background: #1e2124;
33
+ padding: 10px;
34
+ border-bottom: 1px solid var(--muted-color);
35
+ }
36
+
37
+ .sidebar-header .btn {
38
+ color: #E0E0E0;
39
+ background-color: transparent;
40
+ border: none;
41
+ font-size: 12px;
42
+ padding: 0 4px;
43
+ border-radius: 5px;
44
+ cursor: pointer;
45
+ transition: background 0.1s ease-in-out;
46
+
47
+ &:hover {
48
+ background: #2E3A59;
49
+ }
50
+ }
51
+
52
+ .model-wrapper {
53
+ padding: 10px;
54
+ }
55
+
56
+ .model {
57
+ cursor: pointer;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: space-between;
61
+ padding: 5px;
62
+ margin: 5px 0;
63
+ border-radius: 5px;
64
+ color: #E0E0E0;
65
+
66
+ &:hover {
67
+ background: #2E3A59;
68
+ }
69
+ }
70
+
71
+ .model span {
72
+ color: var(--muted-color);
73
+ }
74
+
75
+ .main {
76
+ margin-left: auto;
77
+ height: 100vh;
78
+ width: 85vw;
79
+ display: flex;
80
+ flex-direction: column;
81
+ }
82
+
83
+ .tabs {
84
+ display: flex;
85
+ border-bottom: 1px solid var(--muted-color);
86
+ background: #1e2124;
87
+ }
88
+
89
+ .tab {
90
+ padding: 5px 10px;
91
+ display: flex;
92
+ align-items: center;
93
+ color: var(--muted-color);
94
+ border-left: 1px solid var(--muted-color);
95
+ border-right: 1px solid var(--muted-color);
96
+ position: relative;
97
+ margin: 1px 0;
98
+ cursor: pointer;
99
+ transition: color 0.2s ease-in-out;
100
+
101
+ &:hover {
102
+ color: white;
103
+ }
104
+ }
105
+
106
+
107
+ .tab.active {
108
+ color: white;
109
+ background-color: #2E3A59;
110
+ }
111
+
112
+ .tab .close {
113
+ cursor: pointer;
114
+ font-weight: bold;
115
+ padding: 0 5px;
116
+ }
117
+
118
+ .operations {
119
+ display: flex;
120
+ background: #1e2124;
121
+ align-items: center;
122
+ padding: 8px 5px;
123
+ gap: 10px;
124
+ }
125
+
126
+ .operations .btn {
127
+ background: var(--muted-color);
128
+ color: #E0E0E0;
129
+ border: none;
130
+ padding: 4px 8px;
131
+ border-radius: 5px;
132
+ cursor: pointer;
133
+ transition: background 0.1s ease-in-out;
134
+
135
+ &:hover {
136
+ background: #2E3A59;
137
+ }
138
+ }
139
+
140
+ .operations .btn:disabled {
141
+ background-color: #3a3f4b;
142
+ color: #888;
143
+ cursor: not-allowed;
144
+ }
145
+
146
+ .operations .limit-wrapper,
147
+ .count-wrapper,
148
+ .page-wrapper {
149
+ display: flex;
150
+ align-items: center;
151
+ padding: 2px 5px;
152
+ gap: 5px;
153
+ border-radius: 5px;
154
+ background-color: var(--muted-color);
155
+ cursor: pointer;
156
+
157
+ &:hover {
158
+ background: #2E3A59;
159
+ }
160
+ }
161
+
162
+ .operations .limit-wrapper:hover #limit {
163
+ background: #2E3A59;
164
+ }
165
+
166
+ .operations .text {
167
+ padding: 2px 5px;
168
+ width: 100%;
169
+ height: 100%;
170
+ font-size: 12px;
171
+ }
172
+
173
+ .operations .limit-div,
174
+ .count-div,
175
+ .page-div {
176
+ width: fit-content;
177
+ border-left: 2px solid #424549;
178
+ height: 100%;
179
+ color: #E0E0E0;
180
+ }
181
+
182
+ .operations .count-div,
183
+ .page-div {
184
+ padding: 2px 5px;
185
+ font-size: 12px;
186
+ }
187
+
188
+ .operations .limit-wrapper #limit {
189
+ background-color: var(--muted-color);
190
+ border: none;
191
+ color: #E0E0E0;
192
+ padding: 2px 5px;
193
+ width: fit-content;
194
+ height: 100%;
195
+ font-size: 12px;
196
+
197
+ &:hover {
198
+ background: #2E3A59;
199
+ }
200
+ }
201
+
202
+ .content {
203
+ padding: 10px;
204
+ flex: 1;
205
+ border-top: 1px solid var(--muted-color);
206
+ background: #23272a;
207
+ overflow-y: scroll;
208
+ -ms-overflow-style: none;
209
+ scrollbar-width: none;
210
+ }
211
+
212
+ .content::-webkit-scrollbar {
213
+ display: none;
214
+ }
215
+
216
+ .hidden {
217
+ display: none;
218
+ }
219
+
220
+ summary::marker{
221
+ color: var(--muted-color);
222
+ font-size: 10px;
223
+ margin-right: 10px;
224
+ }
225
+
226
+ .document {
227
+ padding: 10px;
228
+ border: 1px solid var(--muted-color);
229
+ background: #1e2124;
230
+ border-radius: 10px;
231
+ margin-top: 10px;
232
+ }
233
+
234
+ .field {
235
+ padding: 5px 20px;
236
+
237
+ }
238
+
239
+ .field .string {
240
+ color: var(--green-color);
241
+ }
242
+
243
+ .field .string::after {
244
+ content: '"';
245
+ color: var(--green-color);
246
+ }
247
+
248
+ .field .string::before {
249
+ content: '"';
250
+ color: var(--green-color);
251
+ }
252
+
253
+ .field .number {
254
+ color: var(--blue-color);
255
+ }
256
+
257
+ .field .ObjectId {
258
+ color: var(--red-color);
259
+ }
260
+
261
+ .field .ObjectId::after {
262
+ content: ' )';
263
+ color: var(--red-color);
264
+ }
265
+
266
+ .field .ObjectId::before {
267
+ content: 'ObjectId( ';
268
+ color: var(--red-color);
269
+ }
270
+
271
+ .json-data {
272
+ width: 100%;
273
+ height: auto;
274
+ margin-top: 10px;
275
+ background: #1e2124;
276
+ border: none;
277
+ color: #E0E0E0;
278
+ padding: 10px;
279
+ border-radius: 10px;
280
+ resize: none;
281
+ }
282
+
283
+ .document-actions {
284
+ display: flex;
285
+ justify-content: space-between;
286
+ padding: 10px;
287
+ }
288
+
289
+ .document-actions .updateDocument {
290
+ background: transparent;
291
+ color: #E0E0E0;
292
+ border: none;
293
+ padding: 5px 10px;
294
+ border: 1px solid var(--muted-color);
295
+ border-radius: 5px;
296
+ transition: background 0.1s ease-in-out;
297
+ cursor: pointer;
298
+
299
+ &:hover {
300
+ background: var(--green-color);
301
+ color: var(--bg-color);
302
+ }
303
+ }
304
+
305
+ .document-actions .deleteDocument {
306
+ background: transparent;
307
+ color: #E0E0E0;
308
+ border: none;
309
+ padding: 5px 10px;
310
+ border: 1px solid var(--muted-color);
311
+ border-radius: 5px;
312
+ transition: background 0.1s ease-in-out;
313
+ cursor: pointer;
314
+
315
+ &:hover {
316
+ background: var(--red-color);
317
+ color: var(--bg-color);
318
+ }
319
+ }
320
+
321
+ .top-container {
322
+ display: flex;
323
+ justify-content: left;
324
+ width: 100%;
325
+ padding: 0 10px;
326
+ }
327
+
328
+ .top-container .editDocument {
329
+ background: transparent;
330
+ color: #E0E0E0;
331
+ border: none;
332
+ padding: 5px 10px;
333
+ border: 1px solid var(--muted-color);
334
+ border-radius: 5px;
335
+ transition: background 0.1s ease-in-out;
336
+ cursor: pointer;
337
+
338
+ &:hover {
339
+ background: var(--blue-color);
340
+ color: var(--bg-color);
341
+ }
342
+ }
@@ -0,0 +1,9 @@
1
+ :root{
2
+ --bg-color: #171f2a;
3
+ --text-color: #fff;
4
+ --muted-color: #555A6B;
5
+ --blue-color: #4FC3F7;
6
+ --green-color: #00ffaa;
7
+ --red-color: #FF5370;
8
+ --yellow-color: #FFEB3B;
9
+ }