优化目录结构

This commit is contained in:
2026-05-25 20:37:36 +08:00
parent bbe7e71274
commit 40fd7f7e08
101 changed files with 108 additions and 110 deletions

View File

@@ -0,0 +1,136 @@
export function createCallViewController({ store, chatMessage, notify }) {
let isBound = false;
function toggleSidebar() {
chatMessage.toggleSidebar();
}
function toggleMute() {
const state = store.getState();
const currentState = state.session.localUser.mediaState.audio;
store.updateLocalMedia('audio', !currentState);
}
function toggleVideo() {
const state = store.getState();
const currentState = state.session.localUser.mediaState.video;
store.updateLocalMedia('video', !currentState);
}
function toggleLocalVideo() {
toggleVideo();
}
async function toggleRecording() {
try {
const result = await store.toggleRecording();
notify(result.message);
}
catch (error) {
notify(error.message || '\u5f55\u5236\u5931\u8d25');
}
}
function toggleMoreOptions() {
const menu = document.getElementById('moreOptionsMenu');
if (menu) {
menu.classList.toggle('hidden');
}
}
function changeResolution(width, height) {
store.changeResolution(width, height);
const menu = document.getElementById('moreOptionsMenu');
if (menu) {
menu.classList.add('hidden');
}
}
function endCall() {
const dialog = document.getElementById('endCallDialog');
if (dialog) {
dialog.classList.remove('hidden');
}
}
function cancelEndCall() {
const dialog = document.getElementById('endCallDialog');
if (dialog) {
dialog.classList.add('hidden');
}
}
function confirmEndCall() {
cancelEndCall();
store.endCall();
notify('\u901a\u8bdd\u5df2\u7ed3\u675f');
}
function handleKeydown(event) {
if (event.code === 'Space' && !event.target.matches('input, textarea')) {
event.preventDefault();
toggleMute();
}
if (event.ctrlKey && event.key === 'v') {
event.preventDefault();
toggleVideo();
}
}
function handleDocumentClick(event) {
const moreOptionsMenu = document.getElementById('moreOptionsMenu');
const moreOptionsButton = document.getElementById('moreOptionsBtn');
if (
moreOptionsMenu &&
moreOptionsButton &&
!moreOptionsMenu.contains(event.target) &&
!moreOptionsButton.contains(event.target)
) {
moreOptionsMenu.classList.add('hidden');
}
}
function bindButton(buttonId, handler) {
const button = document.getElementById(buttonId);
if (button && !button.dataset.bound) {
button.addEventListener('click', handler);
button.dataset.bound = 'true';
}
}
function exposeWindowHandlers() {
window.toggleSidebar = toggleSidebar;
window.toggleMute = toggleMute;
window.toggleVideo = toggleVideo;
window.toggleLocalVideo = toggleLocalVideo;
window.toggleRecording = toggleRecording;
window.toggleMoreOptions = toggleMoreOptions;
window.changeResolution = changeResolution;
window.endCall = endCall;
window.cancelEndCall = cancelEndCall;
window.confirmEndCall = confirmEndCall;
}
function bindDomEvents() {
exposeWindowHandlers();
if (isBound) {
return;
}
chatMessage.bindMessageEvents();
document.addEventListener('keydown', handleKeydown);
document.addEventListener('click', handleDocumentClick);
bindButton('cancelEndCall', cancelEndCall);
bindButton('confirmEndCall', confirmEndCall);
bindButton('moreOptionsBtn', toggleMoreOptions);
isBound = true;
}
return {
bindDomEvents
};
}

View File

@@ -0,0 +1,190 @@
import { createLogger } from '../../shared/logger.js';
const logger = createLogger('invite');
const DEFAULT_CALLER_NAME = '\u9080\u8bf7\u65b9';
const DEFAULT_CALLER_AVATAR = '/images/p2.png';
const DEFAULT_APPLY_REASON = '\u672a\u586b\u5199';
function readConnectionIdFromSearch(search) {
return new URLSearchParams(search).get('connectionId') || '';
}
function hideInviteDialog() {
const dialog = document.getElementById('callRequestDialog');
if (dialog) {
dialog.classList.add('hidden');
}
}
function normalizeInviteCaller(caller = {}) {
return {
connectionId: caller.connectionId || '',
inviterSocketId: caller.inviterSocketId || '',
inviterUserId: caller.inviterUserId || '',
name: caller.name || caller.inviterName || DEFAULT_CALLER_NAME,
avatar: caller.avatar || caller.inviterAvatar || DEFAULT_CALLER_AVATAR,
applyReason: caller.applyReason || caller.reason || DEFAULT_APPLY_REASON
};
}
function renderInviteDialog(caller) {
const dialog = document.getElementById('callRequestDialog');
if (!dialog) {
return false;
}
const callRequestName = document.getElementById('callRequestName');
const callRequestAvatar = document.getElementById('callRequestAvatar');
const callRequestText = document.getElementById('callRequestText');
const callRequestReason = document.getElementById('callRequestReason');
if (callRequestName) {
callRequestName.textContent = caller.name;
}
if (callRequestAvatar) {
callRequestAvatar.src = caller.avatar;
}
if (callRequestText) {
callRequestText.textContent = caller.connectionId
? `\u6b63\u5728\u9080\u8bf7\u60a8\u52a0\u5165\u901a\u8bdd (${caller.connectionId})`
: '\u6b63\u5728\u8bf7\u6c42\u4e0e\u60a8\u8fdb\u884c\u89c6\u9891\u901a\u8bdd';
}
if (callRequestReason) {
callRequestReason.textContent = caller.applyReason;
}
dialog.classList.remove('hidden');
return true;
}
export function createInviteController({
store,
notify,
onAcceptConnection,
getCurrentView,
getConnectionId,
setConnectionId
}) {
let pendingInvite = null;
let signalHandlersBound = false;
function showCallRequestDialog(caller = {}) {
const normalizedCaller = normalizeInviteCaller(caller);
pendingInvite = normalizedCaller;
if (normalizedCaller.connectionId) {
setConnectionId(normalizedCaller.connectionId);
}
return renderInviteDialog(normalizedCaller);
}
function getInvitePayloadFromUrl(search = window.location.search) {
const params = new URLSearchParams(search);
if (params.get('invite') !== '1') {
return null;
}
return normalizeInviteCaller({
name: params.get('callerName'),
avatar: params.get('callerAvatar'),
connectionId: params.get('connectionId')
});
}
function bindSignalHandlers() {
if (signalHandlersBound) {
return;
}
store.onSocketEvent('invite-call', (payload) => {
const caller = normalizeInviteCaller(payload);
showCallRequestDialog(caller);
notify(`${caller.name} \u9080\u8bf7\u4f60\u52a0\u5165\u901a\u8bdd`);
});
signalHandlersBound = true;
}
async function acceptInvite() {
hideInviteDialog();
const targetConnectionId =
(pendingInvite && pendingInvite.connectionId) ||
getConnectionId() ||
localStorage.getItem('connectionId') ||
readConnectionIdFromSearch(window.location.search);
if (!targetConnectionId) {
notify('\u7f3a\u5c11\u8fde\u63a5ID\uff0c\u65e0\u6cd5\u63a5\u53d7\u9080\u8bf7', 'error');
return;
}
setConnectionId(targetConnectionId);
if (pendingInvite) {
try {
store.sendInviteAccepted({
connectionId: targetConnectionId,
targetSocketId: pendingInvite.inviterSocketId,
targetUserId: pendingInvite.inviterUserId
});
} catch (error) {
logger.error('Error accepting invite:', error);
notify('\u63a5\u53d7\u9080\u8bf7\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5', 'error');
return;
}
}
pendingInvite = null;
notify('\u5df2\u63a5\u53d7\u901a\u8bdd\u8bf7\u6c42');
if (getCurrentView() !== 'call') {
await onAcceptConnection(targetConnectionId);
}
}
function rejectInvite() {
hideInviteDialog();
if (pendingInvite) {
try {
store.sendInviteRejected({
connectionId: pendingInvite.connectionId,
targetSocketId: pendingInvite.inviterSocketId,
targetUserId: pendingInvite.inviterUserId
});
} catch (error) {
logger.error('Error rejecting invite:', error);
}
}
pendingInvite = null;
notify('\u5df2\u62d2\u7edd\u901a\u8bdd\u8bf7\u6c42');
}
function bindDialogEvents() {
window.showCallRequest = showCallRequestDialog;
window.rejectCall = rejectInvite;
window.acceptCall = acceptInvite;
const rejectCallButton = document.getElementById('rejectCall');
const acceptCallButton = document.getElementById('acceptCall');
if (rejectCallButton && !rejectCallButton.dataset.bound) {
rejectCallButton.addEventListener('click', rejectInvite);
rejectCallButton.dataset.bound = 'true';
}
if (acceptCallButton && !acceptCallButton.dataset.bound) {
acceptCallButton.addEventListener('click', acceptInvite);
acceptCallButton.dataset.bound = 'true';
}
}
return {
bindDialogEvents,
bindSignalHandlers,
getInvitePayloadFromUrl,
showCallRequestDialog
};
}

View File

@@ -0,0 +1,220 @@
import { createLogger } from '../../shared/logger.js';
const logger = createLogger('profile');
const DEFAULT_AVATAR = '/images/p1.png';
const MAX_AVATAR_SIZE = 2 * 1024 * 1024;
const USER_ID_PREFIX = 'user_';
const USER_ID_LENGTH = 8;
const USER_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function getElement(id) {
return document.getElementById(id);
}
function setAvatarPreview(avatarUrl) {
const userAvatar = getElement('userAvatar');
const avatarPreview = getElement('avatarPreview');
if (userAvatar) {
userAvatar.src = avatarUrl;
}
if (avatarPreview) {
avatarPreview.src = avatarUrl;
}
}
function updateUserName(name) {
const userName = getElement('userName');
if (userName) {
userName.textContent = name;
}
}
function generateUserId() {
let result = USER_ID_PREFIX;
for (let i = 0; i < USER_ID_LENGTH; i++) {
result += USER_ID_CHARS.charAt(Math.floor(Math.random() * USER_ID_CHARS.length));
}
return result;
}
function readStoredSettings() {
const rawSettings = localStorage.getItem('userSettings');
if (!rawSettings) {
return null;
}
return JSON.parse(rawSettings);
}
function getCurrentSettingsPayload() {
const nicknameInput = getElement('nicknameInput');
const userIdInput = getElement('userIdInput');
const avatarPreview = getElement('avatarPreview');
return {
userId: userIdInput ? userIdInput.value : generateUserId(),
name: nicknameInput ? (nicknameInput.value || '\u6211') : '\u6211',
avatar: avatarPreview ? (avatarPreview.src || DEFAULT_AVATAR) : DEFAULT_AVATAR
};
}
async function uploadAvatar(formData) {
const response = await fetch('/api/upload/avatar', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('\u4e0a\u4f20\u5931\u8d25');
}
return response.json();
}
export function createProfileSettingsController({ store, notify }) {
let documentEventsBound = false;
function loadUserSettings() {
try {
const settings = readStoredSettings();
if (!settings) {
const nextUserId = generateUserId();
const userIdInput = getElement('userIdInput');
if (userIdInput) {
userIdInput.value = nextUserId;
}
setAvatarPreview(DEFAULT_AVATAR);
saveSettings();
return;
}
const userIdInput = getElement('userIdInput');
const nicknameInput = getElement('nicknameInput');
if (settings.userId && userIdInput) {
userIdInput.value = settings.userId;
}
if (settings.name && nicknameInput) {
nicknameInput.value = settings.name;
}
updateUserName(settings.name || '\u6211');
setAvatarPreview(settings.avatar || DEFAULT_AVATAR);
} catch (error) {
logger.error('Error loading user settings:', error);
setAvatarPreview(DEFAULT_AVATAR);
}
}
function saveSettings() {
const settings = getCurrentSettingsPayload();
localStorage.setItem('userSettings', JSON.stringify(settings));
store.syncSocketUserInfo(settings);
updateUserName(settings.name);
setAvatarPreview(settings.avatar);
notify('\u8bbe\u7f6e\u5df2\u4fdd\u5b58', 'success');
}
async function handleAvatarUpload(event) {
const file = event.target.files[0];
if (!file) {
return;
}
if (!file.type.startsWith('image/')) {
notify('\u8bf7\u9009\u62e9\u56fe\u7247\u6587\u4ef6', 'error');
return;
}
if (file.size > MAX_AVATAR_SIZE) {
notify('\u56fe\u7247\u5927\u5c0f\u4e0d\u80fd\u8d85\u8fc72MB', 'error');
return;
}
const formData = new FormData();
formData.append('avatar', file);
const userIdInput = getElement('userIdInput');
if (userIdInput) {
formData.append('userId', userIdInput.value);
}
notify('\u6b63\u5728\u4e0a\u4f20\u5934\u50cf...');
try {
const data = await uploadAvatar(formData);
if (!data.success || !data.avatarUrl) {
throw new Error(data.message || '\u672a\u77e5\u9519\u8bef');
}
setAvatarPreview(data.avatarUrl);
saveSettings();
notify('\u5934\u50cf\u4e0a\u4f20\u6210\u529f', 'success');
} catch (error) {
logger.error('Error uploading avatar:', error);
setAvatarPreview(DEFAULT_AVATAR);
notify('\u5934\u50cf\u4e0a\u4f20\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5', 'error');
}
}
function copyUserId() {
const userIdInput = getElement('userIdInput');
if (!userIdInput) {
return;
}
userIdInput.select();
document.execCommand('copy');
notify('\u7528\u6237ID\u5df2\u590d\u5236\u5230\u526a\u8d34\u677f', 'success');
}
function toggleSettingsMenu() {
const settingsMenu = getElement('settingsMenu');
if (settingsMenu) {
settingsMenu.classList.toggle('hidden');
}
}
function bindDocumentEvents() {
if (documentEventsBound) {
return;
}
document.addEventListener('click', (event) => {
const settingsMenu = getElement('settingsMenu');
const userSettingsButton = getElement('userSettingsBtn');
if (
settingsMenu &&
userSettingsButton &&
!settingsMenu.contains(event.target) &&
!userSettingsButton.contains(event.target)
) {
settingsMenu.classList.add('hidden');
}
});
documentEventsBound = true;
}
function bindWindowHandlers() {
window.saveSettings = saveSettings;
window.handleAvatarUpload = handleAvatarUpload;
window.copyUserId = copyUserId;
window.toggleSettingsMenu = toggleSettingsMenu;
}
return {
bindDocumentEvents,
bindWindowHandlers,
copyUserId,
handleAvatarUpload,
loadUserSettings,
saveSettings,
toggleSettingsMenu
};
}