Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions TUILiveKit.main.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,9 @@ function bindIPCEvent() {
ipcMain.handle('app-path', () => {
return app.getAppPath();
});
ipcMain.handle('get-language', () => {
return language;
});

ipcMain.handle('window-type', (event) => {
if (event.sender === windowMap.main?.webContents) {
Expand Down Expand Up @@ -794,10 +797,8 @@ function bindIPCEvent() {
postMessageToWindow(windowMap.confirm, 'port-to-confirm', null, [port]);
});

ipcMain.on('set-language', (event, args) => {
console.log(`${logPrefix}set-language`, args);
language = args;
});
// Legacy 'set-language' channel removed — language updates now flow through
// the V2 mainProcessHandlers['setLanguage'] via ipcBridge.sendToElectronMain().

ipcMain.on('show-context-menu', (event) => {
const template = [
Expand Down Expand Up @@ -948,6 +949,7 @@ function bindIPCEvent() {

function unbindIPCMainEvent() {
ipcMain.removeHandler('app-path');
ipcMain.removeHandler('get-language');
ipcMain.removeHandler('window-type');
ipcMain.removeAllListeners('window-message'); // V2 IPC message router
ipcMain.removeAllListeners('on-minimize-window');
Expand All @@ -957,7 +959,6 @@ function unbindIPCMainEvent() {
ipcMain.removeAllListeners('close-child');
ipcMain.removeAllListeners('login');
ipcMain.removeAllListeners('port-to-child');
ipcMain.removeAllListeners('set-language');
ipcMain.removeAllListeners('show-context-menu');
ipcMain.removeAllListeners('start-use-driver-installer');
ipcMain.removeAllListeners('app-quit-confirmed');
Expand Down
19 changes: 0 additions & 19 deletions electron-builder.json5
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
{
"from": "node_modules/trtc-electron-sdk/build/mac-framework/${arch}/",
"to": "./Frameworks"
},
{
"from": "node_modules/trtc-electron-plugin-xmagic/plugin/XMagic/mac/",
"to": "./Resources/app/plugin/XMagic/mac/"
}
],
"target": ["dmg"],
Expand All @@ -47,21 +43,6 @@
"**/*"
]
},
{
"from": "node_modules/trtc-electron-plugin-xmagic/plugin/XMagic/win/${arch}/platforms/",
"to": "./resources/platforms",
"filter": ["**/*"],
},
{
"from": "node_modules/trtc-electron-plugin-xmagic/plugin/XMagic/win/${arch}/",
"to": "./resources/app/plugin/XMagic/win/${arch}/",
"filter": ["**/*"],
},
{
"from": "node_modules/trtc-electron-plugin-xmagic/plugin/XMagic/win/res/",
"to": "./resources/app/plugin/XMagic/win/res/",
"filter": ["**/*"],
},
],
"target": ["nsis", "zip"],
},
Expand Down
66 changes: 64 additions & 2 deletions main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { app, ipcMain } = require('electron');
const { app, ipcMain, globalShortcut, Menu } = require('electron');
const { TUILiveKitMain } = require('./TUILiveKit.main');

function quitApplication() {
Expand All @@ -15,7 +15,65 @@ ipcMain.on('openTUILiveKit', (event, args) => {
TUILiveKitMain.on('closed', quitApplication);
});

function registerDisableRefreshShortcuts() {
const shortcuts = [
'CommandOrControl+R',
'CommandOrControl+Shift+R',
'F5',
];
shortcuts.forEach((shortcut) => {
const ok = globalShortcut.register(shortcut, () => {
console.log(`[main.js]blocked refresh shortcut: ${shortcut}`);
});
if (!ok) {
console.warn(`[main.js]failed to register shortcut: ${shortcut}`);
}
});
}

function setupApplicationMenuWithoutReload() {
const template = [];

if (process.platform === 'darwin') {
template.push({
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' },
],
});
}

template.push(
{ role: 'editMenu' },
{
label: 'View',
submenu: [
// Intentionally keep zoom/fullscreen/devtools, but remove reload/forceReload.
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'toggleDevTools' },
{ role: 'togglefullscreen' },
],
},
{ role: 'windowMenu' }
);

Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}

app.whenReady().then(() => {
registerDisableRefreshShortcuts();
setupApplicationMenuWithoutReload();
TUILiveKitMain.open();
});

Expand All @@ -25,4 +83,8 @@ app.on('window-all-closed', () => {

app.on('activate', () => {
TUILiveKitMain.open();
});
});

app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ultra-live-electron",
"version": "5.5.2",
"version": "5.8.2",
"description": "Tencent Cloud Living and Streaming Tool for desktop and laptop",
"main": "main.js",
"author": "Tencent Cloud",
Expand All @@ -23,9 +23,9 @@
"start:with-upload-server": "concurrently \"npm run serve\" \"npm run upload-server\" \"npm run electron\""
},
"dependencies": {
"@tencentcloud/tuiroom-engine-electron": "~4.0.1",
"@tencentcloud/uikit-base-component-vue3": "1.3.7",
"tuikit-atomicx-vue3-electron": "5.5.2",
"@tencentcloud/tuiroom-engine-electron": "~4.0.2",
"@tencentcloud/uikit-base-component-vue3": "1.4.1",
"tuikit-atomicx-vue3-electron": "5.8.2",
"core-js": "^3.36.1",
"movable-resizable-js": "^0.2.0",
"pinia": "^2.1.7",
Expand Down
219 changes: 219 additions & 0 deletions scripts/check-dialog-i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/* eslint-disable no-console */
const fs = require('node:fs');
const path = require('node:path');
const { parse, compileTemplate } = require('vue/compiler-sfc');

const projectRoot = path.resolve(__dirname, '..');
const srcDir = path.join(projectRoot, 'src');

function walk(dir) {
if (!fs.existsSync(dir)) {
return [];
}
const entries = fs.readdirSync(dir, { withFileTypes: true });
const out = [];
entries.forEach((entry) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
out.push(...walk(fullPath));
return;
}
if (entry.isFile() && fullPath.endsWith('.vue')) {
out.push(fullPath);
}
});
return out;
}

function walkTemplateNodes(nodeOrNodes, visitor) {
if (!nodeOrNodes) {
return;
}

if (Array.isArray(nodeOrNodes)) {
nodeOrNodes.forEach((node) => walkTemplateNodes(node, visitor));
return;
}

const node = nodeOrNodes;
switch (node.type) {
case 0: // Root
walkTemplateNodes(node.children, visitor);
break;
case 1: // Element
visitor(node);
walkTemplateNodes(node.children, visitor);
break;
case 9: // If
(node.branches || []).forEach((branch) => walkTemplateNodes(branch, visitor));
break;
case 10: // IfBranch
walkTemplateNodes(node.children, visitor);
break;
case 11: // For
walkTemplateNodes(node.children, visitor);
break;
default:
break;
}
}

function hasDialogTextProp(dialogNode, names) {
for (const prop of dialogNode.props || []) {
if (prop.type === 6 && names.has(prop.name)) {
return true;
}

if (
prop.type === 7
&& prop.name === 'bind'
&& prop.arg
&& prop.arg.type === 4
&& names.has(prop.arg.content)
) {
return true;
}
}
return false;
}

function isFooterSlotTemplate(node) {
for (const prop of node.props || []) {
if (prop.type === 6 && prop.name === 'slot' && prop.value && prop.value.content === 'footer') {
return true;
}

if (
prop.type === 7
&& prop.name === 'slot'
&& prop.arg
&& prop.arg.type === 4
&& prop.arg.content === 'footer'
) {
return true;
}
}

return false;
}

function hasFooterSlot(dialogNode) {
let found = false;
walkTemplateNodes(dialogNode.children || [], (node) => {
if (found) {
return;
}
if (node.tag === 'template' && isFooterSlotTemplate(node)) {
found = true;
}
});
return found;
}

function getLineNumberByOffset(source, offset) {
return source.slice(0, offset).split('\n').length;
}

function isIgnorableSfcParseError(error) {
return String(error).includes('At least one <template> or <script> is required');
}

function collectViolationsInFile(filePath) {
const source = fs.readFileSync(filePath, 'utf8');
const { descriptor, errors: parseErrors } = parse(source, { filename: filePath });

if (parseErrors.length > 0) {
if (parseErrors.every(isIgnorableSfcParseError)) {
return [];
}
return [
{
file: path.relative(projectRoot, filePath),
line: 1,
message: `SFC parse failed: ${String(parseErrors[0])}`,
},
];
}

if (!descriptor.template) {
return [];
}

const compiled = compileTemplate({
source: descriptor.template.content,
filename: filePath,
id: 'demo-dialog-i18n-check',
});

if (compiled.errors.length > 0) {
const firstError = compiled.errors[0];
const line = typeof firstError === 'object' && firstError?.loc?.start?.line
? descriptor.template.loc.start.line + firstError.loc.start.line - 1
: descriptor.template.loc.start.line;
const message = typeof firstError === 'string' ? firstError : firstError.message;
return [
{
file: path.relative(projectRoot, filePath),
line,
message: `Template compile failed: ${message}`,
},
];
}

const violations = [];
const templateStartOffset = descriptor.template.loc.start.offset;

walkTemplateNodes(compiled.ast, (node) => {
if (node.tag !== 'TUIDialog') {
return;
}
if (hasFooterSlot(node)) {
return;
}

const hasCancelText = hasDialogTextProp(node, new Set(['cancelText', 'cancel-text']));
const hasConfirmText = hasDialogTextProp(node, new Set(['confirmText', 'confirm-text']));

if (hasCancelText && hasConfirmText) {
return;
}

const missing = [];
if (!hasCancelText) {
missing.push('cancelText/cancel-text');
}
if (!hasConfirmText) {
missing.push('confirmText/confirm-text');
}

const offset = templateStartOffset + (node.loc?.start?.offset || 0);
violations.push({
file: path.relative(projectRoot, filePath),
line: getLineNumberByOffset(source, offset),
message: `Missing ${missing.join(' and ')}`,
});
});

return violations;
}

function main() {
const vueFiles = walk(srcDir);
const violations = [];

vueFiles.forEach((filePath) => {
violations.push(...collectViolationsInFile(filePath));
});

if (violations.length > 0) {
console.error('Dialog i18n check failed. Please provide explicit i18n button text.');
violations.forEach((item) => {
console.error(`- ${item.file}:${item.line} ${item.message}`);
});
process.exit(1);
}

console.log('Dialog i18n check passed.');
}

main();
Loading