admindb 1.0.1 → 1.1.0
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 +20 -20
- package/README.md +354 -325
- package/dist/db/database.d.ts +61 -39
- package/dist/db/database.js +251 -51
- package/dist/db/database.js.map +1 -1
- package/dist/db/manager.d.ts +2 -2
- package/dist/db/manager.js +3 -2
- package/dist/db/manager.js.map +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/css/input.css +232 -179
- package/dist/public/js/api.js +47 -47
- package/dist/public/js/app.js +120 -120
- package/dist/public/js/browse.js +877 -123
- package/dist/public/js/databases.js +45 -45
- package/dist/public/js/designer.js +205 -205
- package/dist/public/js/forms.js +244 -244
- package/dist/public/js/query.js +232 -232
- package/dist/public/js/schema.js +314 -314
- package/dist/routes/api.js +261 -97
- package/dist/routes/api.js.map +1 -1
- package/dist/routes/pages.js +50 -30
- package/dist/routes/pages.js.map +1 -1
- package/dist/sql/generator.d.ts +0 -22
- package/dist/sql/generator.js +0 -48
- package/dist/sql/generator.js.map +1 -1
- package/dist/test/database.test.js +241 -63
- package/dist/test/database.test.js.map +1 -1
- package/dist/test/generator.test.js +0 -32
- package/dist/test/generator.test.js.map +1 -1
- package/dist/util.d.ts +13 -1
- package/dist/util.js +24 -2
- package/dist/util.js.map +1 -1
- package/dist/views/layouts/main.hbs +49 -49
- package/dist/views/pages/databases.hbs +102 -102
- package/dist/views/pages/designer.hbs +81 -81
- package/dist/views/pages/error.hbs +12 -12
- package/dist/views/pages/form.hbs +62 -62
- package/dist/views/pages/home.hbs +123 -123
- package/dist/views/pages/query.hbs +94 -94
- package/dist/views/pages/schema.hbs +290 -290
- package/dist/views/pages/table.hbs +256 -203
- package/dist/views/partials/navbar.hbs +50 -50
- package/dist/views/partials/sidebar.hbs +116 -128
- package/package.json +66 -62
- package/dist/public/js/triggers.js +0 -155
- package/dist/public/js/views.js +0 -176
- package/dist/views/pages/triggers.hbs +0 -162
- package/dist/views/pages/views.hbs +0 -134
|
@@ -1,179 +1,232 @@
|
|
|
1
|
-
@tailwind base;
|
|
2
|
-
@tailwind components;
|
|
3
|
-
@tailwind utilities;
|
|
4
|
-
|
|
5
|
-
@layer base {
|
|
6
|
-
html {
|
|
7
|
-
-webkit-font-smoothing: antialiased;
|
|
8
|
-
text-rendering: optimizeLegibility;
|
|
9
|
-
}
|
|
10
|
-
body {
|
|
11
|
-
@apply font-sans;
|
|
12
|
-
}
|
|
13
|
-
::selection {
|
|
14
|
-
@apply bg-primary/25;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/* Thin, theme-aware scrollbars */
|
|
18
|
-
::-webkit-scrollbar {
|
|
19
|
-
width: 10px;
|
|
20
|
-
height: 10px;
|
|
21
|
-
}
|
|
22
|
-
::-webkit-scrollbar-track {
|
|
23
|
-
background: transparent;
|
|
24
|
-
}
|
|
25
|
-
::-webkit-scrollbar-thumb {
|
|
26
|
-
background: rgb(0 0 0 / 0.16);
|
|
27
|
-
border-radius: 999px;
|
|
28
|
-
border: 2px solid transparent;
|
|
29
|
-
background-clip: content-box;
|
|
30
|
-
}
|
|
31
|
-
::-webkit-scrollbar-thumb:hover {
|
|
32
|
-
background: rgb(0 0 0 / 0.28);
|
|
33
|
-
border-radius: 999px;
|
|
34
|
-
border: 2px solid transparent;
|
|
35
|
-
background-clip: content-box;
|
|
36
|
-
}
|
|
37
|
-
[data-theme='admindbDark'] ::-webkit-scrollbar-thumb {
|
|
38
|
-
background: rgb(255 255 255 / 0.18);
|
|
39
|
-
border-radius: 999px;
|
|
40
|
-
border: 2px solid transparent;
|
|
41
|
-
background-clip: content-box;
|
|
42
|
-
}
|
|
43
|
-
[data-theme='admindbDark'] ::-webkit-scrollbar-thumb:hover {
|
|
44
|
-
background: rgb(255 255 255 / 0.3);
|
|
45
|
-
border-radius: 999px;
|
|
46
|
-
border: 2px solid transparent;
|
|
47
|
-
background-clip: content-box;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@layer components {
|
|
52
|
-
/* Cards / panels */
|
|
53
|
-
.app-card {
|
|
54
|
-
@apply rounded-2xl border border-base-300/70 bg-base-100 shadow-soft;
|
|
55
|
-
}
|
|
56
|
-
.code-panel {
|
|
57
|
-
@apply overflow-x-auto rounded-xl border border-base-300 bg-neutral p-4 font-mono text-[13px] leading-relaxed text-neutral-content;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/* Sidebar */
|
|
61
|
-
.side-link {
|
|
62
|
-
@apply flex items-center gap-3 rounded-lg px-3 py-2 text-[13.5px] font-medium text-neutral-content/65 transition-colors duration-150;
|
|
63
|
-
}
|
|
64
|
-
.side-link:hover {
|
|
65
|
-
@apply bg-white/[0.06] text-neutral-content;
|
|
66
|
-
}
|
|
67
|
-
.side-link.active {
|
|
68
|
-
@apply bg-primary/20 text-white;
|
|
69
|
-
}
|
|
70
|
-
.side-link svg {
|
|
71
|
-
@apply h-[18px] w-[18px] shrink-0;
|
|
72
|
-
}
|
|
73
|
-
.table-icon {
|
|
74
|
-
@apply grid h-7 w-7 shrink-0 place-items-center rounded-md bg-white/[0.06] text-neutral-content/60;
|
|
75
|
-
}
|
|
76
|
-
.side-link.active .table-icon {
|
|
77
|
-
@apply bg-primary/25 text-white;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/* Row actions revealed on hover */
|
|
81
|
-
.row-actions {
|
|
82
|
-
@apply flex items-center justify-end gap-1 opacity-100 transition-opacity duration-150 lg:opacity-100;
|
|
83
|
-
}
|
|
84
|
-
tr:hover .row-actions,
|
|
85
|
-
tr:focus-within .row-actions {
|
|
86
|
-
@apply opacity-100;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/*
|
|
90
|
-
.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.
|
|
129
|
-
@apply
|
|
130
|
-
}
|
|
131
|
-
.
|
|
132
|
-
@apply text-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
.
|
|
153
|
-
@apply
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
.
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
html {
|
|
7
|
+
-webkit-font-smoothing: antialiased;
|
|
8
|
+
text-rendering: optimizeLegibility;
|
|
9
|
+
}
|
|
10
|
+
body {
|
|
11
|
+
@apply font-sans;
|
|
12
|
+
}
|
|
13
|
+
::selection {
|
|
14
|
+
@apply bg-primary/25;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Thin, theme-aware scrollbars */
|
|
18
|
+
::-webkit-scrollbar {
|
|
19
|
+
width: 10px;
|
|
20
|
+
height: 10px;
|
|
21
|
+
}
|
|
22
|
+
::-webkit-scrollbar-track {
|
|
23
|
+
background: transparent;
|
|
24
|
+
}
|
|
25
|
+
::-webkit-scrollbar-thumb {
|
|
26
|
+
background: rgb(0 0 0 / 0.16);
|
|
27
|
+
border-radius: 999px;
|
|
28
|
+
border: 2px solid transparent;
|
|
29
|
+
background-clip: content-box;
|
|
30
|
+
}
|
|
31
|
+
::-webkit-scrollbar-thumb:hover {
|
|
32
|
+
background: rgb(0 0 0 / 0.28);
|
|
33
|
+
border-radius: 999px;
|
|
34
|
+
border: 2px solid transparent;
|
|
35
|
+
background-clip: content-box;
|
|
36
|
+
}
|
|
37
|
+
[data-theme='admindbDark'] ::-webkit-scrollbar-thumb {
|
|
38
|
+
background: rgb(255 255 255 / 0.18);
|
|
39
|
+
border-radius: 999px;
|
|
40
|
+
border: 2px solid transparent;
|
|
41
|
+
background-clip: content-box;
|
|
42
|
+
}
|
|
43
|
+
[data-theme='admindbDark'] ::-webkit-scrollbar-thumb:hover {
|
|
44
|
+
background: rgb(255 255 255 / 0.3);
|
|
45
|
+
border-radius: 999px;
|
|
46
|
+
border: 2px solid transparent;
|
|
47
|
+
background-clip: content-box;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@layer components {
|
|
52
|
+
/* Cards / panels */
|
|
53
|
+
.app-card {
|
|
54
|
+
@apply rounded-2xl border border-base-300/70 bg-base-100 shadow-soft;
|
|
55
|
+
}
|
|
56
|
+
.code-panel {
|
|
57
|
+
@apply overflow-x-auto rounded-xl border border-base-300 bg-neutral p-4 font-mono text-[13px] leading-relaxed text-neutral-content;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Sidebar */
|
|
61
|
+
.side-link {
|
|
62
|
+
@apply flex items-center gap-3 rounded-lg px-3 py-2 text-[13.5px] font-medium text-neutral-content/65 transition-colors duration-150;
|
|
63
|
+
}
|
|
64
|
+
.side-link:hover {
|
|
65
|
+
@apply bg-white/[0.06] text-neutral-content;
|
|
66
|
+
}
|
|
67
|
+
.side-link.active {
|
|
68
|
+
@apply bg-primary/20 text-white;
|
|
69
|
+
}
|
|
70
|
+
.side-link svg {
|
|
71
|
+
@apply h-[18px] w-[18px] shrink-0;
|
|
72
|
+
}
|
|
73
|
+
.table-icon {
|
|
74
|
+
@apply grid h-7 w-7 shrink-0 place-items-center rounded-md bg-white/[0.06] text-neutral-content/60;
|
|
75
|
+
}
|
|
76
|
+
.side-link.active .table-icon {
|
|
77
|
+
@apply bg-primary/25 text-white;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Row actions revealed on hover */
|
|
81
|
+
.row-actions {
|
|
82
|
+
@apply flex items-center justify-end gap-1 opacity-100 transition-opacity duration-150 lg:opacity-100;
|
|
83
|
+
}
|
|
84
|
+
tr:hover .row-actions,
|
|
85
|
+
tr:focus-within .row-actions {
|
|
86
|
+
@apply opacity-100;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Inline (spreadsheet-style) cell editing */
|
|
90
|
+
.js-inline-cell {
|
|
91
|
+
cursor: text;
|
|
92
|
+
position: relative;
|
|
93
|
+
}
|
|
94
|
+
.js-inline-cell:hover {
|
|
95
|
+
box-shadow: inset 0 0 0 1px oklch(var(--p) / 0.45);
|
|
96
|
+
}
|
|
97
|
+
.js-inline-cell.js-editing {
|
|
98
|
+
box-shadow: inset 0 0 0 2px oklch(var(--p) / 0.85);
|
|
99
|
+
cursor: default;
|
|
100
|
+
background: color-mix(in oklab, oklch(var(--b1)) 88%, oklch(var(--p) / 0.16));
|
|
101
|
+
}
|
|
102
|
+
.js-inline-cell.js-editing .js-cell-value {
|
|
103
|
+
display: none;
|
|
104
|
+
}
|
|
105
|
+
.js-inline-cell.opacity-60 {
|
|
106
|
+
opacity: 0.55;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Pending (staged) edit — highlighted until the user presses Apply.
|
|
110
|
+
Uses the theme's base color (--b1) + a primary tint so it renders
|
|
111
|
+
correctly in both the light and dark (black) themes. */
|
|
112
|
+
.js-inline-cell.cell-dirty {
|
|
113
|
+
background: color-mix(in oklab, oklch(var(--b1)) 90%, oklch(var(--p) / 0.14));
|
|
114
|
+
box-shadow: inset 0 0 0 1px oklch(var(--p) / 0.75);
|
|
115
|
+
}
|
|
116
|
+
.js-inline-cell.cell-dirty::after {
|
|
117
|
+
content: '';
|
|
118
|
+
position: absolute;
|
|
119
|
+
top: 4px;
|
|
120
|
+
right: 4px;
|
|
121
|
+
width: 6px;
|
|
122
|
+
height: 6px;
|
|
123
|
+
border-radius: 999px;
|
|
124
|
+
background: oklch(var(--p));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Sortable column headers */
|
|
128
|
+
.sort-link {
|
|
129
|
+
@apply inline-flex items-center gap-1.5 font-semibold text-base-content/70 transition-colors hover:text-primary;
|
|
130
|
+
}
|
|
131
|
+
.sort-link.active {
|
|
132
|
+
@apply text-primary;
|
|
133
|
+
}
|
|
134
|
+
.sort-link .sort-arrow {
|
|
135
|
+
@apply text-[10px] opacity-100 transition-opacity;
|
|
136
|
+
}
|
|
137
|
+
.sort-link:hover .sort-arrow,
|
|
138
|
+
.sort-link.active .sort-arrow {
|
|
139
|
+
@apply opacity-100;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Form controls */
|
|
143
|
+
.field-label {
|
|
144
|
+
@apply mb-1.5 flex flex-wrap items-center gap-1.5 text-[13px] font-medium text-base-content/70;
|
|
145
|
+
}
|
|
146
|
+
.field-input {
|
|
147
|
+
@apply w-full rounded-xl border border-base-300 bg-base-100 px-3.5 py-2.5 text-sm text-base-content shadow-soft outline-none transition duration-150 placeholder:text-base-content/30 focus:border-primary/70 focus:ring-4 focus:ring-primary/10 disabled:cursor-not-allowed disabled:bg-base-200 disabled:text-base-content/40;
|
|
148
|
+
}
|
|
149
|
+
.field-select {
|
|
150
|
+
@apply w-full rounded-xl border border-base-300 bg-base-100 px-3 py-2.5 text-sm text-base-content shadow-soft outline-none transition duration-150 focus:border-primary/70 focus:ring-4 focus:ring-primary/10;
|
|
151
|
+
}
|
|
152
|
+
.field-check {
|
|
153
|
+
@apply checkbox checkbox-primary checkbox-sm;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Badges / chips */
|
|
157
|
+
.chip {
|
|
158
|
+
@apply inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide;
|
|
159
|
+
}
|
|
160
|
+
.chip-pk {
|
|
161
|
+
@apply bg-primary/10 text-primary;
|
|
162
|
+
}
|
|
163
|
+
.chip-null {
|
|
164
|
+
@apply bg-warning/10 text-warning;
|
|
165
|
+
}
|
|
166
|
+
.chip-type {
|
|
167
|
+
@apply bg-base-200 text-base-content/45;
|
|
168
|
+
}
|
|
169
|
+
.chip-required {
|
|
170
|
+
@apply text-error;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Stat cards */
|
|
174
|
+
.stat-card {
|
|
175
|
+
@apply flex items-center gap-4 rounded-2xl border border-base-300/70 bg-base-100 p-5 shadow-soft;
|
|
176
|
+
}
|
|
177
|
+
.stat-icon {
|
|
178
|
+
@apply grid h-11 w-11 shrink-0 place-items-center rounded-xl;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* SQL editor (query page) */
|
|
182
|
+
.sql-editor-wrap {
|
|
183
|
+
@apply flex overflow-hidden rounded-xl border border-base-300 bg-base-100 shadow-soft transition duration-150 focus-within:border-primary/60 focus-within:ring-4 focus-within:ring-primary/10;
|
|
184
|
+
}
|
|
185
|
+
.sql-gutter {
|
|
186
|
+
@apply select-none overflow-hidden border-r border-base-200 bg-base-200/60 px-3 py-4 text-right font-mono text-[13px] leading-[1.55] text-base-content/35;
|
|
187
|
+
min-width: 3.2rem;
|
|
188
|
+
max-height: 460px;
|
|
189
|
+
}
|
|
190
|
+
.sql-input {
|
|
191
|
+
@apply w-full resize-none overflow-auto bg-base-100 px-4 py-4 font-mono text-[13px] leading-[1.55] text-base-content outline-none placeholder:text-base-content/30;
|
|
192
|
+
max-height: 460px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Empty state */
|
|
196
|
+
.empty-state {
|
|
197
|
+
@apply flex flex-col items-center justify-center gap-3 rounded-2xl border border-dashed border-base-300 px-6 py-14 text-center;
|
|
198
|
+
}
|
|
199
|
+
.empty-state .empty-icon {
|
|
200
|
+
@apply grid h-14 w-14 place-items-center rounded-2xl bg-base-200 text-base-content/40;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Toast */
|
|
204
|
+
.toast-item {
|
|
205
|
+
animation: toast-in 0.22s cubic-bezier(0.16, 1, 0.3, 1);
|
|
206
|
+
}
|
|
207
|
+
@keyframes toast-in {
|
|
208
|
+
from {
|
|
209
|
+
opacity: 0;
|
|
210
|
+
transform: translateY(-8px) scale(0.98);
|
|
211
|
+
}
|
|
212
|
+
to {
|
|
213
|
+
opacity: 1;
|
|
214
|
+
transform: translateY(0) scale(1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Related-rows nested panel */
|
|
219
|
+
.js-ref-anim {
|
|
220
|
+
animation: ref-slide 0.16s ease-out;
|
|
221
|
+
}
|
|
222
|
+
@keyframes ref-slide {
|
|
223
|
+
from {
|
|
224
|
+
opacity: 0;
|
|
225
|
+
transform: translateY(-4px);
|
|
226
|
+
}
|
|
227
|
+
to {
|
|
228
|
+
opacity: 1;
|
|
229
|
+
transform: translateY(0);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
package/dist/public/js/api.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
/* Tiny JSON API client. Every request resolves to the `data` payload of the
|
|
2
|
-
* consistent { success, data?, error? } envelope, or throws with the message. */
|
|
3
|
-
(function () {
|
|
4
|
-
'use strict';
|
|
5
|
-
const base = (window.APP && window.APP.basePath) || '';
|
|
6
|
-
|
|
7
|
-
function url(path) {
|
|
8
|
-
return base + path;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
window.Api = {
|
|
12
|
-
basePath: base,
|
|
13
|
-
url: url,
|
|
14
|
-
|
|
15
|
-
async request(method, path, body) {
|
|
16
|
-
const opts = { method, headers: {} };
|
|
17
|
-
if (body !== undefined) {
|
|
18
|
-
opts.headers['Content-Type'] = 'application/json';
|
|
19
|
-
opts.body = JSON.stringify(body);
|
|
20
|
-
}
|
|
21
|
-
let res;
|
|
22
|
-
try {
|
|
23
|
-
res = await fetch(url(path), opts);
|
|
24
|
-
} catch (e) {
|
|
25
|
-
throw new Error('Network error: ' + e.message);
|
|
26
|
-
}
|
|
27
|
-
let data = null;
|
|
28
|
-
try {
|
|
29
|
-
data = await res.json();
|
|
30
|
-
} catch (e) {
|
|
31
|
-
data = null;
|
|
32
|
-
}
|
|
33
|
-
if (!res.ok) {
|
|
34
|
-
throw new Error((data && data.error) || 'Request failed (' + res.status + ')');
|
|
35
|
-
}
|
|
36
|
-
if (!data || data.success === false) {
|
|
37
|
-
throw new Error((data && data.error) || 'Request failed');
|
|
38
|
-
}
|
|
39
|
-
return data.data;
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
get(path) { return this.request('GET', path); },
|
|
43
|
-
post(path, body) { return this.request('POST', path, body); },
|
|
44
|
-
put(path, body) { return this.request('PUT', path, body); },
|
|
45
|
-
del(path) { return this.request('DELETE', path); },
|
|
46
|
-
};
|
|
47
|
-
})();
|
|
1
|
+
/* Tiny JSON API client. Every request resolves to the `data` payload of the
|
|
2
|
+
* consistent { success, data?, error? } envelope, or throws with the message. */
|
|
3
|
+
(function () {
|
|
4
|
+
'use strict';
|
|
5
|
+
const base = (window.APP && window.APP.basePath) || '';
|
|
6
|
+
|
|
7
|
+
function url(path) {
|
|
8
|
+
return base + path;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
window.Api = {
|
|
12
|
+
basePath: base,
|
|
13
|
+
url: url,
|
|
14
|
+
|
|
15
|
+
async request(method, path, body) {
|
|
16
|
+
const opts = { method, headers: {} };
|
|
17
|
+
if (body !== undefined) {
|
|
18
|
+
opts.headers['Content-Type'] = 'application/json';
|
|
19
|
+
opts.body = JSON.stringify(body);
|
|
20
|
+
}
|
|
21
|
+
let res;
|
|
22
|
+
try {
|
|
23
|
+
res = await fetch(url(path), opts);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
throw new Error('Network error: ' + e.message);
|
|
26
|
+
}
|
|
27
|
+
let data = null;
|
|
28
|
+
try {
|
|
29
|
+
data = await res.json();
|
|
30
|
+
} catch (e) {
|
|
31
|
+
data = null;
|
|
32
|
+
}
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error((data && data.error) || 'Request failed (' + res.status + ')');
|
|
35
|
+
}
|
|
36
|
+
if (!data || data.success === false) {
|
|
37
|
+
throw new Error((data && data.error) || 'Request failed');
|
|
38
|
+
}
|
|
39
|
+
return data.data;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
get(path) { return this.request('GET', path); },
|
|
43
|
+
post(path, body) { return this.request('POST', path, body); },
|
|
44
|
+
put(path, body) { return this.request('PUT', path, body); },
|
|
45
|
+
del(path) { return this.request('DELETE', path); },
|
|
46
|
+
};
|
|
47
|
+
})();
|