quar 1.1.0 → 1.2.1

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/LICENSE CHANGED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ismail Bin Mujeeb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quar",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
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",
@@ -99,12 +99,27 @@ async function loadDocuments() {
99
99
  // Render each array element as its own tree
100
100
  data.documents.forEach((doc, index) => {
101
101
  const wrapper = document.createElement('div');
102
- wrapper.appendChild(createTree(doc, data.schema, index));
102
+ const divDocument = document.createElement('div');
103
+ divDocument.innerHTML += Object.entries(doc).map(([key, value]) => renderData(key, value, index)).join('');
104
+
105
+ divDocument.classList.add('document');
106
+
107
+ wrapper.appendChild(divDocument);
103
108
  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>`;
104
- content.innerHTML += wrapper.innerHTML;
109
+
110
+ content.appendChild(wrapper);
105
111
  });
106
112
  }
107
113
 
114
+ function toggleEditMode(id) {
115
+ const htmlData = document.getElementById(`${id}-html-data`);
116
+ const jsonData = document.getElementById(`${id}-json-data`);
117
+
118
+ htmlData.classList.toggle('hidden');
119
+ jsonData.classList.toggle('hidden');
120
+
121
+ }
122
+
108
123
  function gotoNextPage() {
109
124
  const page = document.getElementById("page-value");
110
125
  page.innerText = Number(page.innerText) + 1;
@@ -129,33 +144,34 @@ function gotoPreviousPage() {
129
144
  loadDocuments();
130
145
  }
131
146
 
132
- function createTree(obj, schema, index, level = "root") {
133
-
134
- const moduleDocument = document.createElement('div');
135
- moduleDocument.classList.add('document');
136
- const container = document.createElement('ul');
137
- container.classList.add('tree');
138
-
139
- for (let key in obj) {
140
- const value = obj[key];
141
- const type = schema[key]?.instance;
142
- const li = document.createElement('li');
143
-
144
- if (typeof value === 'object' && value !== null) {
145
- li.innerHTML = `<span class="caret collapsible">${key} ${type || "Object"}</span>`;
146
- const child = createTree(value, schema[key]?.options?.type?.paths, index, level + "." + key);
147
- child.classList.add('nested');
148
- child.classList.remove('document');
149
- li.appendChild(child);
150
- } else {
151
- li.innerHTML = `<span class="key">${key}</span> : <span class="value ${type}" ${type === "ObjectId" ? "contenteditable='false'" : "contenteditable='true'"} data-level="${level}" data-index="${index}">${value}</span>`;
152
- }
153
-
154
- container.appendChild(li);
147
+ function renderData(key, value, index, level = "root.", parentDataType = "object") {
148
+ const type = typeof value;
149
+ 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>`;
150
+
151
+ if (Array.isArray(value)) {
152
+ return `
153
+ <details class="field">
154
+ <summary ><span class="data-key">${key}</span><span class="data-type"> (Array - ${value.length})</span></summary>
155
+ <div class="data-value">
156
+ ${value.map((v, i) => renderData(`[${i}]`, v, index, level + key, "array")).join('')}
157
+ </div>
158
+ </details>
159
+ `;
160
+ } else if (type === 'object') {
161
+ return `
162
+ <details class="field">
163
+ <summary><span class="data-key">${key}</span><span class="data-type"> (Object)</span></summary>
164
+ <div class="data-value">
165
+ ${Object.entries(value).map(([k, v]) => renderData(k, v, index, level + key, "object")).join('')}
166
+ </div>
167
+ </details>
168
+ `;
169
+ } else if (typeof value === 'string' && /^[a-f\d]{24}$/i.test(value)) {
170
+
171
+ return `<div class="field"><span class="data-key">${key}</span>: <span class="ObjectId">${value}</span></div>`;
172
+ } else {
173
+ 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>`;
155
174
  }
156
-
157
- moduleDocument.appendChild(container);
158
- return moduleDocument;
159
175
  }
160
176
 
161
177
  // Handle collapsibles
@@ -169,22 +185,30 @@ document.addEventListener('click', function (e) {
169
185
  }
170
186
  });
171
187
 
172
- function setNestedValue(obj, path, value) {
188
+ function setNestedValue(obj, path, value, parentDataType) {
173
189
  const keys = path.split(".");
174
190
  let current = obj;
175
191
 
176
192
  keys.forEach((key, index) => {
177
193
  if (index === keys.length - 1) {
178
- current[key] = value; // Set value at last key
194
+ const arrayIndexMatch = key.match(/\[(\d+)\]/);
195
+ if (arrayIndexMatch) {
196
+ const idx = parseInt(arrayIndexMatch[1], 10);
197
+ current[idx] = value;
198
+ } else {
199
+ current[key] = value;
200
+ }
201
+
179
202
  } else {
180
- if (!current[key] || typeof current[key] !== "object") {
181
- current[key] = {}; // Create nested object if it doesn't exist
203
+ if (parentDataType === "array" && !Array.isArray(current[key])) {
204
+ current[key] = [];
205
+ } else if (parentDataType === "object" && typeof current[key] !== "object") {
206
+ current[key] = {};
182
207
  }
208
+
183
209
  current = current[key]; // Go deeper
184
210
  }
185
211
  });
186
-
187
- return obj; // Optional: return the updated object
188
212
  }
189
213
 
190
214
  async function updateDocument(index) {
@@ -199,19 +223,22 @@ async function updateDocument(index) {
199
223
  const values = document.querySelectorAll(`[data-index="${index}"]`);
200
224
 
201
225
  values.forEach(el => {
202
- const key = el.previousElementSibling?.innerText?.trim();
226
+ const key = el.previousElementSibling?.textContent?.trim();
203
227
  const level = el.dataset.level;
228
+
204
229
  if (key) {
205
230
  if (level && level !== "root") {
206
231
  // Build the full path
207
232
  const fullPath = `${level.replace("root.", "")}.${key}`;
208
- setNestedValue(updatedDoc, fullPath, el.innerText);
233
+ setNestedValue(updatedDoc, fullPath, el.textContent, el.dataset.parentDataType);
209
234
  } else {
210
- updatedDoc[key] = el.innerText;
235
+ updatedDoc[key] = el.textContent;
211
236
  }
212
237
  }
213
238
  });
214
239
 
240
+ updatedDoc = { ...updatedDoc, ...updatedDoc[""] }
241
+
215
242
  const res = await fetch(`/update/${currentModelName}/${id}`, {
216
243
  method: "PUT",
217
244
  headers: { "Content-Type": "application/json" },
@@ -301,18 +328,18 @@ function dragEnd() {
301
328
  async function refreshSideBar() {
302
329
  try {
303
330
  const modelWrapper = document.querySelector('.model-wrapper');
304
-
331
+
305
332
  const res = await fetch("/models");
306
-
333
+
307
334
  if (!res.ok) {
308
335
  const error = await res.json();
309
336
  showModal('error', 'Error Occurred!', error.error || 'Something went wrong, please try again.');
310
337
  return;
311
338
  }
312
-
339
+
313
340
  modelWrapper.innerHTML = "";
314
341
  const data = await res.json();
315
-
342
+
316
343
  data.models.forEach(model => {
317
344
  const modelDiv = document.createElement('div');
318
345
  modelDiv.classList.add('model');
@@ -323,5 +350,4 @@ async function refreshSideBar() {
323
350
  } catch (error) {
324
351
  showModal('error', 'Error Occurred!', error.message || 'Something went wrong, please try again.');
325
352
  }
326
- }
327
-
353
+ }
@@ -217,89 +217,67 @@ body {
217
217
  display: none;
218
218
  }
219
219
 
220
+ summary::marker{
221
+ color: var(--muted-color);
222
+ font-size: 10px;
223
+ margin-right: 10px;
224
+ }
225
+
220
226
  .document {
221
227
  padding: 10px;
222
228
  border: 1px solid var(--muted-color);
223
- /* background: var(--bg-color); */
224
229
  background: #1e2124;
225
- max-height: 80vh;
226
230
  border-radius: 10px;
227
231
  margin-top: 10px;
228
232
  }
229
233
 
230
- .tree {
231
- padding-left: 20px;
232
- }
233
-
234
- .tree li {
235
- list-style-type: none;
236
- padding: 5px 5px;
237
- font-size: 14px;
238
- border-radius: 5px;
239
- transition: background 0.1s ease-in-out;
234
+ .field {
235
+ padding: 5px 20px;
240
236
 
241
- &:hover {
242
- background: #2E3A59;
243
- }
244
237
  }
245
238
 
246
- .tree li .String {
239
+ .field .string {
247
240
  color: var(--green-color);
248
241
  }
249
242
 
250
- .tree li .String::after {
243
+ .field .string::after {
251
244
  content: '"';
252
245
  color: var(--green-color);
253
246
  }
254
247
 
255
- .tree li .String::before {
248
+ .field .string::before {
256
249
  content: '"';
257
250
  color: var(--green-color);
258
251
  }
259
252
 
260
- .tree li .Number {
253
+ .field .number {
261
254
  color: var(--blue-color);
262
255
  }
263
256
 
264
- .tree li .ObjectId {
257
+ .field .ObjectId {
265
258
  color: var(--red-color);
266
259
  }
267
260
 
268
- .tree li .ObjectId::after {
261
+ .field .ObjectId::after {
269
262
  content: ' )';
270
263
  color: var(--red-color);
271
264
  }
272
265
 
273
- .tree li .ObjectId::before {
266
+ .field .ObjectId::before {
274
267
  content: 'ObjectId( ';
275
268
  color: var(--red-color);
276
269
  }
277
270
 
278
- .collapsible {
279
- cursor: pointer;
280
- user-select: none;
281
- }
282
-
283
- .tree li .nested {
284
- display: none;
285
- }
286
-
287
- .tree li .active {
288
- display: block;
289
- }
290
-
291
- .caret::before {
292
- content: "▶";
293
- color: #555;
294
- display: inline-block;
295
- font-size: 10px;
296
- margin-right: 6px;
297
- transform: rotate(0deg);
298
- transition: transform 0.3s ease;
299
- }
300
-
301
- .caret-down::before {
302
- transform: rotate(90deg);
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;
303
281
  }
304
282
 
305
283
  .document-actions {
@@ -338,4 +316,27 @@ body {
338
316
  background: var(--red-color);
339
317
  color: var(--bg-color);
340
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
+ }
341
342
  }
package/server.js CHANGED
@@ -198,6 +198,7 @@ app.put("/update/:modelName/:id", async (req, res) => {
198
198
  const { modelName, id } = req.params;
199
199
  const model = mongoose.model(modelName);
200
200
  const updatedDoc = req.body;
201
+
201
202
  delete updatedDoc._id;
202
203
  const result = await model.findByIdAndUpdate(id, updatedDoc, { new: true });
203
204