优化目录结构
This commit is contained in:
136
client/public/call/controllers/call-view-controller.js
Normal file
136
client/public/call/controllers/call-view-controller.js
Normal 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
|
||||
};
|
||||
}
|
||||
190
client/public/call/controllers/invite-controller.js
Normal file
190
client/public/call/controllers/invite-controller.js
Normal 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
|
||||
};
|
||||
}
|
||||
220
client/public/call/controllers/profile-settings.js
Normal file
220
client/public/call/controllers/profile-settings.js
Normal 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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user