var bricks = window.bricks || {}; bricks.UiType =class extends bricks.Layout { constructor(opts){ super(opts); this.name = this.opts.name; this.required = opts.required || false; this.ctype = 'text'; this.value = opts.value || opts.defaultvalue||''; } getValue(){ var o = {} o[this.name] = this.resultValue(); var d = this.getUserData(); if (d){ o = bricks.extend(o, d); } return o; } set_formdata(formdata){ formdata.append(this.name, this.resultValue()); } resultValue(){ return this.value; } focus(){ this.dom_element.focus(); } reset(){ var v = this.opts.value||this.opts.defaultvalue|| ''; this.setValue(v); } setValue(v){ if (! v) v = ''; this.vlaue = v; this.dom_element.value = v; } set_disabled(f){ this.dom_element.disabled = f; } set_readonly(f){ this.dom_element.readOnly = f; } set_required(f){ this.dom_element.required = f; this.required = f; } } bricks.UiHide = class extends bricks.UiType { constructor(opts){ super(opts); this.uitype = 'hide'; this.set_style('display', 'none'); } } bricks.UiStr =class extends bricks.UiType { /* { name: value: defaultValue: align:"left", "center", "right" length: minlength: tip: width: readonly: required: } */ constructor(opts){ super(opts); this.uitype='str'; this.rate = this.opts.rate || 1; this.dynsize = this.opts.dynsize || true; this.cfontsize = this.opts.cfontsize || 1; if (opts.readonly) { this.set_readonly("Y"); } else { this.set_readonly(false); } if (opts.width){ this.dom_element.style.width = opts.width; } this.charsize_sizing(); } create(){ var el = this._create('input'); this.dom_element = el; this.pattern = '.*'; el.autocomplete = 'off'; el.type = 'text'; el.id = this.name = el.name = this.opts.name; if (this.opts.required) el.required = true; if (this.opts.css){ el.classList.add(this.opts.css); this.actived_css = this.opts.css + '-actived'; } else { el.classList.add('input'); this.actived_css = 'input_actived'; } el.style.textAlign = this.opts.align || 'left'; if (this.opts.hasOwnProperty('length')) el.maxlength = this.opts.length; if (this.opts.hasOwnProperty('minlength')) el.minlength = this.opts.minlength; if (this.opts.hasOwnProperty('value')) this.setValue(this.opts.value); if (this.opts.defaultVlaue) el.defaultValue = this.opts.defaultValue; this.reset() if (this.opts.placeholder) el.placeholder = bricks.app.i18n._(this.opts.placeholder); el.addEventListener('focus', this.onfocus.bind(this)); el.addEventListener('onkeydown', this.onkeydown.bind(this)); el.addEventListener('blur', this.onblur.bind(this)); el.addEventListener('input', this.set_value_from_input.bind(this)) } onblur(event){ this.dom_element.classList.remove(this.actived_css); this.value = this.dom_element.value; this.set_value_from_input(event); } onfocus(event){ this.dom_element.classList.add(this.actived_css); } onkeydown(event){ if(event.key == 'Enter'){ var v = this.getValue(); this.dispatch('blur', v) } } check_pattern(value){ var r = new RegExp(this.pattern); var v = value.match(r); if (! v || v[0] == ''){ return null; } return v[0]; } set_value_from_input(event){ var oldv = this.value; var changed = false; var e = event.target; var v = e.value; if (e.value!='' && e.type != 'file'){ v = this.check_pattern(e.value); } if (v == null){ e.value = this.value; return } this.value = v; var o = this.getValue(); this.dispatch('changed', o); } resultValue(){ this.value = this.dom_element.value; return this.value; } setValue(v){ if (! v) v = ''; this.value = v; this.dom_element.value = '' + this.value; } } bricks.UiSearch = class extends bricks.HBox { /* search_url: valueField textField select_event popup_options value, text */ constructor(opts){ super(opts); this.uitype = 'search'; var inputdesc = { name:this.name, value:this.text, readonly:true } this.str_in = new bricks.UiStr(inputdesc); this.search_icon = new bricks.Svg({ url:bricks_resource('imgs/search.svg'), rate:1.5 }); this.str_in.set_css('filler'); this.search_icon.set_css('clickable'); this.search_icon.bind('click', this.open_search_window.bind(this)); this.add_widget(this.str_in); this.add_widget(this.search_icon); } async open_search_window(event){ var desc = { "widgettype":"urlwidget", "options":{ "url":this.search_url } } var w = await bricks.widgetBuild(desc, this); var tabular = null; for (var i=0;i { event.preventDefault(); }); ["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => { this.bind(eventName, (e) => e.preventDefault(), false); }); this.bind('dragenter', () => { this.set_css('hover'); }); this.bind('dragleave', () => { this.set_css('hover', true); }); this.bind('drop', this.dropHandle.bind(this)); this.input = document.createElement('input'); this.input.type = 'file'; if (opts.accept) this.input.accept = opts.accept; if (opts.multiple) this.input.multiple = true; this.input.addEventListener('change', this.handleFileSelect.bind(this)); this.add_widget(new bricks.Text({text:'drop in or click to choose file'})); this.dom_element.appendChild(this.input); } reset(){ var v = this.opts.value || this.opts.defaultvalue||''; this.value = ''; this.input.value = ''; } handleFileSelect(event){ var files = []; Array.from(event.target.files).forEach(f => { if (! this.accept || f.type.startsWith(this.accept)){ files.push(f); } }); if (files.length == 0) return; if (this.opts.multiple){ this.value = files; } else { this.value = files[0]; } console.log('"changed" fired', this.value); this.dispatch('changed', this.getValue()); } set_input_file(files){ const dt = new DataTransfer(); if (this.opts.multiple){ this.value = []; files.forEach(f => { dt.items.add(f); this.value.push(f); }); this.input.files = dt.files; } else { var f = files[0] this.value = f; dt.items.add(f); this.input.files = dt.files; } } dropHandle(event){ event.preventDefault(); var files = []; for (const f of event.dataTransfer.files) { if (! this.opts.accept || f.type.startsWith(this.opts.accept)){ files.push(f); } }; if (files.length == 0) return; if (this.opts.multiple){ this.value = files; this.set_input_file(files); } else { this.value = files[0]; this.set_input_file([this.value]); } this.dispatch('changed', this.getValue()); console.log('"changed" fired', this.getValue()); } set_formdata(fd){ fd.append(this.name, this.resultValue()); } resultValue(){ if (this.value){ return this.value; } if (this.input.files.length) return this.input.files[0]; return null; } getValue(){ var ret = {}; ret[this.name] = this.resultValue(); return ret; } } bricks.UiAudio =class extends bricks.UiFile { constructor(opts){ opts.name = opts.name || 'audio_file'; opts.width = opts.width || '100%'; opts.accept = 'audio/'; super(opts); this.uitype='audio'; this.camera_w = new bricks.Svg({ url:bricks_resource('imgs/mic.svg'), tip:'use mic to record audio', rate:2}); this.add_widget(this.camera_w); this.camera_w.bind('click', this.open_recorder.bind(this)); this.preview = new bricks.VBox({width: '100%'}); this.add_widget(this.preview); this.bind('changed', this.show_audio.bind(this)); } open_recorder(event){ event.stopPropagation(); var recorder = new bricks.SysAudioRecorder({ "archor":"cc", "auto_open":true, "cheight":3, "cwidth":20 }); recorder.bring_to_top(); recorder.bind('record_end', this.accept_audio.bind(this, recorder)); } accept_audio(recorder, event){ recorder.dismiss(); this.value = event.params.file this.set_input_file([this.value]); console.log('record finished, value=', this.value); this.dispatch('changed', this.getValue()); } show_audio(event){ var params = this.value; if (params instanceof File){ params = [ params ]; } if (typeof params == 'string'){ params = [ params ]; } this.preview.clear_widgets(); params.forEach( f => { this._show_audio(f); }); } _show_audio(file) { if (typeof file == 'string'){ var vw = new bricks.AudioPlayer({ url:file, audoplay: true, width:'100%' }); this.preview.add_widget(vw); return; } const reader = new FileReader(); reader.onload = (e) => { var imgw = new bricks.AudioPlayer({ url:e.target.result, autoplay: true, width:'100%' }); console.log('show audio', e.target.result); this.preview.add_widget(imgw); }; reader.readAsDataURL(file); } } bricks.UiVideo =class extends bricks.UiFile { constructor(opts){ opts.name = opts.name || 'video_file'; opts.width = opts.width || '100%'; opts.accept = 'video/'; super(opts); this.uitype='video'; this.camera_w = new bricks.Svg({ url:bricks_resource('imgs/video-recorder.svg'), tip:'use cemera to record video', rate:2}); this.set_css('droparea'); this.add_widget(this.camera_w); this.camera_w.bind('click', this.open_recorder.bind(this)); this.preview = new bricks.VBox({width: '100%'}); this.add_widget(this.preview); this.bind('changed', this.show_video.bind(this)); } open_recorder(event){ event.stopPropagation(); var recorder = new bricks.SysVideoRecorder({ "archor":"cc", "auto_open":true, "height":"90%", "width":"90%" }); recorder.bring_to_top(); recorder.bind('record_end', this.accept_video.bind(this, recorder)); } accept_video(recorder, event){ recorder.dismiss(); this.value = event.params.file this.set_input_file([this.value]); console.log('record finished, value=', this.value); this.dispatch('changed', this.getValue()); } show_video(event){ var params = this.value; console.log('params=', params); if (params instanceof File){ params = [ params ]; } if (typeof params == 'string'){ params = [ params ]; } this.preview.clear_widgets(); params.forEach( f => { this._show_video(f); }); } _show_video(file) { if (typeof file == 'string'){ var vw = new bricks.VideoPlayer({ url:file, audoplay: true, width:'100%' }); this.preview.add_widget(vw); return; } var url = URL.createObjectURL(file); var imgw = new bricks.VideoPlayer({ url:url, autoplay: true, width:'100%' }); console.log('show video', url); this.preview.add_widget(imgw); } } bricks.UiImage =class extends bricks.UiFile { constructor(opts){ opts.name = opts.name || 'image'; opts.width = opts.width || '100%'; opts.accept = 'image/'; super(opts); this.uitype='image'; this.camera_w = new bricks.Svg({ url:bricks_resource('imgs/camera.svg'), tip:'use cemera to take a picture', rate:2}); this.set_css('droparea'); this.add_widget(this.camera_w); this.camera_w.bind('click', this.take_photo.bind(this)); this.preview = new bricks.VBox({width: '100%'}); this.add_widget(this.preview); this.bind('changed', this.show_image.bind(this)); } take_photo(event){ event.stopPropagation(); var camera = new bricks.SysCamera({ "archor":"cc", "auto_open":true, "height":"90%", "width":"90%" }); camera.bring_to_top(); camera.bind('shot', this.accept_photo.bind(this, camera)); } accept_photo(camera, event){ camera.dismiss(); this.value = event.params.file; this.set_input_file([this.value]); var data = {}; data[this.name] = this.value; this.dispatch('changed', data); } show_image(event){ var params = event.params[this.name]; if (params instanceof File){ params = [ params ]; } if (typeof params == 'string'){ params = [ params ]; } this.preview.clear_widgets(); params.forEach( f => { this._show_image(f); }); } _show_image(file) { if (typeof file == 'string'){ var imgw = new bricks.Image({ url:file, width:'100%' }); this.preview.add_widget(imgw); } const reader = new FileReader(); reader.onload = (e) => { var imgw = new bricks.Image({ url:e.target.result, width:'100%' }); this.preview.add_widget(imgw); }; reader.readAsDataURL(file); } } bricks.UiCheck =class extends bricks.UiType { constructor(opts){ super(opts); this.uitype = 'check'; bricks.extend(bricks.UiCheck.prototype, bricks.Layout.prototype); this.add_widget = bricks.Layout.prototype.add_widget.bind(this); this.dom_element.style.width = 'auto'; this.dom_element.style.height = 'auto'; var state = 'unchecked'; if (opts.value){ state = 'checked'; } this.ms_icon = new bricks.MultipleStateIcon({ state:state, urls:{ checked:bricks_resource('imgs/checkbox-checked.svg'), unchecked:bricks_resource('imgs/checkbox-unchecked.svg') } }); this.add_widget(this.ms_icon) this.ms_icon.bind('state_changed', this.set_value_from_input.bind(this)); } set_value_from_input(e){ var v; if (this.ms_icon.state=='checked') v = true; else v = false; this.value = v; var o = {}; o[this.name] = this.value; var d = this.getUserData(); if (d){ o = bricks.extend(o, d); } this.dispatch('changed', o); } setValue(v){ this.value = v; if (v) this.ms_icon.set_state('checked'); else this.ms_icon.set_state('unchecked'); } resultValue(){ return this.value; } } bricks.UiCheckBox =class extends bricks.UiType { /* { name: label: value: textField:'gg', valueField:'hh', otherField:'b', data:[ { 'gg': 'hh': 'b': } ] or: dataurl: params:{}, method: } */ constructor(opts){ super(opts); this.uitype='checkbox'; this.valueField = opts.valueField || 'value'; this.textField = opts.textField || 'text'; this.value = this.opts.value || this.opts.defaultValue||[]; if (! Array.isArray(this.value)){ this.value = [ this.value ]; } this.el_legend = this._create('legend'); var label = this.opts.label||this.opts.name; this.el_legend.innerText = bricks.app.i18n._(label); if (this.opts.dataurl){ schedule_once(this.load_data_onfly.bind(this), 0.01); } else { this.data = opts.data; this.build_checkboxs(); } this.sizable_elements = []; this.sizable_elements.push(this.el_legend); this.charsize_sizing(); } create(){ this.dom_element = this._create('fieldset'); } build_checkboxs(){ var data = this.data; this.input_boxs = []; if (this.multicheck){ if (typeof this.value != typeof []){ this.value = [this.value]; } } for (var i=0; i= 0){ opts.value = true; } } else { if (this.value == value){ opts.value = true } } opts.name = value; var check = new bricks.UiCheck(opts); var otext = data[i][this.textField]; var txt = new bricks.Text({ otext:otext, align:'left', i18n:true}); txt.ht_left(); check.bind('changed', this.set_value_from_input.bind(this)); hbox.add_widget(check); hbox.add_widget(txt); this.add_widget(hbox); this.input_boxs.push(check); } } async load_data_onfly(){ var jc = new bricks.HttpJson(); var data = await jc.httpcall(this.opts.dataurl, { "method":this.opts.method||'GET', "params":this.opts.params}); this.data = data; this.build_checkboxs(); } set_value_from_input(event){ event.stopPropagation(); var e = event.target.bricks_widget; if (this.multicheck){ if (e.value){ this.value.push(e.name); } else { this.value.remove(e.name) } } else { for (var i=0;i= maxHeight) { // 达到或超过最大高度 el.style.height = maxHeight + 'px'; el.style.overflowY = 'auto'; } else { // 低于最大高度 el.style.height = Math.max(targetHeight, minHeight) + 'px'; el.style.overflowY = 'hidden'; } // 5. 恢复滚动(解决 Chrome/Safari 偶尔的置顶 Bug) el.scrollTop = scrollTop; } create(){ this.dom_element = this._create('textarea'); } build(){ var e = this.dom_element; e.id = e.name = this.opts.name; this.reset(); this.bind('input', this.set_value_from_input.bind(this)) } set_value_from_input(event){ this.value = this.dom_element.value; } resultValue(){ var e = this.dom_element; this.value = e.value; return this.value; } setValue(v){ if (! v) v = ''; this.value = v; this.dom_element.value = v; debug('UiText: v=', v); this.handleInput(); } reset(){ var v = this.opts.value || this.opts.defaultvalue||''; this.setValue(v); } key_handle(e){ switch(e.key){ case 'Tab': e.preventDefault(); // 阻止默认Tab行为 var erase = false if (e.shiftKey) erase = true; this.handle_tab_indent(erase); break; case 'Enter': e.preventDefault(); // 阻止默认行为 this.handle_enter(); break; case 'ArrowUp': case 'ArrowDown': default: break; } } set_editor_focus(editor, pos){ editor.selectionStart = editor.selectionEnd = pos; editor.focus(); } handle_enter(){ var editor = this.dom_element; const cursorPos = editor.selectionStart; const value = editor.value; const before = value.substring(0, cursorPos); const after = value.substring(cursorPos); editor.value = before + '\n' + after; editor.selectionStart = editor.selectionEnd = cursorPos + 1; editor.focus(); schedule_once(this.set_editor_focus.bind(this, editor, cursorPos+1), 0.5); } handle_tab_indent(erase){ /* erase == true : delete tabspace else add tabspace */ var editor = this.dom_element; var indent_cnt; const cursorPos = editor.selectionStart; const value = editor.value; // 获取光标所在行的开始位置 const beforeCursor = value.substring(0, cursorPos); const lastLineStart = beforeCursor.lastIndexOf("\n") + 1; // 上一行的结束位置 const currentLineStart = lastLineStart; // 当前行的开始位置 const cur_col = cursorPos - currentLineStart; var pos; if (!erase){ indent_cnt = 4 - cur_col % 4; if (indent_cnt == 0) indent_cnt = 4; // 根据光标前的空格数动态插入合适数量的空格 const before = value.substring(0, cursorPos); const after = value.substring(cursorPos); const indentation = ' '.repeat(indent_cnt); // 生成合适数量的空格 editor.value = before + indentation + after; // 更新光标位置(将光标移到插入的缩进后面) pos = cursorPos + indent_cnt; editor.selectionStart = editor.selectionEnd = cursorPos + indent_cnt; } else { indent_cnt = cur_col % 4; if (indent_cnt == 0) indent_cnt = 4; const before = value.substring(0, cursorPos - indent_cnt); const after = value.substring(cursorPos); editor.value = before + after; pos = cursorPos - indent_cnt; editor.selectionStart = editor.selectionEnd = cursorPos - indent_cnt; } editor.focus(); schedule_once(this.set_editor_focus.bind(this, editor, pos), 0.5); } } bricks.UiCode =class extends bricks.UiType { /* { name: value: valueField: nullable: textField: defaultValue: readonly: required: data: dataurl: params: method: } */ constructor(opts){ opts.cfontsize = opts.cfontsize||1; opts.dynsize = opts.dynsize || true; super(opts); this.uitype='code'; this.data = this.opts.data; this.build(); } create(){ this.dom_element = this._create('select'); } build(){ this.dom_element.id = this.opts.name; this.dom_element.name = this.opts.name; if (this.opts.dataurl){ schedule_once(this.get_data.bind(this), 0.01); return; } this.build_options(this.opts.data); } async loadData(params){ var _params = bricks.extend({}, this.opts.params); bricks.extend(_params, params); await this.load_data(_params); } async get_data(event){ var params = this.opts.params; if(event){ bricks.extend(params, event.params); } await this.load_data(params); } async load_data(params){ var jc = new bricks.HttpJson(); var d = await jc.httpcall(this.opts.dataurl, { method:this.opts.method || 'GET', params : params }); this.data = d; this.build_options(d); } build_options(data){ var e = this.dom_element; while(e.firstChild){ e.removeChild(e.firstChild); } var v = this.opts.value || this.opts.defaultvalue || null; if (!v && ! this.opts.nullable && data.length > 0){ v = data[0][this.opts.valueField] } if (this.opts.nullable){ var tmp = {}; tmp[this.opts.valueField] = null; tmp[this.opts.textField] = ''; data.unshift(tmp); } this.value = v; e.value = v; this.option_widgets = {}; for (var i=0; i