diff --git a/3parties/xterm-addon-fit.js b/3parties/xterm-addon-fit.js new file mode 100644 index 0000000..709e60a --- /dev/null +++ b/3parties/xterm-addon-fit.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(window,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(){}return e.prototype.activate=function(e){this._terminal=e},e.prototype.dispose=function(){},e.prototype.fit=function(){var e=this.proposeDimensions();if(e&&this._terminal){var t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}},e.prototype.proposeDimensions=function(){if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement){var e=this._terminal._core,t=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(t.getPropertyValue("height")),n=Math.max(0,parseInt(t.getPropertyValue("width"))),o=window.getComputedStyle(this._terminal.element),i=r-(parseInt(o.getPropertyValue("padding-top"))+parseInt(o.getPropertyValue("padding-bottom"))),a=n-(parseInt(o.getPropertyValue("padding-right"))+parseInt(o.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth;return{cols:Math.max(2,Math.floor(a/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(i/e._renderService.dimensions.actualCellHeight))}}},e}();t.FitAddon=n}])}); +//# sourceMappingURL=xterm-addon-fit.js.map \ No newline at end of file diff --git a/bricks/asr.js b/bricks/asr.js new file mode 100644 index 0000000..77dc12a --- /dev/null +++ b/bricks/asr.js @@ -0,0 +1,76 @@ +var bricks = window.bricks || {}; +bricks.ASRClient = class extends bricks.VBox { + /* + options: + { + start_icon:record.png, + stop_icon:stop.png + ws_url: + icon_options + ws_params: + } + event: + start: start recording, no params + stop: stop recording, no params + transtext: recognised text, params={ + "content": + "speaker": + "start": + "end": + } + */ + constructor(opts){ + super(opts); + var icon_options = this.icon_options || {}; + icon_options.url = this.start_icon || bricks_resource('imgs/start_recording.png'); + this.icon = new bricks.Icon(icon_options); + this.status = 'stop'; + this.icon.bind('click', this.toggle_button.bind(this)); + this.add_widget(this.icon); + var sessdata = bricks.app.get_session(); + this.socket = new WebSocket(this.ws_url, sessdata); + this.socket.onmessage = this.response_data.bind(this); + this.bind('transtext', this.response_log.bind(this)); + } + response_log(event){ + console.log('response data=', event.params); + } + toggle_button(){ + if (this.status == 'stop'){ + this.icon.set_url(this.start_icon||bricks_resource('imgs/stop_recording.png')); + this.status = 'start'; + this.start_recording(); + } else { + this.icon.set_url(this.stop_icon||bricks_resource('imgs/start_recording.png')); + this.status = 'stop'; + this.stop_recording(); + } + } + async start_recording() { + this.stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + this.mediaRecorder = new MediaRecorder(this.stream); + this.mediaRecorder.ondataavailable = (event) => { + if (event.data.size > 0) { + // 将音频数据通过 WebSocket 发送到服务器 + blobToBase64(event.data).then((b64str) => { + var d = objcopy(this.ws_params); + d.type = 'audiobuffer'; + d.data = b64str; + this.socket.send(JSON.stringify(d)); + }).catch((error) => { + console.log('Error', error); + }); + } + } + this.mediaRecorder.start(1000); // 每 1 秒发送一次数据 + } + stop_recording(){ + this.mediaRecorder.stop(); + } + response_data(event){ + var d = JSON.parse(event.data); + this.dispatch('transtext', d); + } +} + +bricks.Factory.register('ASRClient', bricks.ASRClient); diff --git a/bricks/audio.js b/bricks/audio.js index 2f06e57..29badaa 100644 --- a/bricks/audio.js +++ b/bricks/audio.js @@ -26,14 +26,70 @@ bricks.AudioPlayer = class extends bricks.JsWidget { this.audio = this._create('audio'); this.audio.controls = true; if (this.opts.autoplay){ - this.audio.addEventListener('canplay', this.play_audio.bind(this)); + this.audio.addEventListener('canplay', this.play.bind(this)); } + this.audio.addEventListener('ended', this.playended.bind(this)); this.audio.style.width = "100%" this.dom_element.appendChild(this.audio); if ( this.url ){ this.set_source(this.url); } } + playended(e){ + this.dispatch('ended'); + } + set_stream_urls(response){ + async function* dyn_urls(response) { + const reader = response.body.getReader(); + var value; + var done; + while (true){ + done, value = await reader.read(); + if (value.done){ + console.log('done=', done, 'value=', value); + break; + } + let result = ''; + for (let i = 0; i < value.value.length; i++) { + result += String.fromCharCode(value.value[i]); + } + console.log('audio set url=', result); + yield result; + } + } + this.url_generator = dyn_urls(response); + this.srcList = []; + this.notBegin = true; + schedule_once(this.load_queue_url.bind(this), 0.1); + } + async load_queue_url(){ + while (true){ + var d = await this.url_generator.next(); + if (d.done){ + return; + } + this.srcList.push({played:false, url:d.value}); + if (this.srcList.length < 2 ){ + await this.play_srclist(); + this.audio.addEventListener('ended', + this.play_srclist.bind(this)); + } + } + } + async play_srclist(evnet){ + if (event && ! this.audio.ended){ + return; + } + for (var i=0;i { - if (done) { - // 如果读取完成,关闭流 - controller.close(); - return; - } - // 将数据块放入队列 - controller.enqueue(value); - push(); - }).catch(error => { - console.error('Error reading data', error); - controller.error(error); - }); - } - push(); + var value; + var t = 0; + while (true){ + done, value = await reader.read(); + if (value.done){ + break; } - }) - .then(stream => { - // 将ReadableStream转换为Blob - return new Response(stream).blob(); - }) - .then(blob => { - const audioSrc = widget.set_url(URL.createObjectURL(blob)); - widget.bind('canplay', () => { - widget.play(); - }); - }) - .catch(error => { - console.error('Fetching audio stream failed', error); - }); + let result = ''; + for (let i = 0; i < value.value.length; i++) { + result += String.fromCharCode(value.value[i]); + } + console.log('audio set url=', result); + yield result; + } } diff --git a/bricks/bricks.js b/bricks/bricks.js index ac9b146..334980a 100644 --- a/bricks/bricks.js +++ b/bricks/bricks.js @@ -48,9 +48,20 @@ params: */ bricks.uuid = function(){ - var d = crypto.randomUUID(); - var lst = d.split('-'); - return lst.join(''); + try{ + var d = crypto.randomUUID(); + var lst = d.split('-'); + return lst.join(''); + } catch(e) { + const vv = '1234567890qwertyuiopasdfghjklzxcvbnm'; + var ret = ''; + for (var i=0;i<30;i++){ + var j = parseInt(Math.random() * vv.length); + ret = ret + vv[j]; + } + console.log('uuid() return', ret); + return ret; + } } bricks.deviceid = function(appname){ @@ -124,6 +135,9 @@ bricks.widgetBuild = async function(desc, widget, data){ var klassname = desc.widgettype; var base_url = widget.baseURI; while (klassname == 'urlwidget'){ + if (data){ + desc = bricks.apply_data(desc, data); + } let url = bricks.absurl(desc.options.url, widget); base_url = url; let method = desc.options.method || 'GET'; @@ -137,14 +151,24 @@ bricks.widgetBuild = async function(desc, widget, data){ if (data){ desc = bricks.apply_data(desc, data); } + if (!desc.widgettype){ + console.log('widgettype is null', desc); + return null; + } let klass = bricks.Factory.get(desc.widgettype); if (! klass){ - bricks.debug('widgetBuild():',desc.widgettype, 'not registered', bricks.Factory.widgets_kw); + console.log('widgetBuild():', + desc.widgettype, + 'not registered', + bricks.Factory.widgets_kw); return null; } var options = desc.options || {}; options.baseURI = base_url; let w = new klass(options); + if (! w){ + console.log('w is null'); + } if (desc.id){ w.set_id(desc.id); } @@ -204,14 +228,34 @@ bricks.universal_handler = async function(from_widget, widget, desc, event){ 'desc=', desc, event); } +bricks.default_popup = function(opts){ + var popts = bricks.get_popup_default_options(); + bricks.extend(popts, opts); + return new bricks.Popup(popts); +} +bricks.default_popupwindow = function(opts){ + var popts = bricks.get_popupwindow_default_options(); + bricks.extend(popts, opts); + return new bricks.PopupWindow(popts); +} bricks.buildEventHandler = async function(w, desc, event){ - var target = bricks.getWidgetById(desc.target, w); + var target; + if (desc.target == 'Popup'){ + target = bricks.default_popup(desc.popup_options || {}); + } else if (desc.target == 'PopupWindow') { + target = bricks.default_popupwindow(desc.popup_options || {}); + } else if ( desc.target instanceof bricks.JsWidget ) { + target = desc.target; + } else { + target = bricks.getWidgetById(desc.target, w); + } if (! target){ bricks.debug('target miss desc=', desc, 'w=', w); return null } var rtdata = {}; - + desc.event_params = event.params || {} ; + desc.event = event; if (desc.rtdata) rtdata = desc.rtdata; else if (desc.datawidget){ var data_desc = { @@ -222,9 +266,6 @@ bricks.buildEventHandler = async function(w, desc, event){ } rtdata = await bricks.getRealtimeData(w, data_desc); } - if (typeof event.params == typeof {}){ - rtdata = bricks.extend(rtdata, event.params); - } switch (desc.actiontype){ case 'urlwidget': return bricks.buildUrlwidgetHandler(w, target, rtdata, desc); @@ -270,70 +311,80 @@ bricks.getRealtimeData = async function(w, desc){ return null; } -var _buildWidget = async function(from_widget, target, mode, options){ - bricks.debug('target=', target, 'mode=', mode, 'options=', options); - var w = await (bricks.widgetBuild(options, from_widget)); - if (!w){ - bricks.debug('options=', options, 'widgetBuild() failed'); - return; - } +var _buildWidget = async function(from_widget, target, mode, options, desc){ + bricks.debug('target=', target, 'mode=', mode, 'options=', options); + var w = await (bricks.widgetBuild(options, from_widget)); + if (!w){ + bricks.debug('options=', options, 'widgetBuild() failed'); + return; + } - if (w.parent) return; - - if (mode == 'replace'){ - target.clear_widgets(); - target.add_widget(w); - } else if (mode == 'insert'){ - target.add_widget(w, 0); - } else { - target.add_widget(w); + if (w.parent) { + if (target instanceof bricks.Popup || target instanceof bricks.PopupWindow){ + target.destroy(); } + return; + } + if (target instanceof bricks.Popup || target instanceof bricks.PopupWindow) { + if (! target.parent){ + bricks.app.add_widget(target); + } + console.log('target=', target); + if (desc.popup_options.eventpos){ + target.bind('opened', bricks.relocate_by_eventpos.bind(null, desc.event, target)); + } + } + + if (mode == 'replace'){ + target.clear_widgets(); + target.add_widget(w); + } else if (mode == 'insert'){ + target.add_widget(w, 0); + } else { + target.add_widget(w); + } + if (target instanceof bricks.Popup || target instanceof bricks.PopupWindow) { + target.open(); + } } bricks.buildUrlwidgetHandler = function(w, target, rtdata, desc){ - var f = async function(target, mode, options){ - bricks.debug('target=', target, 'mode=', mode, 'options=', options); - var w = await (bricks.widgetBuild(options, w)); - if (!w){ - bricks.debug('options=', options, 'widgetBuild() failed'); - return; - } - if (mode == 'replace'){ - target.clear_widgets(); - target.add_widget(w); - } else if (mode == 'insert'){ - target.add_widget(w, 0); - } else { - target.add_widget(w); - } - } var options = objcopy(desc.options||{}); var params = options.params || {}; - options = bricks.apply_data(options, rtdata); - options.params = bricks.extend(params, rtdata); + if (desc.event_params instanceof FormData){ + var params = desc.event_params; + for ( const [key, value] of Object.entries(rtdata)){ + params.append(key, value); + } + options = bricks.apply_data(options, rtdata); + for ( const [key, value] of Object.entries(options.params||{})){ + params.append(key, value); + } + options.params = params; + options.method = "POST"; + } else { + rtdata = bricks.extend(rtdata, desc.event_params); + options = bricks.apply_data(options, rtdata); + if (desc.params_mapping){ + rtdata = bricks.map(rtdata, desc.params_mapping.mapping, desc.params_mapping.need_others); + } + options.params = bricks.extend(params, rtdata); + } var opts = { "widgettype":"urlwidget", "options":options } - return _buildWidget.bind(w, target, target, desc.mode || 'replace', opts); + return _buildWidget.bind(null, w, target, desc.mode || 'replace', opts, desc); } bricks.buildBricksHandler = function(w, target, rtdata, desc){ - var f = async function(target, mode, options){ - bricks.debug('target=', target, 'mode=', mode, 'options=', options); - var w = await (bricks.widgetBuild(options, wa)); - if (!w){ - bricks.debug('options=', options, 'widgetBuild() failed'); - return; - } - if (mode == 'replace'){ - target.clear_widgets(); - } - target.add_widget(w); - } var options = objcopy(desc.options||{}); + rtdata = bricks.extend(rtdata, inputdata2dic(desc.event_params)); + if (desc.params_mapping){ + rtdata = bricks.map(rtdata, desc.params_mapping.mapping, desc.params_mapping.need_others); + } options = bricks.apply_data(options, rtdata); - return _buildWidget.bind(w, target, target, desc.mode || 'replace', options); + return _buildWidget.bind(w, target, target, desc.mode || 'replace', options, desc); } bricks.buildRegisterFunctionHandler = function(w, target, rtdata, desc){ var f = bricks.RF.get(desc.rfname); @@ -341,6 +392,9 @@ bricks.buildRegisterFunctionHandler = function(w, target, rtdata, desc){ bricks.debug('rfname:', desc.rfname, 'not registed', desc); return null; } + if (desc.params_mapping){ + rtdata = bricks.map(rtdata, desc.params_mapping.mapping, desc.params_mapping.need_others); + } var params = {}; if (desc.params){ bricks.extend(params, desc.params); @@ -348,6 +402,7 @@ bricks.buildRegisterFunctionHandler = function(w, target, rtdata, desc){ if (rtdata){ bricks.extend(params, rtdata); } + bricks.extend(params, inputdata2dic(desc.event_params)); params = bricks.apply_data(params, rtdata); return f.bind(target, params); } @@ -360,6 +415,10 @@ bricks.buildMethodHandler = function(w, target, rtdata, desc){ var params = {}; bricks.extend(params, desc.params) bricks.extend(params, rtdata); + bricks.extend(params, inputdata2dic(desc.event_params)); + if (desc.params_mapping){ + rtdata = bricks.map(rtdata, desc.params_mapping.mapping, desc.params_mapping.need_others); + } params = bricks.apply_data(params, rtdata); return f.bind(target, params); } @@ -367,6 +426,10 @@ bricks.buildScriptHandler = function(w, target, rtdata, desc){ var params = {}; bricks.extend(params, desc.params) bricks.extend(params, rtdata); + bricks.extend(params, inputdata2dic(desc.event_params)); + if (desc.params_mapping){ + rtdata = bricks.map(rtdata, desc.params_mapping.mapping, desc.params_mapping.need_others); + } params = bricks.apply_data(params, rtdata); var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; var f = new AsyncFunction('params', 'event', desc.script); @@ -380,6 +443,7 @@ bricks.buildDispatchEventHandler = function(w, target, rtdata, desc){ var params = {}; bricks.extend(params, desc.params) bricks.extend(params, rtdata); + bricks.extend(params, inputdata2dic(desc.event_params)); params = bricks.apply_data(params, rtdata); return f.bind(target, desc.dispatch_event, params); } @@ -406,7 +470,7 @@ bricks.getWidgetById = function(id, from_widget){ el = bricks.app.root.dom_element; continue; } - if (ids[i]=='app'){ + if (ids[i]=='app' || ids[i] == 'body'){ return bricks.app; } if (ids[i] == 'window'){ @@ -487,12 +551,38 @@ bricks.App = class extends bricks.Layout { this._Height = this.dom_element.offsetHeight; this.video_stream = null; this.audio_stream = null; + this.video_devices = null + this.vpos = null; document.addEventListener('keydown', this.key_down_action.bind(this)); + this.screen_orient = window.screen.orientation.type; + window.screen.orientation.addEventListener('change', () => { + this.screen_orient = window.screen.orientation.type; + this.bind('orient_changed', this.screen_orient); + }); } - async start_camera() { - if (this.video_stream) return; + async getCameras() { + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + this.video_devices = devices.filter(device => device.kind === 'videoinput'); + } catch (error) { + console.error('获取摄像头数量出错:', error); + } + } + async start_camera(vpos) { + if (typeof(vpos) == 'undefined') vpos = 0; + if (this.video_devices === null){ + await this.getCameras(); + } + if (vpos == this.vpos) return; + this.vpos = vpos; + if (this.video_stream){ + this.video_stream.getTracks().forEach(track => { + track.stop(); + }); + } if (navigator.mediaDevices.getUserMedia) { - this.video_stream = await navigator.mediaDevices.getUserMedia({ video: true }); + var x = { deviceId: this.video_devices[vpos].deviceId }; + this.video_stream = await navigator.mediaDevices.getUserMedia({ video: x }); } else { console.log("Webcam access is not supported in this browser."); } diff --git a/bricks/build.sh b/bricks/build.sh index 2b98cf0..593e317 100755 --- a/bricks/build.sh +++ b/bricks/build.sh @@ -6,9 +6,11 @@ SOURCES=" page_data_loader.js factory.js uitypesdef.js utils.js uitype.js \ tree.js multiple_state_image.js dynamiccolumn.js form.js message.js conform.js \ paging.js datagrid.js iframe.js cols.js echartsext.js \ floaticonbar.js miniform.js wterm.js dynamicaccordion.js \ - binstreaming.js streaming_audio.js vadtext.js rtc.js \ - llm_dialog.js llm.js websocket.js datarow.js tabular.js \ - line.js pie.js bar.js gobang.js " + binstreaming.js streaming_audio.js vadtext.js rtc.js docxviewer.js \ + llm_dialog.js llm.js websocket.js datarow.js tabular.js continueaudio.js \ + line.js pie.js bar.js gobang.js period.js iconbarpage.js \ + keypress.js asr.js webspeech.js countdown.js progressbar.js \ + qaframe.js svg.js " echo ${SOURCES} cat ${SOURCES} > ../dist/bricks.js # uglifyjs --compress --mangle -- ../dist/bricks.js > ../dist/bricks.min.js @@ -38,4 +40,5 @@ cp -a ../examples/* ../dist/examples cp -a ../3parties/* ../dist/3parties cp -a ../docs/* ../dist/docs cp *.tmpl ../dist +cp -a ../dist /tmp echo "Finished ..." diff --git a/bricks/camera.js b/bricks/camera.js index f35ae19..945f506 100644 --- a/bricks/camera.js +++ b/bricks/camera.js @@ -7,42 +7,75 @@ bricks.Camera = class extends bricks.Popup { */ constructor(opts){ opts.fps = opts.fps || 60; + opts.auto_dismiss = false; super(opts); + this.recordedChunks = []; + this.mediaRecorder = null; this.stream = null; this.video = document.createElement('video'); var filler = new bricks.Filler({}); var hbox = new bricks.HBox({ - cheight:2 + cheight:3 }); + this.cur_camera_id = 0; this.add_widget(filler); this.add_widget(hbox); var shot_btn = new bricks.Icon({ url:bricks_resource('imgs/camera.png'), + margin: '10px', + tip:'Take a picture', + rate:2.5 + }); + var switch_btn = new bricks.Icon({ + url:bricks_resource('imgs/switch-camera.png'), + tip:'switch camera', + margin: '10px', rate:1.5 }); var del_btn = new bricks.Icon({ url:bricks_resource('imgs/delete.png'), + tip:'canel it', + margin: '10px', rate:1.5 }) del_btn.bind('click', this.dismiss.bind(this)); shot_btn.bind('click', this.take_picture.bind(this)); + switch_btn.bind('click', this.switch_camera.bind(this, switch_btn)); this.imgw = new bricks.Image({ width:'100%' }); + hbox.add_widget(switch_btn); hbox.add_widget(shot_btn); + hbox.add_widget(new bricks.Filler({})); hbox.add_widget(del_btn); filler.add_widget(this.imgw); this.task_period = 1 / this.fps; - schedule_once(this.startCamera.bind(this), 0.3); + schedule_once(this.startCamera.bind(this), 0.1); } - async startCamera() { - await bricks.app.start_camera(); + async switch_camera(btn, event){ + if (bricks.app.video_devices.length < 2){ + btn.disabled(true); + return; + } + var vpos = bricks.app.vpos; + vpos += 1; + if (vpos >= bricks.app.video_devices.length){ + vpos = 0; + } + this.startCamera(vpos); + } + async startCamera(vpos) { + await bricks.app.start_camera(vpos); this.stream = bricks.app.video_stream; this.video.srcObject = this.stream; this.video.play(); + this.show_cnt = 1; this.task = schedule_once(this.show_picture.bind(this), this.task_period); } show_picture(){ + if (this.task_period == 0){ + return; + } var canvas = document.createElement('canvas'); canvas.height = this.video.videoHeight; canvas.width = this.video.videoWidth; @@ -50,15 +83,51 @@ bricks.Camera = class extends bricks.Popup { context.drawImage(this.video, 0, 0); this.imgw.set_url(canvas.toDataURL('image/jpeg')); this.task = schedule_once(this.show_picture.bind(this), this.task_period); + this.show_cnt += 1; } +<<<<<<< HEAD take_picture(){ if (this.task){ this.task.cancel(); this.task = null; } +======= + videorecorder_start(){ + if (!this.stream) { + throw new Error('Media stream is not initialized. Call init() first.'); + } + this.recordedChunks = []; + this.mediaRecorder = new MediaRecorder(this.stream); + this.mediaRecorder.ondataavailable = (event) => { + if (event.data.size > 0) { + this.recordedChunks.push(event.data); + } + }; + this.mediaRecorder.start(); + } + videorecorder_stop(){ + return new Promise((resolve) => { + this.mediaRecorder.onstop = () => { + const blob = new Blob(this.recordedChunks, { type: 'video/webm' }); + const url = URL.createObjectURL(blob); + resolve({ blob, url }); + }; + this.mediaRecorder.stop(); + this.mediaRecorder = null; + this.recordedChunks = []; + }); + } + take_picture(event){ + event.stopPropagation(); + if (this.task){ + task.cancel(); + this.task = null; + } + this.task_period = 0; + this.task = null; +>>>>>>> 51416a085b5aa99df9f8edf9989fdc1b6306724c var d = this.imgw.base64(); this.dispatch('shot', d); - // Create a blob from the canvas data URL } } diff --git a/bricks/cols.js b/bricks/cols.js index a7c3544..5574c69 100644 --- a/bricks/cols.js +++ b/bricks/cols.js @@ -55,6 +55,7 @@ bricks.Cols = class extends bricks.VBox { } async handle_click(rw, event){ + event.stopPropagation(); var orev = null; if (this.select_record){ orev = this.select_record; @@ -64,6 +65,8 @@ bricks.Cols = class extends bricks.VBox { } this.select_record = rw; this.select_record.set_css('selected_record'); + console.log('record data=', rw.user_data); + this.dispatch('record_click', rw.user_data); } async dataHandle(d){ var data = d.rows; @@ -71,18 +74,20 @@ bricks.Cols = class extends bricks.VBox { if (!data){ return; } - if (! this.loader.is_max_page(page)){ + var rev = ! this.loader.is_max_page(page); + if (rev){ data.reverse(); } for (var i=0;i { + const source = this.audioContext.createBufferSource(); + source.buffer = decodedBuffer; + source.connect(this.gainNode); + + const startTime = Math.max(this.audioContext.currentTime, this.nextStartTime); + source.start(startTime); + + this.nextStartTime = startTime + decodedBuffer.duration; + + if (typeof this.options.onStart === 'function') { + this.options.onStart(); + } + + source.onended = () => { + if (typeof this.options.onEnd === 'function') { + this.options.onEnd(); + } + }; + }).catch(err => { + console.error("Error decoding audio data:", err); + }); + } + + /** + * ⏸ 暂停播放 + */ + pauseAudio() { + if (this.audioContext && this.audioContext.state === 'running') { + this.audioContext.suspend().then(() => { + if (typeof this.options.onPause === 'function') { + this.options.onPause(); + } + }); + } + } + + /** + * ▶️ 恢复播放 + */ + resumeAudio() { + if (this.audioContext && this.audioContext.state === 'suspended') { + this.audioContext.resume().then(() => { + if (typeof this.options.onResume === 'function') { + this.options.onResume(); + } + }); + } + } + + /** + * 🔁 重新开始播放 + */ + restart() { + console.log("Restarting audio playback..."); + if (this.audioContext && this.audioContext.state !== 'closed') { + this.audioContext.close().then(() => { + this.initAudioContext(); + }); + } else { + this.initAudioContext(); + } + } + + /** + * 🔊 设置音量(0.0 - 1.0) + */ + setVolume(value) { + this.volume = Math.max(0, Math.min(1, value)); + if (this.gainNode) { + this.gainNode.gain.value = this.muted ? 0 : this.volume; + } + this.emit('onVolumeChange', this.volume); + } + + /** + * 🔇 切换静音 + */ + toggleMute() { + this.muted = !this.muted; + this.gainNode.gain.value = this.muted ? 0 : this.volume; + this.emit('onVolumeChange', this.muted ? 0 : this.volume); + } + + /** + * 🧩 触发事件回调 + */ + emit(eventName, ...args) { + if (typeof this.options[eventName] === 'function') { + this.options[eventName](...args); + } + } +} + +bricks.Factory.register('ContinueAudioPlayer', bricks.ContinueAudioPlayer); diff --git a/bricks/countdown.js b/bricks/countdown.js new file mode 100644 index 0000000..6d48ea8 --- /dev/null +++ b/bricks/countdown.js @@ -0,0 +1,103 @@ +var bricks = window.bricks || {}; + +bricks.formatTime = function(seconds) { + let hrs = Math.floor(seconds / 3600); + let mins = Math.floor((seconds % 3600) / 60); + let secs = seconds % 60; + + return [ + hrs.toString().padStart(2, '0'), + mins.toString().padStart(2, '0'), + secs.toString().padStart(2, '0') + ].join(':'); +} +bricks.TimePassed = class extends bricks.VBox { + constructor(opts){ + super(opts); + this.seconds = 0; + var t = bricks.formatTime(this.seconds); + this.text_w = new bricks.Text({ + text:this.t, + rate:this.text_rate + }); + this.add_widget(this.text_w); + } + start(){ + this.task = schedule_once(this.add_one_second.bind(this)); + } + + add_one_second(){ + this.seconds += 1; + var t = bricks.formatTime(this.seconds); + this.text_w.set_text(t); + this.task = schedule_once(this.add_one_second.bind(this)); + } + stop(){ + this.task.cancel(); + this.task = null; + } +} +bricks.Countdown = class extends bricks.VBox { + /* + options: + limit_time: 01:00:00 + text_rate: + event: + timeout + timeout event is fired after the countdown time is over. + method: + start + start method is to start the countdown, step is 1 secord + */ + constructor(opts){ + super(opts); + var parts = opts.limit_time.split(':'); + var hours, minutes, seconds; + switch(parts.length){ + case 0: + hours = 0; + minutes = 0; + seconds = 0; + break; + case 1: + hours = 0; + minutes = 0; + seconds = parseInt(parts[0]) + break; + case 2: + hours = 0; + minutes = 0; + seconds = parseInt(parts[0]) + break; + case 3: + default: + hours = parseInt(parts[0]); + minutes = parseInt(parts[1]); + seconds = parseInt(parts[2]) + break; + } + this.seconds = hours * 3600 + minutes * 60 + seconds; + this.text_w = new bricks.Text({ + text:this.limit_time, + rate:this.text_rate + }); + this.add_widget(this.text_w); + } + start(){ + schedule_once(this.time_down_second.bind(this), 1) + } + time_down_second(){ + if (this.seconds < 1){ + this.dispatch('timeout'); + return; + } + var h, m, s; + this.seconds -= 1; + var ts = bricks.formatTime(this.seconds); + this.text_w.set_text(ts); + schedule_once(this.time_down_second.bind(this), 1) + } +} + +bricks.Factory.register('Countdown', bricks.Countdown); +bricks.Factory.register('TimePassed', bricks.TimePassed); diff --git a/bricks/css/bricks.css b/bricks/css/bricks.css index d4406de..7cefd08 100755 --- a/bricks/css/bricks.css +++ b/bricks/css/bricks.css @@ -7,6 +7,10 @@ body { display: flex; } +pre { + overflow-x: auto; /* 允许内容超出容器显示 */ + background-color: #b5e5e5; +} * { box-sizing: border-box!important; } @@ -37,7 +41,7 @@ hr { background-color: #eeeeee; } .clickable { - color: #80ff88; + color: #40cc40; cursor: pointer; } @@ -86,6 +90,10 @@ hr { padding: 4px; } +.titlebar { + background-color: #d8d8c8; +} + .toppopup { box-shadow: 10px 5px 10px #000, 0 -5px 5px #fff; } @@ -414,6 +422,7 @@ hr { margin-left: 5px; margin-right: auto; margin-bottom: 10px; + padding: 3px; background-color:#fefedd; border-top-left-radius: 10px; border-top-right-radius: 0; @@ -436,3 +445,19 @@ hr { .llm_title { background-color:#eeeeee; } +.progress-container { + width: 80%; + background-color: #ddd; + border-radius: 5px; + overflow: hidden; + margin-top: 20px; +} +.progress-bar { + height: 30px; + width: 0%; + background-color: #4CAF50; + text-align: center; + color: white; + line-height: 30px; +} + diff --git a/bricks/datarow.js b/bricks/datarow.js index 8bbd31b..8e9a00e 100644 --- a/bricks/datarow.js +++ b/bricks/datarow.js @@ -29,13 +29,17 @@ bricks.DataRow = class extends bricks.HBox { this.render(false); } render(header){ - this.build_toolbar(header); + // this.build_toolbar(header); if (this.checkField){ var w; if (header){ w = new bricks.BlankIcon({}); } else { - w = new bricks.UiCheck({name:this.checkField,value:this.user_data[this.checkField]}); + var v = 0 + if (this.user_data){ + v = this.user_data[this.checkField]; + } + w = new bricks.UiCheck({name:this.checkField,value:v}); w.bind('changed', this.get_check_state.bind(this)); } this.add_widget(w); diff --git a/bricks/dataviewer.js b/bricks/dataviewer.js index d8a9e89..8f10b9b 100644 --- a/bricks/dataviewer.js +++ b/bricks/dataviewer.js @@ -138,7 +138,7 @@ bricks.DataViewer = class extends bricks.VBox { } if (this.toolbar){ this.toolbar.tools.forEach(t => { - if (! edit_names.incloudes(t.name)){ + if (! edit_names.includes(t.name)){ tbdesc.tools.push(t); } }); @@ -168,12 +168,13 @@ bricks.DataViewer = class extends bricks.VBox { this.delete_record(this.select_row); return; } - var d = { - tabular:this, - row:this.select_row, - data:row.user_data + var data = null; + if (this.select_row){ + var r = this.select_row; + var data = r.user_data; } - this.dispathc(t.name. d); + console.log(tdesc.name, 'clicked ==================', tdesc.name, data) + this.dispatch(tdesc.name, data); } get_edit_fields(){ var fs = this.row_options.fields; @@ -193,7 +194,8 @@ bricks.DataViewer = class extends bricks.VBox { this.dispatch('row_check_changed', event.params.user_data); } async renew_record_view(form, row){ - var d = form.getValue(); + var d = form._getValue(); + d = form._getValue(); var record = bricks.extend(row.user_data, d); row.renew(record); } @@ -324,8 +326,8 @@ bricks.DataViewer = class extends bricks.VBox { var d = await this.loader.loadNextPage(); if (d){ this.dataHandle(d); - var total = this.scrollpanel.dom_element.scrollHeight - this.scrollpanel.dom_element.clientHeight; - this.scrollpanel.dom_element.scrollTop = d.pos_rate * total; + // var total = this.scrollpanel.dom_element.scrollHeight - this.scrollpanel.dom_element.clientHeight; + // this.scrollpanel.dom_element.scrollTop = d.pos_rate * total; } else { bricks.debug(this.loader, 'load next page error'); } diff --git a/bricks/docxviewer.js b/bricks/docxviewer.js new file mode 100644 index 0000000..d990187 --- /dev/null +++ b/bricks/docxviewer.js @@ -0,0 +1,119 @@ +/* need mammoth module + +*/ +var bricks = window.bricks || {}; + +bricks.DOCXviewer = class extends bricks.VBox { + /* + url: + */ + constructor(opts){ + super(opts); + this.bind('on_parent', this.set_url.bind(this)); + // schedule_once(this.set_url.bind(this, this.url), 0.2); + } + async set_url(url){ + var container = this.dom_element + var hab = new bricks.HttpArrayBuffer(); + var ab = await hab.get(this.url); + var result = await mammoth.convertToHtml({ arrayBuffer: ab }); + container.innerHTML = result.value; + } +} + +function extractBodyContent(htmlString) { + // 正则表达式匹配和之间的内容 + const regex = /]*>([\s\S]*?)<\/body>/i; + const matches = htmlString.match(regex); + return matches ? matches[1] : null; // 如果匹配到,返回匹配的内容,否则返回null +} + +bricks.EXCELviewer = class extends bricks.VBox { + constructor(opts){ + opts.height = "100%", + super(opts); + this.sheets_w = new bricks.HBox({cheight:3, width:'100%'}); + this.sheets_w.set_css('scroll'); + this.cur_sheetname = null; + this.container = new bricks.Filler({}); + this.add_widget(this.container); + this.add_widget(this.sheets_w); + this.bind('on_parent', this.set_url.bind(this)); + } + async set_url(url){ + this.sheets_w.clear_widgets(); + var hab = new bricks.HttpArrayBuffer(); + var ab = await hab.get(this.url); + const data = new Uint8Array(ab); + this.workbook = XLSX.read(data, {type: 'array'}); + this.workbook.SheetNames.forEach((sheetname, index) => { + var w = new bricks.Text({text:sheetname, wrap:false}); + w.set_css('clickable'); + w.set_style('padding', '10px'); + w.bind('click', this.show_sheet_by_name.bind(this, sheetname, w)); + this.sheets_w.add_widget(w); + if (index==0){ + this.show_sheet_by_name(this.workbook.SheetNames[0], w); + } + }); + } + show_sheet_by_name(sheetname, tw){ + if (this.cur_sheetname == sheetname) return; + this.sheets_w.children.forEach(c => c.set_css('selected', true)); + tw.set_css('selected'); + const x = new bricks.VScrollPanel({width: '100%', height: '100%'}); + const sheet = this.workbook.Sheets[sheetname]; + // const html = extractBodyContent(XLSX.utils.sheet_to_html(sheet)); + const html = XLSX.utils.sheet_to_html(sheet); + x.dom_element.innerHTML = html; + this.container.clear_widgets(); + this.container.add_widget(x); + this.cur_sheetname = sheetname; + } +} + +bricks.PDFviewer = class extends bricks.VBox { + /* + url: + */ + constructor(opts){ + opts.width = '100%'; + super(opts); + this.bind('on_parent', this.set_url.bind(this)); + } + async set_url(url){ + var container = this.dom_element + var hab = new bricks.HttpArrayBuffer(); + var ab = await hab.get(this.url); + const task = pdfjsLib.getDocument({ data: ab }); + task.promise.then((pdf) => { + this.pdf = pdf; + for (let i = 1; i <= this.pdf.numPages; i++) { + this.pdf.getPage(i).then((page) => { + this.add_page_content(page); + }); + } + }).catch((err) => { + console.log('error'); + }) + } + add_page_content(page){ + const scale = 1.5; + const viewport = page.getViewport({ scale }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; + page.render({ canvasContext: context, viewport }); + var w = new bricks.JsWidget(); + w.dom_element.appendChild(canvas); + this.add_widget(w); + if (i < this.pdf.numPages){ + w = new bricks.Splitter(); + this.add_widget(w) + } + } +} +bricks.Factory.register('DOCXviewer', bricks.DOCXviewer); +bricks.Factory.register('EXCELviewer', bricks.EXCELviewer); +bricks.Factory.register('PDFviewer', bricks.PDFviewer); diff --git a/bricks/dynamiccolumn.js b/bricks/dynamiccolumn.js index 8d7945b..569ecfa 100644 --- a/bricks/dynamiccolumn.js +++ b/bricks/dynamiccolumn.js @@ -36,6 +36,7 @@ bricks.DynamicColumn = class extends bricks.Layout { if (bricks.is_mobile() && width < height){ cols = this.mobile_cols; cw = (width - (cols - 1) * gap) / cols; + console.log('====mobile==cols=', cols, '===='); } else { if (this.col_cwidth){ cw = bricks.app.charsize * this.col_cwidth; diff --git a/bricks/form.js b/bricks/form.js index d245c94..c2e62c5 100644 --- a/bricks/form.js +++ b/bricks/form.js @@ -80,7 +80,7 @@ bricks.FormBody = class extends bricks.VScrollPanel { this.form = form; this.name_inputs = {}; this.fg = new bricks.FieldGroup({}); - this.fg.build_fields(form, this, form.opts.fields) + this.fg.build_fields(form, this, form.nontextfields) } create(){ this.dom_element = this._create('form'); @@ -176,7 +176,7 @@ bricks.FormBase = class extends bricks.Layout { w.reset(); } } - getValue(){ + _getValue(){ var data = {}; for (var name in this.name_inputs){ if (! this.name_inputs.hasOwnProperty(name)){ @@ -195,6 +195,14 @@ bricks.FormBase = class extends bricks.Layout { } return data; } + getValue(){ + if (this.data) { + var ret = this.data; + this.data = null; + return ret; + } + return this.get_formdata(); + } get_formdata(){ var data = new FormData(); for (var name in this.name_inputs){ @@ -204,37 +212,35 @@ bricks.FormBase = class extends bricks.Layout { var w = this.name_inputs[name]; var d = w.getValue(); if (w.required && ( d[name] == '' || d[name] === null)){ - bricks.debug('data=', data, 'd=', d); new bricks.Error({title:'Requirement', message:'required field must input"' + w.label + '"'}) w.focus(); return; } - if (bricks.need_formdata_fields.includes(w.uitype)){ - var files = w.get_files(); - for (var i=0;i { + if (f.uitype == 'text'){ + this.textfields.push(f); + } else { + this.nontextfields.push(f); + } + }); this.body = new bricks.FormBody(this, opts); - filler.add_widget(this.body); + if (this.textfields.length > 0){ + var tp; + var tp_options = { + height: "100%", + tab_pos: "top", + items:[ + { + "name":"form", + "label":"Form", + "content":this.body + } + ] + }; + this.textfields.forEach((f) => { + var txtw = new bricks.UiText({ + name:f.name, + width:"100%", + height:"100%", + value:f.value + }); + txtw.bind('active', txtw.focus.bind(txtw)); + tp_options.items.push({ + name:f.name, + label:f.label, + content:txtw + }); + this.name_inputs[f.name] = txtw; + }); + tp = new bricks.TabPanel(tp_options); + filler.add_widget(tp); + } else { + filler.add_widget(this.body); + } if (! opts.notoolbar) this.build_toolbar(this); } diff --git a/bricks/header.tmpl b/bricks/header.tmpl index ba0a53d..afdf312 100644 --- a/bricks/header.tmpl +++ b/bricks/header.tmpl @@ -4,31 +4,37 @@ - - - - + + + + - - - + + + + - - - + + +