Skip to content
Open
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
10 changes: 5 additions & 5 deletions packages/tuikit-atomicx-vue3-electron/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tuikit-atomicx-vue3-electron",
"version": "5.5.2",
"version": "5.8.2",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
Expand Down Expand Up @@ -38,10 +38,10 @@
"scripts": {},
"peerDependencies": {
"@tencentcloud/lite-chat": "^1.6.4",
"@tencentcloud/chat-uikit-engine-lite": "~1.0.4",
"@tencentcloud/tui-core-lite": "~1.0.0",
"@tencentcloud/tuiroom-engine-electron": "~4.0.1",
"@tencentcloud/uikit-base-component-vue3": "~1.3.7",
"@tencentcloud/chat-uikit-engine-lite": "~1.0.5",
"@tencentcloud/tui-core-lite": "~1.0.1",
"@tencentcloud/tuiroom-engine-electron": "~4.0.2",
"@tencentcloud/uikit-base-component-vue3": "~1.4.1",
"trtc-electron-sdk": "13.1.709-beta.0",
"vue": "^3.4.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
class="audio-control-container"
>
<icon-button
:title="t('Mic')"
:title="t('AudioSetting.Mic')"
:has-more="
audioSettingProps?.displayMode === MediaSettingDisplayMode.IconWithPanel
"
Expand All @@ -31,13 +31,13 @@ import { ref, defineEmits, computed, inject } from 'vue';
import AudioIcon from '../../baseComp/AudioIcon.vue';
import IconButton from '../../baseComp/IconButton.vue';
import vClickOutside from '../../directives/vClickOutside';
import { useI18n } from '../../locales';
import { useDeviceState } from '../../states/DeviceState';
import { useRoomState } from '../../states/RoomState';
import { DeviceStatus, DeviceError, MediaSettingDisplayMode } from '../../types';
import AudioSettingTab from './AudioSettingTab.vue';
import type { AudioSettingProps } from '../../types';

import { useUIKit } from '@tencentcloud/uikit-base-component-vue3';
const audioSettingProps: AudioSettingProps | undefined
= inject('audioSettingProps');

Expand All @@ -57,7 +57,7 @@ const {
const emits = defineEmits(['click-icon']);

const showAudioSettingTab: Ref<boolean> = ref(false);
const { t } = useI18n();
const { t } = useUIKit();

const isMuted = computed(() => {
if (!currentRoom?.value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
<template>
<tui-dialog
v-model="showRequestOpenMicDialog"
:title="t('Tips')"
:title="t('AudioSetting.Tips')"
:modal="true"
:show-close="false"
:close-on-click-modal="false"
width="500px"
:append-to-room-container="true"
:confirm-button="t('Turn on the microphone')"
:cancel-button="t('Keep it closed')"
:confirm-button="t('AudioSetting.TurnOnMicrophone')"
:cancel-button="t('AudioSetting.KeepClosed')"
@confirm="handleAccept"
@cancel="handleReject"
>
<span>{{ dialogContent }}</span>
<template #footer>
<TUIButton type="primary" @click="handleAccept">
{{ t('Turn on the microphone') }}
{{ t('AudioSetting.TurnOnMicrophone') }}
</TUIButton>
<TUIButton @click="handleReject">
{{ t('Keep it closed') }}
{{ t('AudioSetting.KeepClosed') }}
</TUIButton>
</template>
</tui-dialog>
Expand Down Expand Up @@ -54,9 +54,9 @@ async function onRequestReceived(eventInfo: { request: TUIRequest }) {
if (requestAction === TUIRequestAction.kRequestToOpenRemoteMicrophone) {
const userRole
= getUserInfo({ userId })?.userRole === TUIRole.kRoomOwner
? t('RoomOwner')
: t('Admin');
dialogContent.value = t('Sb invites you to turn on the microphone', {
? t('AudioSetting.RoomOwner')
: t('AudioSetting.Admin');
dialogContent.value = t('AudioSetting.InviteTurnOnMicrophone', {
role: userRole,
});
requestOpenMicRequestId.value = requestId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<template>
<div class="audio-setting-tab">
<div v-if="audioSettingProps?.supportSwitchMicrophone" class="item-setting">
<span class="title">{{ t('Mic') }}</span>
<span class="title">{{ t('AudioSetting.Mic') }}</span>
<div class="flex">
<microphone-select class="select" />
<TUIButton v-if="isDetailMode" @click="handleMicrophoneTest">
{{ isMicrophoneTesting ? t('Stop') : t('Test') }}
{{ isMicrophoneTesting ? t('AudioSetting.Stop') : t('AudioSetting.Test') }}
</TUIButton>
</div>
</div>
<div v-if="audioSettingProps?.supportAudioLevel" class="item-setting">
<span class="title">{{ t('Output') }}</span>
<span class="title">{{ t('AudioSetting.Output') }}</span>
<div class="mic-bar-container">
<div
v-for="(item, index) in new Array(volumeTotalNum).fill('')"
Expand All @@ -23,7 +23,7 @@
</div>
</div>
<div v-if="audioSettingProps?.supportSwitchSpeaker" class="item-setting">
<span class="title">{{ t('Speaker') }}</span>
<span class="title">{{ t('AudioSetting.Speaker') }}</span>
<div class="flex">
<speaker-select
class="select"
Expand All @@ -32,7 +32,7 @@
"
/>
<TUIButton v-if="isDetailMode" @click="handleSpeakerTest">
{{ isSpeakerTesting ? t('Stop') : t('Test') }}
{{ isSpeakerTesting ? t('AudioSetting.Stop') : t('AudioSetting.Test') }}
</TUIButton>
</div>
</div>
Expand Down Expand Up @@ -105,7 +105,7 @@ async function handleSpeakerTest() {
} catch (error) {
console.error('[AudioSettingTab] speaker test action failed:', error);
if (isStartAction) {
TUIToast.error({ message: t('Speaker test failed to start, please check device connection') });
TUIToast.error({ message: t('AudioSetting.SpeakerTestFailed') });
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const resource = {
AudioSetting: {
Mic: 'Mic',
Output: 'Output',
Speaker: 'Speaker',
Test: 'Test',
Stop: 'Stop',
SpeakerTestFailed: 'Speaker test failed to start, please check device connection',
Tips: 'Tips',
RoomOwner: 'Room owner',
Admin: 'Admin',
TurnOnMicrophone: 'Turn on the microphone',
KeepClosed: 'Keep it closed',
InviteTurnOnMicrophone: '{{ role }} invites you to turn on the microphone',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { resource as enResource } from './en-US/index';
export { resource as zhResource } from './zh-CN/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const resource = {
AudioSetting: {
Mic: '麦克风',
Output: '输出',
Speaker: '扬声器',
Test: '测试',
Stop: '停止',
SpeakerTestFailed: '扬声器测试启动失败,请检查设备连接',
Tips: '提示',
RoomOwner: '房主',
Admin: '管理员',
TurnOnMicrophone: '打开麦克风',
KeepClosed: '暂不打开',
InviteTurnOnMicrophone: '{{ role }}邀请你打开麦克风',
},
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { addI18n } from '../../i18n';
import { enResource, zhResource } from './i18n';
import AudioSetting from './index.vue';

addI18n('en-US', { translation: enResource });
addI18n('zh-CN', { translation: zhResource });

export { AudioSetting };
export default AudioSetting;
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,11 @@
@blur="emit('blur')"
>
<template #prefix>
<div
v-if="!isRightSafeMode"
class="input-actions input-actions--prefix"
>
<div ref="prefixActionsRef" class="input-actions input-actions--prefix">
<EmojiPicker
:disabled="disabledAndPlaceholder.disabled"
:trigger-style="{ display: 'flex' }"
/>
</div>
</template>
<template #suffix>
<div
v-if="isRightSafeMode"
class="input-actions input-actions--suffix"
>
<EmojiPicker
:disabled="disabledAndPlaceholder.disabled"
:trigger-style="{ display: 'flex' }"
align="end"
:panel-width="emojiPanelWidth"
v-bind="emojiPickerProps"
/>
</div>
</template>
Expand Down Expand Up @@ -71,7 +56,7 @@ interface Props {
disabled?: boolean;
autoFocus?: boolean;
maxLength?: number;
emojiPopupMode?: 'default' | 'rightSafe';
emojiPopupMode?: 'inset' | 'outset';
}

const props = withDefaults(defineProps<Props>(), {
Expand All @@ -83,30 +68,58 @@ const props = withDefaults(defineProps<Props>(), {
disabled: false,
autoFocus: true,
maxLength: 80,
emojiPopupMode: 'default',
emojiPopupMode: 'outset',
});

const messageInputContainerRef = ref<HTMLElement | null>(null);
const prefixActionsRef = ref<HTMLElement | null>(null);
const emojiPanelWidth = ref(310);
const isRightSafeMode = computed(() => props.emojiPopupMode === 'rightSafe');
const emojiAlignOffset = ref(0);
const isInsetMode = computed(() => props.emojiPopupMode === 'inset');

// Aggregate EmojiPicker props based on popup mode to keep the template clean.
const emojiPickerProps = computed(() => {
if (isInsetMode.value) {
return {
align: 'start' as const,
alignOffset: emojiAlignOffset.value,
panelWidth: emojiPanelWidth.value,
panelMaxHeight: 320,
showScrollbar: true,
};
}
return {
align: 'center' as const,
alignOffset: 0,
panelWidth: 310,
panelMaxHeight: undefined,
showScrollbar: false,
};
});

let resizeObserver: ResizeObserver | null = null;
let resizeRafId: number | null = null;

function updateEmojiPanelWidth() {
if (!isRightSafeMode.value || !messageInputContainerRef.value) {
if (!isInsetMode.value || !messageInputContainerRef.value) {
return;
}
const containerWidth = messageInputContainerRef.value.clientWidth;
if (!containerWidth) {
return;
}
const rightPanel = messageInputContainerRef.value.closest('.main-right') as HTMLElement | null;
const panelWidth = rightPanel?.clientWidth || containerWidth;

// Constrain popup width by available area inside the right panel.
// This avoids left overflow when the right panel is narrow on Windows.
const safeMaxWidth = Math.min(containerWidth - 8, panelWidth - 24);
emojiPanelWidth.value = Math.min(310, Math.max(160, safeMaxWidth));
// In inset mode, set popup width equal to the input container width.
emojiPanelWidth.value = Math.max(160, containerWidth);

// Compute negative offset so the popup left edge aligns with the container left edge.
// The trigger (emoji icon) is inside prefix padding, so we shift the popup left
// by the distance between the container's left edge and the trigger's left edge.
const containerRect = messageInputContainerRef.value.getBoundingClientRect();
if (prefixActionsRef.value) {
const triggerRect = prefixActionsRef.value.getBoundingClientRect();
// Ensure offset is always non-positive to prevent unexpected rightward shift.
emojiAlignOffset.value = Math.min(0, -(triggerRect.left - containerRect.left));
}
}

function scheduleEmojiPanelResize() {
Expand Down Expand Up @@ -188,10 +201,6 @@ onBeforeUnmount(() => {
overflow: auto;
box-sizing: border-box;

&:focus-within {
border-color: var(--text-color-link);
}

.input-actions {
display: flex;
align-items: center;
Expand All @@ -202,10 +211,6 @@ onBeforeUnmount(() => {
margin-right: 12px;
}

&--suffix {
margin-left: 12px;
}

.send-button {
padding: 8px 20px;
background: var(--text-color-link);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,46 @@
gap: 8px;
width: var(--emoji-panel-width, 310px);
max-width: calc(100vw - 24px);
max-height: 300px;
max-height: var(--emoji-panel-max-height, 300px);
overflow-y: auto;
overflow-x: hidden;
border-radius: 16px;
padding: 16px;
padding: 12px;
margin-bottom: 10px;
background-color: var(--dropdown-color-default);
box-shadow: 0 0 10px 0 var(--shadow-color);
user-select: none;
scrollbar-width: none; // Firefox
-ms-overflow-style: none; // IE/Edge Legacy
justify-content: space-around;
justify-content: flex-start;

&::-webkit-scrollbar {
width: 0;
height: 0;
display: none;
}

// Show vertical scrollbar when content overflows in inset mode.
&--scrollbar-visible {
scrollbar-width: thin; // Firefox
-ms-overflow-style: auto; // IE/Edge Legacy

&::-webkit-scrollbar {
width: 6px;
height: 0;
display: block;
}

&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}

&::-webkit-scrollbar-track {
background: transparent;
}
}

&-item {
display: flex;
align-items: center;
Expand Down
Loading