派了Ch1-2/3-4/5-6/7/8/9/10-11共7个agent对全部11章重新深度测试。后端问题已核实追加进
docs/IM-后端问题清单-2026-09-04.md(新增6-9条,发给用户);本次是前端问题的修复。
**REASON_MESSAGES系统性补全(最高优先级,多个agent独立发现同一个缺口)**
im-api.js的REASON_MESSAGES表新增22个条目,最重要的是PERMISSION_DENIED——后端
server/im_http.go里403响应统一用这个reason(30余处调用),但表里原来只有一个从未
被后端实际发送过的死条目FORBIDDEN,导致全站几乎每一个403从工程一开始就在展示原始
英文"permission denied"。一并补上VERSION_CONFLICT/MALFORMED_REQUEST(Ch9 agent实测
命中)、NOT_FOUND、VERIFICATION_RATE_LIMITED(动态文案,同TOO_MANY_ATTEMPTS模式)等。
新增translateReason()导出函数并用于send-message.jsx的sendFavorite——favorites/forward
等几个批量接口恒定200、真实成败包在items[].reason里,imRequest的自动翻译够不到这种
per-item场景,之前直接显示原始reason字符串。tinode-web.jsx同理补了CTRL_ERROR_TEXT_CN
(原生WS {ctrl}协议错误,同样绕开REASON_MESSAGES)并修了原表从未真正匹配过的bug——
CommError会把状态码拼进message变成"authentication failed (401)",原表按裸文本精确
匹配,看着正常纯粹是因为401/403兜底文案凑巧一样,一旦命中"unknown authentication
scheme"这类会被兜底文案误导、"policy violation"这类会直接漏出英文。uploadLargeFile/
createChatUploader/createFileDownloader同样绕开REASON_MESSAGES(走原生{ctrl}协议),
补了UPLOAD_CTRL_TEXT_CN小词典。已用真实账号端到端验证:favorites/forward到P2P目标
触发的PERMISSION_DENIED现在正确显示"没有权限执行此操作";draft接口版本冲突正确显示
"内容已被修改,请刷新后重试"。
**编辑消息时打开附件面板会静默覆盖被编辑消息(Ch3-4 agent发现,数据损坏级别)**
sendMessage对editing状态一律用head.replace,但onAttachFile/onAttachImage/onAttachAudio
原来只在forwardMessage时置空,编辑态没有同款门禁——编辑一条消息不改文字、点"+"选张图片
或名片,会直接把附件内容替换掉被编辑消息的原文,无任何确认。messages-view.jsx新增
editingMessage门禁,和forwardMessage一样把三个attach回调置空(等价于隐藏整个"+"按钮)。
已用真实浏览器验证:编辑态下"+"按钮消失,取消编辑后正常恢复。
**草稿防抖单一共享定时器丢字(Ch1-2、Ch3-4两个agent独立发现)**
handleComposerChange用单个this.draftSaveTimer,话题A打字触发600ms保存后300ms内切到
话题B,会把A的定时器clearTimeout掉,那次输入永久丢失;version也在定时器触发时才重新
读state,此时可能已经在别的话题上,退化成硬编码0——叠加后端draft接口对version=0"无条件
跳过乐观锁"的行为(已记入后端清单新增6),可能静默覆盖别的标签页写入的更新草稿。改为
按话题分开的定时器map,version在调用的当下(保证仍在该话题)同步捕获进闭包。
**呼叫状态机两处真实bug(Ch8 agent此前发现,本次重新核实live代码后确认+修复)**
1. 通话中导航离开messages-view.jsx整个组件树(切到工作台/个人设置,或移动端切回会话
列表)时,没有任何hangup清理——componentSetup已有的"切换话题时正确挂断"逻辑只在
componentDidUpdate里,组件真正卸载时不会触发。CallPanel自己的componentWillUnmount
只做本地清理,不通知对方也不重置tinode-web.jsx顶层callState,导致下次切回通话话题
时remount出一个callState卡在CALL_STATE_IN_PROGRESS的"僵尸"面板——start()走"我们刚
接听"分支直接return,从不调getUserMedia,本地媒体永久拿不到。补了对称的
componentWillUnmount挂断逻辑。
2. this.callTimeoutTimer声明后从未被真正setTimeout赋值——呼出无人接听或accept信令
(fire-and-forget的{note}包)被丢会永久卡在"呼叫中"。补了armCallRingTimeout(),
60秒未接通按跟手动取消完全一样的路径挂断(不发明新的通话结果语义),在收到对方
accept信号的两处分支都正确清timer。
**其余前端修复(按文件)**
- messages-view.jsx: @提醒mentionedUsers只在切话题时清空,同一话题内后续消息里字面
文本"@张三"会被静默解析成真实提醒(发送后清空);jumpToMessage点开已撤回/删除的
搜索结果原来只console.error静默失败,补了用户可见的错误提示;批量收藏对
favorites/batch共享Idempotency-Key导致的静默丢失(已知后端问题)不再照单全收
"收藏成功",改为发送后重新拉取真实收藏列表核对每条是否真的落地。
- audio-recorder.jsx: getUserMedia权限弹窗未决出结果时组件被卸载,补_unmounted
标记避免在死实例上继续跑mediaRecorder.start()。
- send-message.jsx: disabled变为true时(对方拉黑/被禁言等实时ACS变化)没有强制结束
正在进行的按住录音,可能remount出无用户手势的"自动录音";mouseup/touchend覆盖不到
拖出窗口/切换标签页/权限弹窗抢焦点的释放场景,补了blur/visibilitychange兜底。
- tinode-web.jsx: messages-menu视图和messages-home共用MessageCenterView实例无key,
从messages-home以非整体刷新方式切到messages-menu时菜单打不开,补key={view}。
- message-center-view.jsx: 实名认证驳回提醒的dismiss key只按用户+状态区分,同一状态
的两次独立驳回会互相误压制,改按submission_id区分。
- contact-management-view.jsx: Profile/Settings靠getUserProfile刷新拉黑状态,但该
接口对被拉黑对象直接403,导致"之前会话就已拉黑"的场景完全感知不到——叠加
getConversationContext兜底(该接口对拉黑目标正确返回200+relationship)。
- group-management-view.jsx: AdminList显示群主/管理员姓名漏了group_nickname兜底;
JoinRequests从聊天页横幅进入时返回按钮硬编码回group-settings(应回聊天本身),
从Governance进入时返回group-settings而非group-governance(与AdminList/Muted不一致)。
- workplace-collaboration-view.jsx: OrganizationDirectory/DeptNode的部门树加载失败
被无条件当成"暂无数据"渲染,补loadError区分+重试链接。
- personal-settings-view.jsx: CacheClear对navigator.storage/caches整个不存在的情况
只有react出了范围的.catch()覆盖不到的同步抛错,加了特性检测+try/catch;
YouthModeVerify触发TOO_MANY_ATTEMPTS锁定后确定按钮没有禁用/倒计时,会继续发出
注定失败的请求。
- security-support-view.jsx: 意见反馈category字段在config未加载完成前可为空但没有
提交前校验,会打到后端400。
- use-cached-data.js: bootstrap等首次拉取失败且无缓存兜底时永久停在undefined,加了
一次3秒后重试(仅在完全没有缓存值时触发,已有缓存值维持原"显示旧数据"行为不变)。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
422 lines
16 KiB
JavaScript
422 lines
16 KiB
JavaScript
"use strict";
|
|
(self["webpackChunktinode_webapp"] = self["webpackChunktinode_webapp"] || []).push([["src_widgets_audio-recorder_jsx"],{
|
|
|
|
/***/ "./src/widgets/audio-recorder.jsx":
|
|
/*!****************************************!*\
|
|
!*** ./src/widgets/audio-recorder.jsx ***!
|
|
\****************************************/
|
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
|
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-intl */ "react-intl");
|
|
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
|
|
/* harmony import */ var _audio_player_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./audio-player.jsx */ "./src/widgets/audio-player.jsx");
|
|
/* harmony import */ var webm_duration_fix__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! webm-duration-fix */ "./node_modules/webm-duration-fix/lib/index.js");
|
|
/* harmony import */ var webm_duration_fix__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(webm_duration_fix__WEBPACK_IMPORTED_MODULE_3__);
|
|
/* harmony import */ var _lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../lib/blob-helpers.js */ "./src/lib/blob-helpers.js");
|
|
/* harmony import */ var _lib_strformat__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../lib/strformat */ "./src/lib/strformat.js");
|
|
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
|
|
/* harmony import */ var react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! react/jsx-dev-runtime */ "./node_modules/react/jsx-dev-runtime.js");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const BUFFER_SIZE = 256;
|
|
const CANVAS_UPSCALING = 2.0;
|
|
const LINE_WIDTH = 3 * CANVAS_UPSCALING;
|
|
const SPACING = 2 * CANVAS_UPSCALING;
|
|
const MILLIS_PER_BAR = 100;
|
|
const BAR_COLOR = '#BBBD';
|
|
const BAR_SCALE = 64.0;
|
|
const VISUALIZATION_BARS = 96;
|
|
const MAX_SAMPLES_PER_BAR = 10;
|
|
const DEFAULT_AUDIO_MIME_TYPE = 'audio/webm';
|
|
const SAFARI_AUDIO_MIME_TYPE = 'audio/mp4';
|
|
const AUDIO_MIME_TYPES = [DEFAULT_AUDIO_MIME_TYPE, SAFARI_AUDIO_MIME_TYPE, ''];
|
|
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
|
icon_title_delete: {
|
|
id: 'icon_title_delete',
|
|
defaultMessage: 'Delete recording',
|
|
description: 'Icon tool tip for deleting recorded audio'
|
|
},
|
|
icon_title_pause: {
|
|
id: 'icon_title_pause',
|
|
defaultMessage: 'Pause playback',
|
|
description: 'Icon tool tip for pausing audio playback'
|
|
},
|
|
icon_title_resume: {
|
|
id: 'icon_title_resume',
|
|
defaultMessage: 'Resume playback',
|
|
description: 'Icon tool tip for resuming audio playback'
|
|
},
|
|
icon_title_send: {
|
|
id: 'icon_title_send',
|
|
defaultMessage: 'Send message',
|
|
description: 'Icon tool tip for sending a message'
|
|
},
|
|
failed_to_init_audio: {
|
|
id: 'failed_to_init_audio',
|
|
defaultMessage: 'Failed to initialize audio recording',
|
|
description: 'Error message when audio is not available'
|
|
}
|
|
});
|
|
class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
enabled: true,
|
|
audioRecord: null,
|
|
recording: true,
|
|
paused: false,
|
|
duration: '0:00',
|
|
blobUrl: null,
|
|
preview: null
|
|
};
|
|
this.visualize = this.visualize.bind(this);
|
|
this.initMediaRecording = this.initMediaRecording.bind(this);
|
|
this.initCanvas = this.initCanvas.bind(this);
|
|
this.getRecording = this.getRecording.bind(this);
|
|
this.cleanUp = this.cleanUp.bind(this);
|
|
this.handleResume = this.handleResume.bind(this);
|
|
this.handlePause = this.handlePause.bind(this);
|
|
this.handleDelete = this.handleDelete.bind(this);
|
|
this.handleDone = this.handleDone.bind(this);
|
|
this.durationMillis = 0;
|
|
this.startedOn = null;
|
|
this.viewBuffer = [];
|
|
this.canvasRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
|
|
this.recordingTimestamp = 0;
|
|
}
|
|
componentDidMount() {
|
|
this.stream = null;
|
|
this.mediaRecorder = null;
|
|
this.audioContext = null;
|
|
this.audioInput = null;
|
|
this.analyser = null;
|
|
this.audioChunks = [];
|
|
try {
|
|
navigator.mediaDevices.getUserMedia({
|
|
audio: true,
|
|
video: false
|
|
}).then(this.initMediaRecording, this.props.onError);
|
|
} catch (err) {
|
|
this.props.onError(err);
|
|
}
|
|
}
|
|
componentWillUnmount() {
|
|
this._unmounted = true;
|
|
this.startedOn = null;
|
|
if (this.stream) {
|
|
this.cleanUp();
|
|
}
|
|
}
|
|
visualize() {
|
|
this.initCanvas();
|
|
const pcmData = new Uint8Array(this.analyser.frequencyBinCount);
|
|
const width = this.canvasWidth;
|
|
const height = this.canvasHeight;
|
|
const viewLength = width / (LINE_WIDTH + SPACING) | 0;
|
|
const viewDuration = MILLIS_PER_BAR * viewLength;
|
|
this.canvasContext.lineWidth = LINE_WIDTH;
|
|
this.canvasContext.strokeStyle = BAR_COLOR;
|
|
let prevBarCount = 0;
|
|
let volume = 0.0;
|
|
let countPerBar = 0;
|
|
const drawFrame = _ => {
|
|
if (!this.startedOn) {
|
|
return;
|
|
}
|
|
window.requestAnimationFrame(drawFrame);
|
|
const duration = this.durationMillis + (Date.now() - this.startedOn);
|
|
this.setState({
|
|
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_5__.secondsToTime)(duration / 1000)
|
|
});
|
|
if (duration > _config_js__WEBPACK_IMPORTED_MODULE_6__.MAX_DURATION) {
|
|
this.startedOn = null;
|
|
this.mediaRecorder.pause();
|
|
this.durationMillis += Date.now() - this.startedOn;
|
|
this.setState({
|
|
enabled: false,
|
|
recording: false,
|
|
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_5__.secondsToTime)(this.durationMillis / 1000)
|
|
});
|
|
}
|
|
this.analyser.getByteTimeDomainData(pcmData);
|
|
let amp = 0.0;
|
|
for (const amplitude of pcmData) {
|
|
amp += (amplitude - 127) ** 2;
|
|
}
|
|
volume += Math.sqrt(amp / pcmData.length);
|
|
countPerBar++;
|
|
let barCount = duration / MILLIS_PER_BAR | 0;
|
|
const dx = viewDuration > duration ? 0 : (duration - MILLIS_PER_BAR * barCount) / MILLIS_PER_BAR * (LINE_WIDTH + SPACING);
|
|
if (prevBarCount != barCount) {
|
|
prevBarCount = barCount;
|
|
this.viewBuffer.push(volume / countPerBar);
|
|
volume = 0.0;
|
|
countPerBar = 0;
|
|
if (this.viewBuffer.length > viewLength) {
|
|
this.viewBuffer.shift();
|
|
}
|
|
}
|
|
this.canvasContext.clearRect(0, 0, width, height);
|
|
this.canvasContext.beginPath();
|
|
for (let i = 0; i < this.viewBuffer.length; i++) {
|
|
let x = i * (LINE_WIDTH + SPACING) - dx;
|
|
let y = Math.max(Math.min(this.viewBuffer[i] / BAR_SCALE, 0.9) * height, 1);
|
|
this.canvasContext.moveTo(x, (height - y) * 0.5);
|
|
this.canvasContext.lineTo(x, (height + y) * 0.5);
|
|
}
|
|
this.canvasContext.stroke();
|
|
const now = new Date().getTime();
|
|
if (now - this.recordingTimestamp > _config_js__WEBPACK_IMPORTED_MODULE_6__.KEYPRESS_DELAY) {
|
|
this.props.onRecordingProgress();
|
|
this.recordingTimestamp = now;
|
|
}
|
|
};
|
|
drawFrame();
|
|
}
|
|
handlePause(e) {
|
|
e.preventDefault();
|
|
this.mediaRecorder.pause();
|
|
this.mediaRecorder.requestData();
|
|
this.durationMillis += Date.now() - this.startedOn;
|
|
this.startedOn = null;
|
|
this.setState({
|
|
recording: false
|
|
});
|
|
}
|
|
handleResume(e) {
|
|
e.preventDefault();
|
|
if (this.state.enabled) {
|
|
this.startedOn = Date.now();
|
|
this.mediaRecorder.resume();
|
|
this.setState({
|
|
recording: true
|
|
}, this.visualize);
|
|
}
|
|
}
|
|
handleDelete(e) {
|
|
e.preventDefault();
|
|
this.durationMillis = 0;
|
|
this.startedOn = null;
|
|
if (this.mediaRecorder) {
|
|
this.mediaRecorder.stop();
|
|
}
|
|
this.cleanUp();
|
|
this.setState({
|
|
recording: false
|
|
});
|
|
if (!this.mediaRecorder && this.props.onDeleted) {
|
|
this.props.onDeleted();
|
|
}
|
|
}
|
|
handleDone(e) {
|
|
e.preventDefault();
|
|
this.setState({
|
|
recording: false
|
|
});
|
|
if (this.startedOn) {
|
|
this.durationMillis += Date.now() - this.startedOn;
|
|
this.startedOn = null;
|
|
}
|
|
if (this.mediaRecorder) {
|
|
this.mediaRecorder.stop();
|
|
} else if (this.props.onDeleted) {
|
|
this.props.onDeleted();
|
|
}
|
|
}
|
|
initCanvas() {
|
|
this.canvasRef.current.width = this.canvasRef.current.offsetWidth * CANVAS_UPSCALING;
|
|
this.canvasRef.current.height = this.canvasRef.current.offsetHeight * CANVAS_UPSCALING;
|
|
this.canvasContext = this.canvasRef.current.getContext('2d');
|
|
this.canvasContext.lineCap = 'round';
|
|
this.canvasContext.translate(0.5, 0.5);
|
|
this.canvasWidth = this.canvasRef.current.width;
|
|
this.canvasHeight = this.canvasRef.current.height;
|
|
}
|
|
initMediaRecording(stream) {
|
|
if (this._unmounted) {
|
|
stream.getTracks().forEach(t => t.stop());
|
|
return;
|
|
}
|
|
this.stream = stream;
|
|
AUDIO_MIME_TYPES.some(mimeType => {
|
|
if (MediaRecorder.isTypeSupported(mimeType)) {
|
|
this.mediaRecorder = new MediaRecorder(stream, {
|
|
mimeType: mimeType,
|
|
audioBitsPerSecond: 24_000
|
|
});
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
if (!this.mediaRecorder) {
|
|
console.warn('MediaRecorder failed to initialize: no supported audio formats');
|
|
this.props.onError(this.props.intl.formatMessage(messages.failed_to_init_audio));
|
|
return;
|
|
}
|
|
this.audioContext = new AudioContext();
|
|
this.audioInput = this.audioContext.createMediaStreamSource(stream);
|
|
if (!this.audioInput) {
|
|
console.warn('createMediaStreamSource returned null: audio input unavailable');
|
|
this.props.onError(this.props.intl.formatMessage(messages.failed_to_init_audio));
|
|
return;
|
|
}
|
|
this.analyser = this.audioContext.createAnalyser();
|
|
this.analyser.fftSize = BUFFER_SIZE;
|
|
this.audioInput.connect(this.analyser);
|
|
this.mediaRecorder.onstop = _ => {
|
|
if (this.durationMillis > _config_js__WEBPACK_IMPORTED_MODULE_6__.MIN_DURATION) {
|
|
this.getRecording(this.mediaRecorder.mimeType).then(result => this.props.onFinished(result.url, result.preview, this.durationMillis));
|
|
} else {
|
|
this.props.onDeleted();
|
|
}
|
|
this.cleanUp();
|
|
};
|
|
this.mediaRecorder.ondataavailable = e => {
|
|
if (e.data.size > 0) {
|
|
this.audioChunks.push(e.data);
|
|
}
|
|
if (this.mediaRecorder.state != 'inactive') {
|
|
this.getRecording(this.mediaRecorder.mimeType).then(result => {
|
|
this.setState({
|
|
blobUrl: result.url,
|
|
preview: result.preview
|
|
});
|
|
});
|
|
}
|
|
};
|
|
this.durationMillis = 0;
|
|
this.startedOn = Date.now();
|
|
this.mediaRecorder.start();
|
|
this.visualize();
|
|
this.props.onRecordingProgress();
|
|
this.recordingTimestamp = this.startedOn;
|
|
}
|
|
getRecording(mimeType) {
|
|
mimeType = mimeType || DEFAULT_AUDIO_MIME_TYPE;
|
|
let blob = new Blob(this.audioChunks, {
|
|
type: mimeType
|
|
});
|
|
const result = mimeType == DEFAULT_AUDIO_MIME_TYPE ? webm_duration_fix__WEBPACK_IMPORTED_MODULE_3___default()(blob, mimeType) : Promise.resolve(blob);
|
|
return result.then(fixedBlob => {
|
|
blob = fixedBlob;
|
|
return fixedBlob.arrayBuffer();
|
|
}).then(arrayBuff => this.audioContext.decodeAudioData(arrayBuff)).then(decoded => this.createPreview(decoded)).then(preview => ({
|
|
url: window.URL.createObjectURL(blob),
|
|
preview: (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__.intArrayToBase64)(preview)
|
|
}));
|
|
}
|
|
createPreview(audio) {
|
|
const data = audio.getChannelData(0);
|
|
const viewLength = Math.min(data.length, VISUALIZATION_BARS);
|
|
const totalSPB = data.length / viewLength | 0;
|
|
const samplingRate = Math.max(1, totalSPB / MAX_SAMPLES_PER_BAR | 0);
|
|
let buffer = [];
|
|
let max = -1;
|
|
for (let i = 0; i < viewLength; i++) {
|
|
let amplitude = 0;
|
|
let count = 0;
|
|
for (let j = 0; j < totalSPB; j += samplingRate) {
|
|
amplitude += data[totalSPB * i + j] ** 2;
|
|
count++;
|
|
}
|
|
const val = Math.sqrt(amplitude / count);
|
|
buffer.push(val);
|
|
max = Math.max(max, val);
|
|
}
|
|
if (max > 0) {
|
|
buffer = buffer.map(a => 100 * a / max | 0);
|
|
}
|
|
return buffer;
|
|
}
|
|
cleanUp() {
|
|
if (this.audioInput) {
|
|
this.audioInput.disconnect();
|
|
}
|
|
if (this.stream) {
|
|
this.stream.getTracks().forEach(track => track.stop());
|
|
}
|
|
}
|
|
render() {
|
|
const {
|
|
formatMessage
|
|
} = this.props.intl;
|
|
const resumeClass = 'material-icons ' + (this.state.enabled ? 'red' : 'gray');
|
|
if (this.props.holdMode) {
|
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
|
className: 'audio audio-hold' + (this.props.cancelling ? ' cancelling' : ''),
|
|
children: [this.state.recording ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("canvas", {
|
|
ref: this.canvasRef
|
|
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
|
className: "duration",
|
|
children: this.state.duration
|
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
|
className: "audio-hold-hint",
|
|
children: this.props.cancelling ? '松开手指,取消发送' : '手指上滑,取消发送'
|
|
}, void 0, false)]
|
|
}, void 0, true);
|
|
}
|
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
|
className: "audio",
|
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("a", {
|
|
href: "#",
|
|
onClick: this.handleDelete,
|
|
title: formatMessage(messages.icon_title_delete),
|
|
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("i", {
|
|
className: "material-icons gray",
|
|
children: "delete_outline"
|
|
}, void 0, false)
|
|
}, void 0, false), this.state.recording ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("canvas", {
|
|
ref: this.canvasRef
|
|
}, void 0, false) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(_audio_player_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
|
|
src: this.state.blobUrl,
|
|
preview: this.state.preview,
|
|
duration: this.durationMillis,
|
|
short: true
|
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
|
className: "duration",
|
|
children: this.state.duration
|
|
}, void 0, false), this.state.recording ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("a", {
|
|
href: "#",
|
|
onClick: this.handlePause,
|
|
title: formatMessage(messages.icon_title_pause),
|
|
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("i", {
|
|
className: "material-icons",
|
|
children: "pause_circle_outline"
|
|
}, void 0, false)
|
|
}, void 0, false) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("a", {
|
|
href: "#",
|
|
onClick: this.handleResume,
|
|
title: formatMessage(messages.icon_title_resume),
|
|
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("i", {
|
|
className: resumeClass,
|
|
children: "radio_button_checked"
|
|
}, void 0, false)
|
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("a", {
|
|
href: "#",
|
|
onClick: this.handleDone,
|
|
title: formatMessage(messages.icon_title_send),
|
|
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("i", {
|
|
className: "material-icons",
|
|
children: "send"
|
|
}, void 0, false)
|
|
}, void 0, false)]
|
|
}, void 0, true);
|
|
}
|
|
}
|
|
/* harmony default export */ __webpack_exports__["default"] = ((0,react_intl__WEBPACK_IMPORTED_MODULE_1__.injectIntl)(AudioRecorder, {
|
|
forwardRef: true
|
|
}));
|
|
|
|
/***/ })
|
|
|
|
}]);
|
|
//# sourceMappingURL=src_widgets_audio-recorder_jsx.dev.js.map
|