import { computed, onUnmounted, ref, watch } from "vue"; import { ComponentModel, dbRemote, structure } from "./cykLang"; import { ComponentModelParameter, componentModelParameter } from "./cykReact"; import { useI18n } from "vue-i18n"; import loglevel from 'loglevel' import { BufferData, DBColumn, ObjectData, PrimitiveData, Scope, Tag, Variable, variable2json, Variables } from "@cyklang/core"; const logger = loglevel.getLogger('cykFile.ts') logger.setLevel('debug') /** * * @param bytes * @returns */ function formatFileSize(bytes: number) { if (!bytes) return '0 octets'; const k = 1024; const sizes = ['octets', 'ko', 'Mo', 'Go', 'To']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } export function useCykFile(props: { componentArg: ComponentModel | undefined, readonly: Boolean | undefined }) { const { t } = useI18n() const isLoading = ref(true) const isImage = ref(false) const cpLocked = new ComponentModelParameter(props.componentArg, "locked", false) const locked = cpLocked.model const disable = ref(false) const visible = componentModelParameter(props.componentArg, "visible", props.componentArg?.objectData?.tag.attributes.VISIBLE || true) const label = componentModelParameter(props.componentArg, "label", structure.scope.xlate(props.componentArg?.objectData?.tag.attributes.LABEL)) const tooltip = componentModelParameter(props.componentArg, "tooltip", structure.scope.xlate(props.componentArg?.objectData?.tag.attributes.TOOLTIP)) const table_name = componentModelParameter(props.componentArg, "table_name", props.componentArg?.objectData?.tag.attributes.TABLE_NAME) const field_name = componentModelParameter(props.componentArg, "field_name", props.componentArg?.objectData?.tag.attributes.FIELD_NAME) const key = componentModelParameter(props.componentArg, "key", props.componentArg?.objectData?.tag.attributes.KEY) const on_change = props.componentArg?.objectData?.variables.getFunction('onchange') const controlUploadable = (): boolean => { let result = false; if (table_name.value && field_name.value && key.value !== null && key.value !== undefined && key.value !== '') result = true if (props.readonly) result = false return result; }; const uploadable = ref(false) const mimetype = ref('') const fileicon = ref('') const description = computed({ get() { let result = ''; if (props.componentArg && props.componentArg.model && props.componentArg.model.data && props.componentArg.model.data.type.isObject()) { const filename = (props.componentArg.model.data as ObjectData).variables.getString('filename') const size = Number((props.componentArg.model.data as ObjectData).variables.getString('size')) result = `${t('filename')} : ${filename}, ${t('size')} : ${formatFileSize(size)} ` } else { result = "???" } return result }, set() { throw 'cykFile.mimetype.setter not supported' } }) const uploadFile = async (file: File) => { logger.debug('uploadFile file :', file, ', key = ', key.value, ', filename = ', file.name) const form = new FormData() form.append('uploadFile', file) const route = '/api/file/' + table_name.value + '/' + encodeURIComponent(key.value) + '/' + field_name.value; const url = new URL(route, 'https://www.example.com'); try { logger.debug("upload file url", url.pathname + url.search); const metadata = await dbRemote.apiServer.post( url.pathname + url.search, form ); logger.info("-> file uploaded"); const objData = structure.scope.js2Data(metadata); if (props.componentArg?.model !== undefined) { logger.debug("debut***"); props.componentArg.model.setValue(objData); props.componentArg.dirtyManager?.variableChanged( props.componentArg.model ); logger.debug("variableChanged to ", metadata); } } catch (err) { logger.error(err) alert(err) } } const removeFile = () => { if (confirm(t('confirm_delete_file'))) { (async () => { try { await dbRemote.apiServer.delete('/api/file/' + table_name.value + '/' + encodeURIComponent(key.value) + '/' + field_name.value) if (on_change) { if (!props.componentArg?.objectData) throw `props.componentArg.objectData is undefined` await on_change.callFunction( new Variables(), props.componentArg.objectData.scope ) } if (props.componentArg === undefined || props.componentArg.model === undefined) return props.componentArg.model.setValue(null) props.componentArg.dirtyManager?.variableChanged(props.componentArg.model) } catch (err) { alert(String(err)) } })() } } /** * * @returns */ const doLoading = async () => { try { if (!props.componentArg) return await props.componentArg.interpolateAttributes() uploadable.value = controlUploadable() if (props.componentArg.model?.data) { if (!props.componentArg.model.data.type.isObject()) throw `model is not object but is ${props.componentArg.model.data.type.name}` const metaObject = props.componentArg.model.data as ObjectData logger.debug('metaObject', metaObject) mimetype.value = metaObject.variables.getString('mimetype') || '' if (mimetype.value.startsWith('image/')) { fileicon.value = "image" isImage.value = true } else if (mimetype.value === 'application/pdf') { fileicon.value = "picture_as_pdf" } else if (mimetype.value.startsWith('text/')) { fileicon.value = "description" } else if (mimetype.value) { fileicon.value = "attach_file" } } else { throw `no model` } if (props.readonly || locked.value) { disable.value = true } else { disable.value = false } } catch (err) { logger.error(err) } finally { isLoading.value = false } } doLoading() watch([locked], () => { isLoading.value = true doLoading() }) const file = ref(); const model = computed({ get() { return file.value }, set(nval: File | undefined) { (async () => { try { logger.debug('FileComponent.model.set nval: ', nval) file.value = nval if (file.value) { if (controlUploadable()) { await uploadFile(file.value) } else if (props.componentArg?.model) { await setModelValue(props.componentArg, file.value) } } // on_change if (on_change) { if (!props.componentArg?.objectData) throw "objectData undefined"; // alert('before onclick.value.callFunction'); await on_change.callFunction( new Variables(), props.componentArg.objectData?.scope ); // alert('after onclick.value.callFunction'); } } catch (err) { logger.error(err) alert(err) } })() } }) /** * * @param componentArg * @param file */ const setModelValue = async (componentArg: ComponentModel, file: File) => { const variable = componentArg.model if (!variable) throw `setModelValue() model undefined` const dataTypeName = variable.dataType.name let ndata if (dataTypeName === 'string') { const content = await file.text() ndata = new PrimitiveData(structure.stringDataType, content) } else if (dataTypeName === 'buffer') { const content = await file.arrayBuffer() ndata = new BufferData(structure.bufferDataType); ndata.buffer = Buffer.from(content) } else if (variable.dataType.isObject()) { const content = await file.arrayBuffer() ndata = new ObjectData(structure.objectDataType, new Tag(''), structure.scope) const objectMeta = new ObjectData(structure.objectDataType, new Tag(''), structure.scope) ndata.variables.setVariable('meta', new Variable(structure.objectDataType, objectMeta)) objectMeta.variables.setVariable('mimetype', new Variable(structure.stringDataType, new PrimitiveData(structure.stringDataType, file.type))) objectMeta.variables.setVariable('filename', new Variable(structure.stringDataType, new PrimitiveData(structure.stringDataType, file.name))) objectMeta.variables.setVariable('size', new Variable(structure.numberDataType, new PrimitiveData(structure.numberDataType, file.size))) const bufferData = new BufferData(structure.bufferDataType) bufferData.buffer = Buffer.from(content) ndata.variables.setVariable('content', new Variable(structure.bufferDataType, bufferData)) } variable.setValue(ndata) } onUnmounted(() => { props.componentArg?.objectData?.destroy() cpLocked.destroy() }); return { isLoading, isImage, visible, disable, label, model, uploadable, fileicon, mimetype, description, tooltip, table_name, field_name, key, removeFile } } /** * */ export function useCykFileCell(props: { row: ObjectData | undefined, dbColumn: DBColumn | undefined, scopeRow: Scope | undefined }) { const isLoading = ref(true) const label = ref('') const doLoading = async () => { try { if (!props.row || !props.dbColumn) throw `row or dbColumn undefined` const model = props.row.variables.getVariable(props.dbColumn.name) const meta = model ? variable2json(model) : undefined; if (meta) { label.value = `${meta.filename} ( ${formatFileSize(meta.size)} )` } else { label.value = '' } } catch (err) { logger.error(err) } finally { isLoading.value = false } }; doLoading(); return { isLoading, label } }