create-fuzionx 0.1.48 β 0.1.50
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 +1 -1
- package/templates/common/locales/en.json +53 -3
- package/templates/common/locales/ko.json +53 -3
- package/templates/common/package.json.tpl +2 -2
- package/templates/spa/meta.json +1 -1
- package/templates/spa/views/default/spa/package.json +1 -0
- package/templates/spa/views/default/spa/src/components/FileUpload.vue +27 -11
- package/templates/spa/views/default/spa/src/components/Navbar.vue +1 -0
- package/templates/spa/views/default/spa/src/composables/useApi.js +2 -2
- package/templates/spa/views/default/spa/src/router/index.js +5 -0
- package/templates/spa/views/default/spa/src/views/BoardForm.vue +3 -3
- package/templates/spa/views/default/spa/src/views/LiveList.vue +488 -0
- package/templates/spa/views/default/spa/src/views/LiveRoom.vue +573 -0
- package/templates/spa/views/default/spa/src/views/LiveWatch.vue +319 -0
- package/templates/ssr/controllers/LiveController.js +36 -0
- package/templates/ssr/routes/web.js +12 -4
- package/templates/ssr/views/default/layouts/main.html +2 -0
- package/templates/ssr/views/default/pages/board/form.html +35 -9
- package/templates/ssr/views/default/pages/live/index.html +351 -0
- package/templates/ssr/views/default/pages/live/room.html +321 -0
- package/templates/ssr/views/default/pages/live/watch.html +148 -0
package/package.json
CHANGED
|
@@ -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",
|
|
@@ -229,7 +235,8 @@
|
|
|
229
235
|
"login": "Login",
|
|
230
236
|
"logout": "Logout",
|
|
231
237
|
"profile": "Profile",
|
|
232
|
-
"register": "Register"
|
|
238
|
+
"register": "Register",
|
|
239
|
+
"live": "Live"
|
|
233
240
|
},
|
|
234
241
|
"page": {
|
|
235
242
|
"404": "Page not found",
|
|
@@ -246,5 +253,48 @@
|
|
|
246
253
|
"title": "Edit Profile",
|
|
247
254
|
"update_success": "Profile updated."
|
|
248
255
|
},
|
|
249
|
-
"welcome": "Welcome, {name}! π"
|
|
256
|
+
"welcome": "Welcome, {name}! π",
|
|
257
|
+
"live": {
|
|
258
|
+
"back": "Back to list",
|
|
259
|
+
"broadcast": "Broadcast",
|
|
260
|
+
"cancel": "Cancel",
|
|
261
|
+
"channel_id": "Channel ID",
|
|
262
|
+
"channel_exists": "Channel already exists.",
|
|
263
|
+
"chat": "Chat",
|
|
264
|
+
"chat_placeholder": "Type a message...",
|
|
265
|
+
"connecting": "Connecting...",
|
|
266
|
+
"connection_failed": "connection failed",
|
|
267
|
+
"create": "New Channel",
|
|
268
|
+
"create_btn": "Create",
|
|
269
|
+
"create_failed": "Create failed",
|
|
270
|
+
"create_title": "Create New Channel",
|
|
271
|
+
"default": "Default",
|
|
272
|
+
"enter_channel_id": "Please enter a channel ID.",
|
|
273
|
+
"file_path": "File Path",
|
|
274
|
+
"file_stream": "File Streaming",
|
|
275
|
+
"hero_sub": "WebRTC Live Broadcast Β· VideoChat",
|
|
276
|
+
"join": "Join",
|
|
277
|
+
"join_room": "Enter Room",
|
|
278
|
+
"leave": "Leave",
|
|
279
|
+
"nickname": "Nickname",
|
|
280
|
+
"no_channels": "No active channels.",
|
|
281
|
+
"options": "Encoding Options",
|
|
282
|
+
"publisher": "Publisher",
|
|
283
|
+
"refresh": "Refresh",
|
|
284
|
+
"resolution": "Resolution",
|
|
285
|
+
"role": "Select Role",
|
|
286
|
+
"send": "Send",
|
|
287
|
+
"source": "Source",
|
|
288
|
+
"source_url": "Streaming URL",
|
|
289
|
+
"start_hint": "Create a new channel to get started.",
|
|
290
|
+
"stop": "Stop",
|
|
291
|
+
"stop_failed": "Stop failed",
|
|
292
|
+
"type": "Type",
|
|
293
|
+
"url_stream": "External URL",
|
|
294
|
+
"video_bitrate": "Video Bitrate",
|
|
295
|
+
"audio_bitrate": "Audio Bitrate",
|
|
296
|
+
"video_codec": "Video Codec",
|
|
297
|
+
"videochat": "VideoChat",
|
|
298
|
+
"viewer": "Viewer"
|
|
299
|
+
}
|
|
250
300
|
}
|
|
@@ -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": "β λͺ©λ‘",
|
|
@@ -229,7 +235,8 @@
|
|
|
229
235
|
"login": "λ‘κ·ΈμΈ",
|
|
230
236
|
"logout": "λ‘κ·Έμμ",
|
|
231
237
|
"profile": "νλ‘ν",
|
|
232
|
-
"register": "νμκ°μ
"
|
|
238
|
+
"register": "νμκ°μ
",
|
|
239
|
+
"live": "Live"
|
|
233
240
|
},
|
|
234
241
|
"page": {
|
|
235
242
|
"404": "νμ΄μ§λ₯Ό μ°Ύμ μ μμ΅λλ€",
|
|
@@ -246,5 +253,48 @@
|
|
|
246
253
|
"title": "νλ‘ν μμ ",
|
|
247
254
|
"update_success": "νλ‘νμ΄ μμ λμμ΅λλ€."
|
|
248
255
|
},
|
|
249
|
-
"welcome": "νμν©λλ€, {name}λ π"
|
|
256
|
+
"welcome": "νμν©λλ€, {name}λ π",
|
|
257
|
+
"live": {
|
|
258
|
+
"back": "λͺ©λ‘μΌλ‘",
|
|
259
|
+
"broadcast": "λ°©μ‘",
|
|
260
|
+
"cancel": "μ·¨μ",
|
|
261
|
+
"channel_id": "μ±λ ID",
|
|
262
|
+
"channel_exists": "μ΄λ―Έ μ‘΄μ¬νλ μ±λμ
λλ€.",
|
|
263
|
+
"chat": "μ±ν
",
|
|
264
|
+
"chat_placeholder": "λ©μμ§ μ
λ ₯...",
|
|
265
|
+
"connecting": "μ°κ²° μ€...",
|
|
266
|
+
"connection_failed": "μ°κ²° μ€ν¨",
|
|
267
|
+
"create": "μ μ±λ",
|
|
268
|
+
"create_btn": "μμ±",
|
|
269
|
+
"create_failed": "μμ± μ€ν¨",
|
|
270
|
+
"create_title": "μ μ±λ λ§λ€κΈ°",
|
|
271
|
+
"default": "κΈ°λ³Έκ°",
|
|
272
|
+
"enter_channel_id": "μ±λ IDλ₯Ό μ
λ ₯νμΈμ.",
|
|
273
|
+
"file_path": "νμΌ κ²½λ‘",
|
|
274
|
+
"file_stream": "νμΌ μ€νΈλ¦¬λ°",
|
|
275
|
+
"hero_sub": "WebRTC λΌμ΄λΈ λ°©μ‘ Β· νμμ±ν
",
|
|
276
|
+
"join": "μ
μ₯",
|
|
277
|
+
"join_room": "μ
μ₯",
|
|
278
|
+
"leave": "λκ°κΈ°",
|
|
279
|
+
"nickname": "λλ€μ",
|
|
280
|
+
"no_channels": "νμ± μ±λμ΄ μμ΅λλ€.",
|
|
281
|
+
"options": "μΈμ½λ© μ΅μ
",
|
|
282
|
+
"publisher": "μ‘μΆμ",
|
|
283
|
+
"refresh": "μλ‘κ³ μΉ¨",
|
|
284
|
+
"resolution": "ν΄μλ",
|
|
285
|
+
"role": "μν μ ν",
|
|
286
|
+
"send": "μ μ‘",
|
|
287
|
+
"source": "μμ€",
|
|
288
|
+
"source_url": "μ€νΈλ¦¬λ° URL",
|
|
289
|
+
"start_hint": "μ μ±λμ λ§λ€μ΄ μμνμΈμ.",
|
|
290
|
+
"stop": "μ’
λ£",
|
|
291
|
+
"stop_failed": "μ’
λ£ μ€ν¨",
|
|
292
|
+
"type": "νμ
",
|
|
293
|
+
"url_stream": "μΈλΆ URL",
|
|
294
|
+
"video_bitrate": "μμ λΉνΈλ μ΄νΈ",
|
|
295
|
+
"audio_bitrate": "μ€λμ€ λΉνΈλ μ΄νΈ",
|
|
296
|
+
"video_codec": "λΉλμ€ μ½λ±",
|
|
297
|
+
"videochat": "νμμ±ν
",
|
|
298
|
+
"viewer": "μμ²μ"
|
|
299
|
+
}
|
|
250
300
|
}
|
package/templates/spa/meta.json
CHANGED
|
@@ -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.
|
|
7
|
+
"@fuzionx/client": "^0.1.50"
|
|
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>{{
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
sizeText.value = '';
|
|
172
|
+
speedLabel.value = '';
|
|
157
173
|
progressColor.value = 'linear-gradient(90deg, #667eea, #764ba2)';
|
|
158
174
|
}
|
|
159
175
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
<router-link to="/features" class="nav-link">{{ t('nav.features', 'Features') }}</router-link>
|
|
15
15
|
<router-link to="/chat" class="nav-link">{{ t('nav.chat', 'Chat Demo') }}</router-link>
|
|
16
16
|
<router-link to="/board" class="nav-link">{{ t('nav.board', 'κ²μν') }}</router-link>
|
|
17
|
+
<router-link to="/live" class="nav-link">{{ t('nav.live', 'Live') }}</router-link>
|
|
17
18
|
<template v-if="authStore.isAuthenticated">
|
|
18
19
|
<router-link to="/profile" class="nav-link">{{ t('nav.profile', 'νλ‘ν') }}</router-link>
|
|
19
20
|
<button class="nav-link btn-link" @click="handleLogout">{{ t('nav.logout', 'λ‘κ·Έμμ') }}</button>
|
|
@@ -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 = () => {
|
|
@@ -15,6 +15,11 @@ export const routes = [
|
|
|
15
15
|
{ path: '/', name: 'home', component: HomeView },
|
|
16
16
|
{ path: '/features', name: 'features', component: () => import('../views/FeaturesView.vue') },
|
|
17
17
|
|
|
18
|
+
// ββ Live (μΈμ¦ νμ) ββ
|
|
19
|
+
{ path: '/live', name: 'live', component: () => import('../views/LiveList.vue'), meta: { auth: true } },
|
|
20
|
+
{ path: '/live/watch/:channelId', name: 'live-watch', component: () => import('../views/LiveWatch.vue'), meta: { auth: true } },
|
|
21
|
+
{ path: '/live/room/:channelId', name: 'live-room', component: () => import('../views/LiveRoom.vue'), meta: { auth: true } },
|
|
22
|
+
|
|
18
23
|
// ββ κ²μ€νΈ μ μ© (λ‘κ·ΈμΈ μνλ©΄ νμΌλ‘) ββ
|
|
19
24
|
{ path: '/login', name: 'login', component: () => import('../views/Login.vue'), meta: { guest: true } },
|
|
20
25
|
{ path: '/register', name: 'register', component: () => import('../views/Register.vue'), meta: { guest: true } },
|
|
@@ -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');
|