Browse Source

debug drop and drag.

tags/v0.1.8
Gitea 4 years ago
parent
commit
c2a25f262d
5 changed files with 320 additions and 360 deletions
  1. +12
    -23
      package-lock.json
  2. +1
    -0
      package.json
  3. +0
    -1
      templates/repo/datasets/dataset.tmpl
  4. +139
    -1
      templates/repo/datasets/index.tmpl
  5. +168
    -335
      web_src/js/components/MinioUploader.vue

+ 12
- 23
package-lock.json View File

@@ -1925,6 +1925,13 @@
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.10.0"
},
"dependencies": {
"follow-redirects": {
"version": "1.13.2",
"resolved": "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.13.2.tgz?cache=0&sync_timestamp=1611606737937&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.13.2.tgz",
"integrity": "sha1-3XPI7/wScoulz0JZ12DqX7g+MUc="
}
}
},
"babel-loader": {
@@ -5225,29 +5232,6 @@
"readable-stream": "^2.3.6"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"fomantic-ui": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/fomantic-ui/-/fomantic-ui-2.8.4.tgz",
@@ -5380,6 +5364,11 @@
"readable-stream": "^2.0.0"
}
},
"fs": {
"version": "0.0.1-security",
"resolved": "https://registry.npm.taobao.org/fs/download/fs-0.0.1-security.tgz",
"integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ="
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npm.taobao.org/fs-extra/download/fs-extra-8.1.0.tgz?cache=0&sync_timestamp=1611075469998&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffs-extra%2Fdownload%2Ffs-extra-8.1.0.tgz",


+ 1
- 0
package.json View File

@@ -23,6 +23,7 @@
"fast-glob": "3.2.2",
"file-loader": "6.0.0",
"fomantic-ui": "2.8.4",
"fs": "0.0.1-security",
"highlight.js": "10.0.3",
"imports-loader": "0.8.0",
"jquery": "3.5.1",


+ 0
- 1
templates/repo/datasets/dataset.tmpl View File

@@ -3,7 +3,6 @@
<label>{{.i18n.Tr "dataset.file"}}</label>
<div class="files"></div>
<div class="ui dropzone" id="dataset" data-upload-url="{{AppSubUrl}}/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-remove-url="{{AppSubUrl}}/attachments/delete" data-csrf="{{.CsrfToken}}" dataset-id={{.dataset.ID}} data-max-file="100" data-dataset-id="{{.dataset.ID}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}">

</div>
</div>
</div>

+ 139
- 1
templates/repo/datasets/index.tmpl View File

@@ -1,6 +1,35 @@
<script src="./dropzone.js"></script>
{{template "base/head" .}}
<style>
.dropzone.dz-clickable {
cursor: pointer;
}
.dropzone {
border-width: 2px;
border-style: dashed;
border-color: rgb(0, 135, 247);
border-image: initial;
border-radius: 5px;
background: white;
}

.dropzone {
min-height: 150px;
border-width: 2px;
border-style: solid;
border-color: rgba(0, 0, 0, 0.3);
border-image: initial;
background: white;
padding: 54px;
}

.dropzone, .dropzone * {
box-sizing: border-box;
}
</style>

<div class="repository release dataset-list view">
{{template "repo/header" .}}
{{template "repo/header" .}}
<form class="ui container" action="{{.Link}}" method="post">
<input name="id" value="{{.dataset.ID}}" type="hidden" />
<!--
@@ -77,6 +106,15 @@
</div>
<div class="dataset ui middle very relaxed page">
<div class="column">
<!-- 上传数据集 -->
<!-- <div class="dropzone dz-clickable" id="test">
<div id="file_upload"></div>
</div> -->



{{if .Permission.CanWrite $.UnitTypeDatasets}}
<div style='display:none;'
id="minioUploader-params"
@@ -106,6 +144,7 @@
</div>
{{if eq .StoreType "minio"}}
<div id="minioUploader"></div>

{{else}}
<div style="margin: 2em 0;"> {{.i18n.Tr "dropzone.enable_minio_support"}} </div>
{{end}}
@@ -132,3 +171,102 @@
{{template "base/delete_modal_actions" .}}
</div>
{{template "base/footer" .}}



<script>
// var myDropzone = new Dropzone("div#test", { url: "/todouploader"});
// var filepath = document.getElementById('file_upload').value

// myDropzone.options.myAwesomeDropzone = {
// paramName: filepath, // The name that will be used to transfer the file
// maxFilesize: 1, // MB
// accept: function(file, done) {
// if (file.name != "justinbieber.jpg") {
// done("Naha, you don't.");
// }
// else { done(); }
// }
// };
// 引入obs库
// var ObsClient = require('esdk-obs-nodejs');

// // 创建ObsClient实例
// var obsClient = new ObsClient({
// access_key_id: 'FDP3LRMHLB9S77VWEHE3',
// secret_access_key: 'LyM82Wk80pgjhs2z7AdDcsdpCWhbsJtSzQ7hkESN',
// server : 'https://112.95.163.82'
// });

// var bucketName = "mybucket"

// // 创建桶
// obsClient.createBucket({
// Bucket : bucketName
// }, (err, result) => {
// if(err){
// console.error('Error-->' + err);
// }else{
// console.log('Status-->' + result.CommonMsg.Status);
// }
// });

// var objectKey = get_result().key

// function get_result(){
// var res
// $.ajax({
// url: '/attachments/get_obs_key',
// type: 'GET',
// async: false,
// success: function(result){
// res = result
// }
// });
// return res
// }

// var filepath = document.getElementById('file_upload').value

// obsClient.initiateMultipartUpload({
// Bucket : 'mybucket',
// Key : objectKey,
// UploadFile : filepath,
// PartSize : 64 * 1024 * 1024,
// EnableCheckpoint : true,
// EnableCheckSum, true
// }, (err, result) => {
// if(err){
// console.error('Error-->' + err);
// }else{
// console.log('RequestId-->' + result.InterfaceResult.RequestId);
// console.log('Bucket-->' + result.InterfaceResult.Bucket);
// console.log('Key-->' + result.InterfaceResult.Key);
// console.log('Location-->' + result.InterfaceResult.Location);



// $.ajax({
// url: '/attachments/add',
// type: 'POST',
// data: {
// 'uuid': get_result().uuid,
// 'file_name': filepath.split('/')[-1],
// 'size': file.size,
// 'dataset_id': file.datasetId,
// '_csrf': csrf,
// 'type': 1
// },
// async: false,
// success: function (data) {
// console.log("chenggong ")
// }
// })

// }
// });




</script>

+ 168
- 335
web_src/js/components/MinioUploader.vue View File

@@ -4,13 +4,20 @@
id="dataset"
class="dropzone"
/>
<!-- <form class="dropzone" action="/todouploader">
<div class="fallback">
<input name="file" type="file"/>
</div>
</form> -->

<p class="upload-info">
{{ file_status_text }}
<span class="success">{{ status }}</span>
</p>
</div>
</div>
</template>


<script>
/* eslint-disable eqeqeq */
// import Dropzone from 'dropzone/dist/dropzone.js';
@@ -21,7 +28,6 @@ import qs from 'qs';
import createDropzone from '../features/dropzone.js';

const {_AppSubUrl, _StaticUrlPrefix, csrf} = window.config;
const cloud_brain_type = 1;

export default {
data() {
@@ -66,6 +72,79 @@ export default {
previewTemplate += ' </div>\n';
previewTemplate += '</div>';

// var fileArr = new Array();
// jQuery(function($){
// Dropzone.autoDiscover = true;
// Dropzone.options.myAwesomeDropzone = false;
// try {
// $(".dropzone").dropzone({
// url:"/todouploader",
// method:"post",
// paramName:"file",
// autoProcessQueue:true,//自动上传
// maxFilesize:1 * 1024 * 1024 * 1024 * 1024, // MB
// acceptedFiles:"*/*",
// dictInvalidFileType:"无效的文件类型",
// addRemoveLinks:true,
// maxFiles: 1, //指的是上传目录下的最大文件数
// // dictRemoveFile:"移除文件",
// dictDefaultMessage:
// "<span class='bigger-150 bolder'><i class='icon-caret-right red'></i>拖动文件</span>上传\
// <span class='smaller-80 gre'>(或者点击上传)</span> <br /> \
// <i class='upload-icon icon-cloud-upload blue icon-3x'></i>",
// dictResponseError:"文件上传失败!",
// dictFileTooBig:"文件过大,上传失败!",
// previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n <div class=\"dz-details\">\n <div class=\"dz-filename\"><span data-dz-name></span></div>\n <div class=\"dz-size\" data-dz-size></div>\n <img data-dz-thumbnail />\n </div>\n <div class=\"progress progress-small progress-striped active\"><div class=\"progress-bar progress-bar-success\" data-dz-uploadprogress></div></div>\n <div class=\"dz-success-mark\"><span>上传成功</span></div>\n <div class=\"dz-error-mark\"><span>上传失败</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
// init:function(){
// this.on("addedfile",function(file,data) {
// fileArr.push(file.upload.uuid);
// //解决点击时重复发送请求
// $(".dz-remove").each(function(index) {
// if(!$(".dz-remove:eq(" + index + ")").attr("id")) {
// $(".dz-remove:eq(" + index + ")").attr("id",fileArr[index]);
// }
// })
// }),

// this.on("success",function(file,data){
// //var myDropzone = this;
// $("#" + file.upload.uuid).click(function() {
// var fileName = $(this).parent().find(".dz-filename").text();
// console.log(fileName )
// })
// });
// this.on("complete",function(file) {
// if(file.status == "canceled" || file.status == "error") {
// var fileName = $("#" + file.upload.uuid).parent().find(".dz-filename").text();
// // setTimeout(function() {
// // $.ajax({
// // type:"POST",
// // url:"${pageContext.request.contextPath}/uploadController/delete.action",
// // data:{"fileName":fileName},
// // dataType:"json",
// // success:function(data){
// // if(data == "success") {
// // // alert("删除成功");
// // }
// // },
// // error:function(ajax) {
// // alert(ajax.status);
// // }
// // })
// // },2000);
// }
// })
// }
// });
// } catch(e) {
// alert('Dropzone.js does not support older browsers!');
// }
// });



const $dropzone = $('div#dataset');
console.log('createDropzone');
const dropzoneUploader = await createDropzone($dropzone[0], {
@@ -98,6 +177,7 @@ export default {

this.dropzoneUploader = dropzoneUploader;
},

methods: {
resetStatus() {
this.progress = 0;
@@ -108,354 +188,107 @@ export default {
'.dz-upload'
).style.width = `${progress}%`;
},
emitDropzoneSuccess(file) {
file.status = 'success';
this.dropzoneUploader.emit('success', file);
this.dropzoneUploader.emit('complete', file);
},
emitDropzoneFailed(file) {
this.status = this.dropzoneParams.data('falied');
file.status = 'error';
this.dropzoneUploader.emit('error', file);
// this.dropzoneUploader.emit('complete', file);
},

onFileAdded(file) {
file.datasetId = document
.getElementById('datasetId')
.getAttribute('datasetId');
this.resetStatus();
this.computeMD5(file);
// this.computeMD5(file);
},

finishUpload(file) {
this.emitDropzoneSuccess(file);
// setTimeout(() => {
// window.location.reload();
// }, 1000);
},

// computeMD5(file) {
// this.resetStatus();
// const blobSlice =
// File.prototype.slice ||
// File.prototype.mozSlice ||
// File.prototype.webkitSlice,
// chunkSize = 1024 * 1024 * 64,
// chunks = Math.ceil(file.size / chunkSize),
// spark = new SparkMD5.ArrayBuffer(),
// fileReader = new FileReader();
// let currentChunk = 0;

// const time = new Date().getTime();
// // console.log('计算MD5...')
// this.status = this.dropzoneParams.data('md5-computing');
// file.totalChunkCounts = chunks;
// loadNext();

// fileReader.onload = (e) => {
// fileLoaded.call(this, e);
// };
// fileReader.onerror = (err) => {
// console.warn('oops, something went wrong.', err);
// file.cancel();
// };

// function fileLoaded(e) {
// spark.append(e.target.result); // Append array buffer
// currentChunk++;
// if (currentChunk < chunks) {
// // console.log(`第${currentChunk}分片解析完成, 开始第${currentChunk +1}/${chunks}分片解析`);
// this.status = `${this.dropzoneParams.data('loading-file')} ${(
// (currentChunk / chunks) *
// 100
// ).toFixed(2)}% (${currentChunk}/${chunks})`;
// this.updateProgress(file, ((currentChunk / chunks) * 100).toFixed(2));
// loadNext();
// return;
// }

// const md5 = spark.end();
// console.log(
// `MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
// file.size
// } 用时:${(new Date().getTime() - time) / 1000} s`
// );
// spark.destroy(); // 释放缓存
// file.uniqueIdentifier = md5; // 将文件md5赋值给文件唯一标识
// file.cmd5 = false; // 取消计算md5状态
// this.computeMD5Success(file);
// }

// function loadNext() {
// const start = currentChunk * chunkSize;
// const end =
// start + chunkSize >= file.size ? file.size : start + chunkSize;
// fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
// }
// },

// async computeMD5Success(md5edFile) {
// const file = await this.getSuccessChunks(md5edFile);
// try {
// if (file.uploadID == '' || file.uuid == '') {
// // 未上传过
// await this.newMultiUpload(file);
// if (file.uploadID != '' && file.uuid != '') {
// file.chunks = '';
// this.multipartUpload(file);
// } else {
// // 失败如何处理
// return;
// }
// return;
// }
// finishUpload(file) {
// this.emitDropzoneSuccess(file);
// setTimeout(() => {
// window.location.reload();
// }, 1000);
// },

// if (file.uploaded == '1') {
// // 已上传成功
// // 秒传
// if (file.attachID == '0') {
// // 删除数据集记录,未删除文件
// await addAttachment(file);
// }
// //不同数据集上传同一个文件
// if (file.datasetID != '') {
// if (Number(file.datasetID) != file.datasetId) {
// var info = "该文件已上传,对应数据集(" + file.datasetName + ")-文件(" + file.realName + ")";
// window.alert(info);
// window.location.reload();
// }
// }
// console.log('文件已上传完成');
// this.progress = 100;
// this.status = this.dropzoneParams.data('upload-complete');
// this.finishUpload(file);
// } else {
// // 断点续传
// this.multipartUpload(file);
// }
// } catch (error) {
// this.emitDropzoneFailed(file);
// console.log(error);
// }

// async function addAttachment(file) {
// return await axios.post(
// '/attachments/add',
// qs.stringify({
// uuid: file.uuid,
// file_name: file.name,
// size: file.size,
// dataset_id: file.datasetId,
// _csrf: csrf
// })
// );
// }
// },

// async getSuccessChunks(file) {
// const params = {
// params: {
// md5: file.uniqueIdentifier,
// type: cloud_brain_type,
// _csrf: csrf
// }
// };
// try {
// const response = await axios.get('/attachments/get_chunks', params);
// file.uploadID = response.data.uploadID;
// file.uuid = response.data.uuid;
// file.uploaded = response.data.uploaded;
// file.chunks = response.data.chunks;
// file.attachID = response.data.attachID;
// file.datasetID = response.data.datasetID;
// file.datasetName = response.data.datasetName;
// file.realName = response.data.fileName;
// return file;
// } catch (error) {
// this.emitDropzoneFailed(file);
// console.log('getSuccessChunks catch: ', error);
// return null;
// }
// },

// async newMultiUpload(file) {
// const res = await axios.get('/attachments/new_multipart', {
// params: {
// totalChunkCounts: file.totalChunkCounts,
// md5: file.uniqueIdentifier,
// size: file.size,
// fileType: file.type,
// type: cloud_brain_type,
// _csrf: csrf
// }
// });
// console.log("dsnjs", res)
// file.uploadID = res.data.uploadID;
// file.uuid = res.data.uuid;
// },

// multipartUpload(file) {
// const blobSlice =
// File.prototype.slice ||
// File.prototype.mozSlice ||
// File.prototype.webkitSlice,
// chunkSize = 1024 * 1024 * 64,
// chunks = Math.ceil(file.size / chunkSize),
// fileReader = new FileReader(),
// time = new Date().getTime();
// let currentChunk = 0;

// function loadNext() {
// const start = currentChunk * chunkSize;
// const end =
// start + chunkSize >= file.size ? file.size : start + chunkSize;
// fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
// }

// function checkSuccessChunks() {
// const index = successChunks.indexOf((currentChunk + 1).toString());
// if (index == -1) {
// return false;
// }
// return true;
// }

// // async function getUploadChunkUrl(currentChunk, partSize) {
// // const res = await axios.get('/attachments/get_multipart_url', {
// // params: {
// // uuid: file.uuid,
// // uploadID: file.uploadID,
// // size: partSize,
// // chunkNumber: currentChunk + 1,
// // type: cloud_brain_type,
// // _csrf: csrf
// // }
// // });
// // urls[currentChunk] = res.data.url;
// // }

// // async function uploadMinio(url, e) {
// // const res = await axios.put(url, e.target.result);
// // etags[currentChunk] = res.headers.etag;
// // }
fileLoaded(e) {
this.updateProgress(file, ((currentChunk / chunks) * 100).toFixed(2));
},

// // async function updateChunk(currentChunk) {
// // await axios.post(
// // '/attachments/update_chunk',
// // qs.stringify({
// // uuid: file.uuid,
// // chunkNumber: currentChunk + 1,
// // etag: etags[currentChunk],
// // _csrf: csrf
// // })
// // );
// // }
// async computeMD5Success(md5edFile) {
// async function addAttachment(file) {
// return await axios.post(
// '/attachments/add',
// qs.stringify({
// uuid: file.uuid,
// file_name: file.name,
// size: file.size,
// dataset_id: file.datasetId,
// _csrf: csrf
// })
// );
// }
// },


async newMultiUpload(file) {
const res = await axios.get('/attachments/new_multipart', {
params: {
totalChunkCounts: file.totalChunkCounts,
md5: file.uniqueIdentifier,
size: file.size,
fileType: file.type,
_csrf: csrf
}
});
file.uploadID = res.data.uploadID;
file.uuid = res.data.uuid;
},

// async function uploadPart(currentChunk, partSize, e) {
// console.log(e);
// let params = new FormData();
// params.append("uuid", file.uuid);
// params.append("uploadId", file.uploadID);
// params.append("size", partSize);
// params.append("chunkNumber", currentChunk + 1);
// params.append("file", e.target.file);
// params.append("_csrf", csrf);
// return await axios.post('/attachments/upload_part',
// params,
// {headers: {'Content-Type': 'multipart/form-data'}}
// );
// }
multipartUpload(file) {
async function updateChunk(currentChunk) {
await axios.post(
'/attachments/update_chunk',
qs.stringify({
uuid: file.uuid,
chunkNumber: currentChunk + 1,
etag: etags[currentChunk],
_csrf: csrf
})
);
}

// async function uploadChunk(e) {
// try {
// if (!checkSuccessChunks()) {
// const start = currentChunk * chunkSize;
// const partSize =
// start + chunkSize >= file.size ? file.size - start : chunkSize;
// await uploadPart(currentChunk, partSize, e);
async function completeUpload() {
return await axios.post(
'/attachments/complete_multipart',
qs.stringify({
uuid: file.uuid,
uploadID: file.uploadID,
file_name: file.name,
size: file.size,
dataset_id: file.datasetId,
_csrf: csrf
})
);
}

// // 获取分片上传url
// // await getUploadChunkUrl(currentChunk, partSize);
// // if (urls[currentChunk] != '') {
// // // 上传到minio
// // await uploadMinio(urls[currentChunk], e);
// // if (etags[currentChunk] != '') {
// // // 更新数据库:分片上传结果
// // //await updateChunk(currentChunk);
// // } else {
// // console.log("上传到minio uploadChunk etags[currentChunk] == ''");// TODO
// // }
// // } else {
// // console.log("uploadChunk urls[currentChunk] != ''");// TODO
// // }
const successChunks = [];
let successParts = [];
successParts = file.chunks.split(',');
for (let i = 0; i < successParts.length; i++) {
successChunks[i] = successParts[i].split('-')[0];
}

// }
// } catch (error) {
// console.log(error);
// this.emitDropzoneFailed(file);
// }
// }
}
}
};
</script>

// async function completeUpload() {
// return await axios.post(
// '/attachments/complete_multipart',
// qs.stringify({
// uuid: file.uuid,
// uploadID: file.uploadID,
// file_name: file.name,
// size: file.size,
// dataset_id: file.datasetId,
// type: cloud_brain_type,
// _csrf: csrf
// })
// );
// }
<style>
/* .fallback {
border: 1px white solid;
}

// const successChunks = [];
// let successParts = [];
// successParts = file.chunks.split(',');
// for (let i = 0; i < successParts.length; i++) {
// successChunks[i] = successParts[i].split('-')[0];
// }
// const urls = []; // TODO const ?
// const etags = [];
// console.log('上传分片...');
// this.status = this.dropzoneParams.data('uploading');
// loadNext();
// fileReader.onload = async (e) => {
// await uploadChunk(e);
// fileReader.abort();
// currentChunk++;
// if (currentChunk < chunks) {
// console.log(
// `第${currentChunk}个分片上传完成, 开始第${currentChunk +
// 1}/${chunks}个分片上传`
// );
// this.progress = Math.ceil((currentChunk / chunks) * 100);
// this.updateProgress(file, ((currentChunk / chunks) * 100).toFixed(2));
// this.status = `${this.dropzoneParams.data('uploading')} ${(
// (currentChunk / chunks) *
// 100
// ).toFixed(2)}%`;
// await loadNext();
// } else {
// await completeUpload();
// console.log(
// `文件上传完成:${file.name} \n分片:${chunks} 大小:${
// file.size
// } 用时:${(new Date().getTime() - time) / 1000} s`
// );
// this.progress = 100;
// this.status = this.dropzoneParams.data('upload-complete');
// this.finishUpload(file);
// }
// };
// }
// }
// };
// </script>
button, input {
overflow: visible;
width: 100%;
height: 15%;
opacity: 0;
} */

<style>
.dropzone-wrapper {
margin: 2em auto;
}
@@ -475,4 +308,4 @@ export default {
border-bottom: 1px solid #dadce0;
min-height: 0;
}
</style>
</style>

Loading…
Cancel
Save