create-fuzionx 0.1.48 β†’ 0.1.49

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fuzionx",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "description": "Create a new FuzionX application β€” npx create-fuzionx my-app",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,13 @@
21
21
  "processing": "Processing",
22
22
  "store_success": "Post saved successfully.",
23
23
  "title": "Board",
24
- "update_success": "Post updated successfully."
24
+ "update_success": "Post updated successfully.",
25
+ "upload_speed": "Speed",
26
+ "upload_complete": "Upload complete!",
27
+ "upload_error": "Upload error",
28
+ "upload_network_error": "Network error",
29
+ "uploading": "Uploading...",
30
+ "thumbnail_extracting": "πŸ”„ Extracting thumbnails..."
25
31
  },
26
32
  "btn": {
27
33
  "back": "← Back",
@@ -21,7 +21,13 @@
21
21
  "processing": "μ²˜λ¦¬μ€‘",
22
22
  "store_success": "κ²Œμ‹œκΈ€μ΄ μ„±κ³΅μ μœΌλ‘œ μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€.",
23
23
  "title": "κ²Œμ‹œνŒ",
24
- "update_success": "κ²Œμ‹œκΈ€μ΄ μ„±κ³΅μ μœΌλ‘œ μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€."
24
+ "update_success": "κ²Œμ‹œκΈ€μ΄ μ„±κ³΅μ μœΌλ‘œ μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€.",
25
+ "upload_speed": "속도",
26
+ "upload_complete": "μ—…λ‘œλ“œ μ™„λ£Œ!",
27
+ "upload_error": "였λ₯˜ λ°œμƒ",
28
+ "upload_network_error": "λ„€νŠΈμ›Œν¬ 였λ₯˜",
29
+ "uploading": "μ—…λ‘œλ“œ 쀑...",
30
+ "thumbnail_extracting": "πŸ”„ 썸넀일 μΆ”μΆœμ€‘..."
25
31
  },
26
32
  "btn": {
27
33
  "back": "← λͺ©λ‘",
@@ -9,8 +9,8 @@
9
9
  "test": "vitest run"
10
10
  },
11
11
  "dependencies": {
12
- "@fuzionx/framework": "^0.1.48",
13
- "@fuzionx/client": "^0.1.48",
12
+ "@fuzionx/framework": "^0.1.49",
13
+ "@fuzionx/client": "^0.1.49",
14
14
  "joi": "^18.1.1"
15
15
  },
16
16
  "devDependencies": {
@@ -4,7 +4,7 @@
4
4
  "description": "Vue.js 3 SPA + Tera SSR ν•˜μ΄λΈŒλ¦¬λ“œ. WASM μ•”ν˜Έν™” 톡신.",
5
5
  "features": ["auth", "board", "i18n", "asp", "wasm"],
6
6
  "dependencies": {
7
- "@fuzionx/client": "^0.1.48"
7
+ "@fuzionx/client": "^0.1.49"
8
8
  },
9
9
  "devDependencies": {},
10
10
  "spaDevDependencies": {
@@ -54,7 +54,8 @@
54
54
  </div>
55
55
  <div class="progress-info" style="display: flex; justify-content: space-between; margin-top: 6px; font-size: 0.85rem; color: #aaa;">
56
56
  <span>{{ progressText }}</span>
57
- <span>{{ speedText }}</span>
57
+ <span>{{ sizeText }}</span>
58
+ <span>{{ speedLabel }}</span>
58
59
  </div>
59
60
  </div>
60
61
  </div>
@@ -62,6 +63,9 @@
62
63
 
63
64
  <script setup>
64
65
  import { ref, computed } from 'vue';
66
+ import { useLocale } from '../composables/useLocale.js';
67
+
68
+ const { t } = useLocale();
65
69
 
66
70
  /**
67
71
  * @prop {string} label - λ ˆμ΄λΈ” ν…μŠ€νŠΈ
@@ -83,14 +87,16 @@ const selectedFiles = ref([]);
83
87
  const uploading = ref(false);
84
88
  const progress = ref(0);
85
89
  const progressText = ref('0%');
86
- const speedText = ref('');
90
+ const sizeText = ref('');
91
+ const speedLabel = ref('');
87
92
  const progressColor = ref('linear-gradient(90deg, #667eea, #764ba2)');
88
93
 
89
94
  /** 파일 크기 포맷 */
90
95
  function formatSize(bytes) {
91
96
  if (bytes < 1024) return bytes + ' B';
92
97
  if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
93
- return (bytes / 1048576).toFixed(1) + ' MB';
98
+ if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
99
+ return (bytes / 1073741824).toFixed(2) + ' GB';
94
100
  }
95
101
 
96
102
  /** 파일 선택 이벀트 */
@@ -122,23 +128,32 @@ function setFiles(fileList) {
122
128
  }
123
129
 
124
130
  /** μ§„ν–‰λ₯  μ—…λ°μ΄νŠΈ (μ™ΈλΆ€μ—μ„œ 호좜) */
125
- function setProgress(pct, speed) {
131
+ function setProgress(pct, speed, loaded, total) {
126
132
  uploading.value = true;
127
133
  progress.value = pct;
128
134
  progressText.value = pct + '%';
129
- speedText.value = speed || '';
135
+
136
+ // 파일 크기 ν‘œμ‹œ
137
+ if (loaded !== undefined && total !== undefined) {
138
+ sizeText.value = formatSize(loaded) + ' / ' + formatSize(total);
139
+ }
140
+
141
+ // 속도 λ ˆμ΄λΈ”
142
+ if (speed) {
143
+ speedLabel.value = t('board.upload_speed', '속도') + ': ' + speed;
144
+ }
130
145
  }
131
146
 
132
147
  /** μ—…λ‘œλ“œ μ™„λ£Œ */
133
- function setComplete(text = 'μ—…λ‘œλ“œ μ™„λ£Œ!') {
148
+ function setComplete(text) {
134
149
  progress.value = 100;
135
- progressText.value = text;
136
- speedText.value = '';
150
+ progressText.value = text || t('board.upload_complete', 'μ—…λ‘œλ“œ μ™„λ£Œ!');
151
+ speedLabel.value = '';
137
152
  }
138
153
 
139
154
  /** μ—…λ‘œλ“œ 였λ₯˜ */
140
- function setError(text = '였λ₯˜ λ°œμƒ') {
141
- progressText.value = text;
155
+ function setError(text) {
156
+ progressText.value = text || t('board.upload_error', '였λ₯˜ λ°œμƒ');
142
157
  progressColor.value = '#e74c3c';
143
158
  }
144
159
 
@@ -153,7 +168,8 @@ function reset() {
153
168
  uploading.value = false;
154
169
  progress.value = 0;
155
170
  progressText.value = '0%';
156
- speedText.value = '';
171
+ sizeText.value = '';
172
+ speedLabel.value = '';
157
173
  progressColor.value = 'linear-gradient(90deg, #667eea, #764ba2)';
158
174
  }
159
175
 
@@ -80,7 +80,7 @@ export function useApi() {
80
80
  * XHR μ—…λ‘œλ“œ β€” μ§„ν–‰λ₯  콜백 지원.
81
81
  * @param {string} url - μ—…λ‘œλ“œ URL
82
82
  * @param {FormData} formData - 폼 데이터
83
- * @param {Function} onProgress - μ§„ν–‰λ₯  콜백 (percent, speed)
83
+ * @param {Function} onProgress - μ§„ν–‰λ₯  콜백 (percent, speed, loaded, total)
84
84
  * @param {string} [method='POST'] - HTTP λ©”μ„œλ“œ
85
85
  * @returns {Promise<{ok: boolean, responseURL: string}>}
86
86
  */
@@ -100,7 +100,7 @@ export function useApi() {
100
100
  ? (bps / 1048576).toFixed(1) + ' MB/s'
101
101
  : (bps / 1024).toFixed(0) + ' KB/s';
102
102
  }
103
- onProgress(pct, speed);
103
+ onProgress(pct, speed, ev.loaded, ev.total);
104
104
  };
105
105
 
106
106
  xhr.onload = () => {
@@ -162,10 +162,10 @@ async function handleSubmit() {
162
162
  try {
163
163
  if (hasFiles) {
164
164
  // XHR μ—…λ‘œλ“œ + μ§„ν–‰λ₯ 
165
- const result = await api.uploadWithProgress(uploadUrl, formData, (pct, speed) => {
166
- fileUploader.value?.setProgress(pct, speed);
165
+ const result = await api.uploadWithProgress(uploadUrl, formData, (pct, speed, loaded, total) => {
166
+ fileUploader.value?.setProgress(pct, speed, loaded, total);
167
167
  }, method);
168
- fileUploader.value?.setComplete('μ—…λ‘œλ“œ μ™„λ£Œ!');
168
+ fileUploader.value?.setComplete();
169
169
  toast.success(isEdit.value ? t('board.updated', '글이 μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€.') : t('board.created', '글이 μž‘μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'));
170
170
  setTimeout(() => {
171
171
  router.push(isEdit.value ? `/board/${route.params.id}` : '/board');
@@ -1,4 +1,4 @@
1
- import { auth } from '@fuzionx/framework';
1
+ import { auth, loadUser } from '@fuzionx/framework';
2
2
  import HomeController from '../controllers/HomeController.js';
3
3
  import FeaturesController from '../controllers/FeaturesController.js';
4
4
  import ChatController from '../controllers/ChatController.js';
@@ -7,9 +7,11 @@ import UserController from '../controllers/UserController.js';
7
7
  import PostController from '../controllers/PostController.js';
8
8
 
9
9
  export default (r) => {
10
- // ── 곡개 νŽ˜μ΄μ§€ (인증 λΆˆν•„μš”) ──
11
- r.get('/', HomeController.index);
12
- r.get('/features', FeaturesController.index);
10
+ // ── 곡개 νŽ˜μ΄μ§€ (인증 λΆˆν•„μš”, μ„Έμ…˜ μœ μ € λ‘œλ“œ) ──
11
+ r.group('', { middleware: [loadUser()] }, (r) => {
12
+ r.get('/', HomeController.index);
13
+ r.get('/features', FeaturesController.index);
14
+ });
13
15
 
14
16
  // ── 인증 ──
15
17
  r.get('/login', AuthController.loginPage);
@@ -81,6 +81,7 @@
81
81
  </div>
82
82
  <div class="progress-info" style="display:flex;justify-content:space-between;margin-top:6px;font-size:0.85rem;color:#aaa">
83
83
  <span id="progressText">0%</span>
84
+ <span id="progressSize"></span>
84
85
  <span id="progressSpeed"></span>
85
86
  </div>
86
87
  </div>
@@ -128,7 +129,8 @@ function editorInsert(text) {
128
129
  function formatSize(bytes) {
129
130
  if (bytes < 1024) return bytes + ' B';
130
131
  if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
131
- return (bytes / 1048576).toFixed(1) + ' MB';
132
+ if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
133
+ return (bytes / 1073741824).toFixed(2) + ' GB';
132
134
  }
133
135
 
134
136
  function renderFiles() {
@@ -168,7 +170,25 @@ function editorInsert(text) {
168
170
  });
169
171
  })();
170
172
 
171
- /** XHR μ—…λ‘œλ“œ β€” μ§„ν–‰λ₯  + μžλ™ λ¦¬λ‹€μ΄λ ‰νŠΈ */
173
+ /** i18n ν…μŠ€νŠΈ */
174
+ var _uploadLabels = {
175
+ speed: '{{ t(key="board.upload_speed", default="속도") }}',
176
+ complete: '{{ t(key="board.upload_complete", default="μ—…λ‘œλ“œ μ™„λ£Œ!") }}',
177
+ error: '{{ t(key="board.upload_error", default="였λ₯˜ λ°œμƒ") }}',
178
+ networkError: '{{ t(key="board.upload_network_error", default="λ„€νŠΈμ›Œν¬ 였λ₯˜") }}',
179
+ uploading: '{{ t(key="board.uploading", default="μ—…λ‘œλ“œ 쀑...") }}',
180
+ thumbnailExtracting: '{{ t(key="board.thumbnail_extracting", default="πŸ”„ 썸넀일 μΆ”μΆœμ€‘...") }}'
181
+ };
182
+
183
+ /** 파일 크기 포맷 (μ§„ν–‰λ₯ μš©) */
184
+ function _fmtSize(bytes) {
185
+ if (bytes < 1024) return bytes + ' B';
186
+ if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
187
+ if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
188
+ return (bytes / 1073741824).toFixed(2) + ' GB';
189
+ }
190
+
191
+ /** XHR μ—…λ‘œλ“œ β€” μ§„ν–‰λ₯  + 파일 크기 + 속도 + μžλ™ λ¦¬λ‹€μ΄λ ‰νŠΈ */
172
192
  function handleUpload(e) {
173
193
  var fileInput = document.getElementById('fileInput');
174
194
  if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
@@ -181,10 +201,11 @@ function handleUpload(e) {
181
201
  var progress = document.getElementById('uploadProgress');
182
202
  var fill = document.getElementById('progressFill');
183
203
  var pText = document.getElementById('progressText');
204
+ var pSize = document.getElementById('progressSize');
184
205
  var pSpeed = document.getElementById('progressSpeed');
185
206
 
186
207
  btn.disabled = true;
187
- btn.textContent = 'μ—…λ‘œλ“œ 쀑...';
208
+ btn.textContent = _uploadLabels.uploading;
188
209
  progress.style.display = 'block';
189
210
 
190
211
  var formData = new FormData(form);
@@ -197,30 +218,35 @@ function handleUpload(e) {
197
218
  fill.style.width = pct + '%';
198
219
  pText.textContent = pct + '%';
199
220
 
221
+ // 파일 크기 ν‘œμ‹œ (loaded / total)
222
+ pSize.textContent = _fmtSize(ev.loaded) + ' / ' + _fmtSize(ev.total);
223
+
200
224
  var elapsed = (Date.now() - startTime) / 1000;
201
225
  if (elapsed > 0.5) {
202
226
  var bps = ev.loaded / elapsed;
227
+ var speedVal;
203
228
  if (bps > 1048576) {
204
- pSpeed.textContent = (bps / 1048576).toFixed(1) + ' MB/s';
229
+ speedVal = (bps / 1048576).toFixed(1) + ' MB/s';
205
230
  } else {
206
- pSpeed.textContent = (bps / 1024).toFixed(0) + ' KB/s';
231
+ speedVal = (bps / 1024).toFixed(0) + ' KB/s';
207
232
  }
233
+ pSpeed.textContent = _uploadLabels.speed + ': ' + speedVal;
208
234
  }
209
235
  };
210
236
 
211
237
  xhr.onload = function() {
212
238
  if (xhr.status >= 200 && xhr.status < 400) {
213
239
  fill.style.width = '100%';
214
- pText.textContent = 'μ—…λ‘œλ“œ μ™„λ£Œ!';
240
+ pText.textContent = _uploadLabels.complete;
215
241
  pSpeed.textContent = '';
216
- btn.textContent = 'πŸ”„ 썸넀일 μΆ”μΆœμ€‘...';
242
+ btn.textContent = _uploadLabels.thumbnailExtracting;
217
243
  setTimeout(function() {
218
244
  window.location.href = xhr.responseURL || '/board';
219
245
  }, 1500);
220
246
  } else {
221
247
  btn.disabled = false;
222
248
  btn.textContent = 'μž¬μ‹œλ„';
223
- pText.textContent = '였λ₯˜ λ°œμƒ';
249
+ pText.textContent = _uploadLabels.error;
224
250
  fill.style.background = '#e74c3c';
225
251
  }
226
252
  };
@@ -228,7 +254,7 @@ function handleUpload(e) {
228
254
  xhr.onerror = function() {
229
255
  btn.disabled = false;
230
256
  btn.textContent = 'μž¬μ‹œλ„';
231
- pText.textContent = 'λ„€νŠΈμ›Œν¬ 였λ₯˜';
257
+ pText.textContent = _uploadLabels.networkError;
232
258
  fill.style.background = '#e74c3c';
233
259
  };
234
260