Bug fixes to data layer.
Paste input now support images. New static time stamp control. Some more utility functions for converting data types. New control for showing raw HTML data. New control to show tags, to be expanded latter.
This commit is contained in:
parent
c617c63510
commit
11b15e1b9b
11723
package-lock.json
generated
11723
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -30,16 +30,17 @@
|
||||
"@riotjs/parcel-transformer-riot": "^7.0.3",
|
||||
"electron": "^16.0.7",
|
||||
"jest": "^27.4.7",
|
||||
"parcel": "^2.2.1"
|
||||
"parcel": "^2",
|
||||
"electron-rebuild": "^3.2.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yaireo/tagify": "^4.9.5",
|
||||
"better-sqlite3": "^7.5.0",
|
||||
"conf": "^10.1.1",
|
||||
"dayjs": "^1.10.7",
|
||||
"electron-rebuild": "^3.2.7",
|
||||
"empty-lite": "^1.2.0",
|
||||
"es6-interface": "^3.2.1",
|
||||
"hash.js": "^1.1.7",
|
||||
"nanoid": "^3.2.0",
|
||||
"pubsub-js": "^1.9.4",
|
||||
"riot": "^6.1.2",
|
||||
|
||||
@ -15,8 +15,8 @@ const DB = ConnectToDatabase(DBPath);
|
||||
|
||||
const DbRecord = new TimeChainDataSqliteRecord();
|
||||
const DbFile = new TimeChainDataSqliteFile();
|
||||
const dbTag = new TimeChainDataSqliteTag();
|
||||
const dbTagLink = new TimeChainDataSqliteTagLink();
|
||||
const DbTag = new TimeChainDataSqliteTag();
|
||||
const DbTagLink = new TimeChainDataSqliteTagLink();
|
||||
//const configDir = app.getPath('userData');
|
||||
//const appPath = app.getPath('exe');
|
||||
|
||||
|
||||
@ -60,9 +60,15 @@ class TimeChainDataSqliteTag extends Interface(InterfaceTag) {
|
||||
|
||||
add(tag){
|
||||
return new Promise(resolve=>{
|
||||
const dt = Math.floor(Date.now());
|
||||
const res = this.table_add.run(tag,dt,dt);
|
||||
return resolve(res?.changes);
|
||||
this.has(tag).then(cnt=>{
|
||||
if(cnt){
|
||||
return resolve(1);
|
||||
}else{
|
||||
const dt = Math.floor(Date.now());
|
||||
const res = this.table_add.run(tag,dt,dt);
|
||||
return resolve(res?.changes);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -380,6 +386,21 @@ class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) {
|
||||
return this._table_fine_one;
|
||||
}
|
||||
|
||||
get table_find(){
|
||||
if(!this._table_find){
|
||||
this._table_find = db.prepare(`
|
||||
select records.*, GROUP_CONCAT(taglink.tag,',') AS tags
|
||||
from records
|
||||
left join taglink on (taglink.uuid = records.uuid)
|
||||
group by records.uuid
|
||||
order by records.timestamp DESC
|
||||
limit ? offset ?
|
||||
`);
|
||||
}
|
||||
|
||||
return this._table_find;
|
||||
}
|
||||
|
||||
add(uuid,timestamp,content,mime,hash){
|
||||
return new Promise(resolve=>{
|
||||
const dt = Math.floor(Date.now());
|
||||
@ -398,13 +419,12 @@ class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) {
|
||||
find(search,sort=null,limit=undefined,offset=0){
|
||||
//TODO: add logic for seach and sort (Currently does nothing)
|
||||
return new Promise(resolve=>{
|
||||
const rescount = this.db.prepare("SELECT count(*) as cnt FROM records").get();
|
||||
const prepare = this.table_find;
|
||||
|
||||
if(limit){
|
||||
const res = this.db.prepare("SELECT * FROM records ORDER BY timestamp DESC LIMIT ? OFFSET ?").all(limit,offset);
|
||||
}else{
|
||||
const res = this.db.prepare("SELECT * FROM records ORDER BY timestamp DESC").all();
|
||||
}
|
||||
const res = prepare.all(100,0);
|
||||
|
||||
//TODO: add count here
|
||||
const rescount = {cnt:100};
|
||||
|
||||
return resolve({
|
||||
data: res,
|
||||
@ -413,7 +433,7 @@ class TimeChainDataSqliteRecord extends Interface(InterfaceRecord) {
|
||||
offset: offset
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
delete(uuid){
|
||||
|
||||
21
src/lib/blobconvert.js
Normal file
21
src/lib/blobconvert.js
Normal file
@ -0,0 +1,21 @@
|
||||
//**dataURL to blob**
|
||||
function dataURLtoBlob(dataurl) {
|
||||
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
|
||||
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
|
||||
while(n--){
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new Blob([u8arr], {type:mime});
|
||||
}
|
||||
|
||||
//**blob to dataURL**
|
||||
function blobToDataURL(blob, callback) {
|
||||
var a = new FileReader();
|
||||
a.onload = function(e) {callback(e.target.result);}
|
||||
a.readAsDataURL(blob);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dataURLtoBlob,
|
||||
blobToDataURL
|
||||
}
|
||||
15
src/ui/raw.riot
Normal file
15
src/ui/raw.riot
Normal file
@ -0,0 +1,15 @@
|
||||
<raw>
|
||||
<script>
|
||||
export default {
|
||||
setInnerHTML() {
|
||||
this.root.innerHTML = this.props.html
|
||||
},
|
||||
onMounted() {
|
||||
this.setInnerHTML()
|
||||
},
|
||||
onUpdated() {
|
||||
this.setInnerHTML()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</raw>
|
||||
@ -1,16 +1,25 @@
|
||||
<timechain-input>
|
||||
|
||||
<div class="timechain-input-tempate" if={state.show_tagging}>
|
||||
<timechain-tag if={state.show_tagging} onchange="{onTags}"/>
|
||||
<timechain-input-buttons if={state.show_tagging} onsave="{onSave}" />
|
||||
<timechain-tag if={state.show_tagging} ontag="{onTags}" />
|
||||
<timechain-input-buttons if={state.show_tagging} onsave="{onSave}" oncancel={onCancel} />
|
||||
</div>
|
||||
|
||||
<div id="pasteme" ondblclick="{onSwap}">
|
||||
<div id="pasteme" ondblclick="{onSwap}" >
|
||||
<p>{state.message}</p>
|
||||
</div>
|
||||
|
||||
<!-- textarea id="pasteme" ondblclick="{onSwap}" if="{content_mime == 'text/plain'}">
|
||||
{state.message}
|
||||
</textarea -->
|
||||
|
||||
<style>
|
||||
|
||||
textarea#pasteme {
|
||||
width:100%;
|
||||
color:black;
|
||||
}
|
||||
|
||||
.paste-html {
|
||||
background: white !important;
|
||||
color: black !important;
|
||||
@ -38,11 +47,20 @@
|
||||
|
||||
const pubsub = require('pubsub-js');
|
||||
const empty = require('empty-lite');
|
||||
const hash = require('hash.js');
|
||||
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import TimechainTag from './timechain-tag.riot'
|
||||
import TimechainInputButtons from './timechain-input-buttons.riot';
|
||||
import {blobToDataURL} from '../lib/blobconvert';
|
||||
|
||||
const {TimeChainDataSqliteRecord} = require('../data/sqlite-client');
|
||||
const {
|
||||
TimeChainDataSqliteRecord,
|
||||
TimeChainDataSqliteTag,
|
||||
TimeChainDataSqliteTagLink
|
||||
} = require('../data/sqlite-client');
|
||||
|
||||
export default {
|
||||
state: {
|
||||
@ -53,38 +71,140 @@
|
||||
TimechainTag,
|
||||
TimechainInputButtons
|
||||
},
|
||||
onBeforeMount(){
|
||||
this.tc = 0;
|
||||
this.clean_props = ['timestamp','content_text','content_mime','content_type','content_swap','content_tags'];
|
||||
},
|
||||
onMounted(){
|
||||
document.addEventListener('paste', this.pasteEvent.bind(this));
|
||||
},
|
||||
cleanFields(){
|
||||
this.clean_props.forEach(f=>{
|
||||
this[f]=null;
|
||||
});
|
||||
const el = this.$('#pasteme');
|
||||
el.innerHTML = `<p>${this.state.message}</p>`;
|
||||
el.classList.remove('paste-html');
|
||||
pubsub.publish('tag.clean');
|
||||
this.update({show_tagging:false});
|
||||
pubsub.publish('timestamp-resume',true);
|
||||
},
|
||||
pasteEvent(e){
|
||||
e.preventDefault();
|
||||
|
||||
console.log("Bla");
|
||||
console.log(e.clipboardData.items[0]);
|
||||
console.log(e.clipboardData.items[1]);
|
||||
console.log("Paste Event");
|
||||
console.log("Item 1", e.clipboardData.items[0]);
|
||||
console.log("Item 2", e.clipboardData.items[1]);
|
||||
|
||||
if (e.clipboardData.types.indexOf('text/html') > -1) {
|
||||
const newData = e.clipboardData.getData('text/html');
|
||||
const newDataText = e.clipboardData.getData('text/plain');
|
||||
const el = this.$('#pasteme');
|
||||
pubsub.publish('timestamp-puase',true);
|
||||
this.timestamp = Math.floor(new Date().getTime());
|
||||
pubsub.publish('timestamp-settime',this.timestamp);
|
||||
el.innerHTML = this.content = newData;
|
||||
this.content_text = e.clipboardData.getData('text/plain');
|
||||
this.content_mime = 'text/html';
|
||||
el.classList.add('paste-html');
|
||||
let known_type = true;
|
||||
let isFile = false;
|
||||
const types = e.clipboardData.types;
|
||||
|
||||
this.content_type = "html";
|
||||
this.content_swap = 0;
|
||||
this.content_orig = this.content;
|
||||
console.log("types",types);
|
||||
|
||||
if(types.indexOf('Files') > -1){
|
||||
|
||||
|
||||
const c = e.clipboardData.items.length;
|
||||
|
||||
console.log("FILES!!", c);
|
||||
|
||||
for(let i=0; i < c ; i++){
|
||||
console.log(i);
|
||||
const t = e.clipboardData.items[i].type;
|
||||
console.log("Clip Data", e.clipboardData.items[i]);
|
||||
|
||||
if(t == 'image/jpeg'){
|
||||
isFile = true;
|
||||
this.isIMAGE(e.clipboardData.items[i], 'image/jpeg');
|
||||
break;
|
||||
}else if(t == 'image/png'){
|
||||
isFile = true;
|
||||
this.isIMAGE(e.clipboardData.items[i], 'image/png')
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}else if (types.indexOf('text/html') > -1) {
|
||||
//this.content_mime = 'text/html';
|
||||
//this.update();
|
||||
this.isHTML(e);
|
||||
}else if(types.indexOf('text/plain') > -1){
|
||||
//this.content_mime = 'text/plain';
|
||||
//this.update();
|
||||
this.isTEXT(e);
|
||||
}else{
|
||||
known_type = false;
|
||||
}
|
||||
|
||||
if(!empty(this.content)){
|
||||
console.log(known_type,this.content,isFile || empty(this.content));
|
||||
if(known_type && (isFile || !empty(this.content)) ){
|
||||
this.update({show_tagging:true});
|
||||
}else{
|
||||
console.log("No reason to show tagging");
|
||||
}
|
||||
|
||||
},
|
||||
isHTML(e){
|
||||
console.log("Handling HTML");
|
||||
const newData = e.clipboardData.getData('text/html');
|
||||
const newDataText = e.clipboardData.getData('text/plain');
|
||||
|
||||
const el = this.$('#pasteme');
|
||||
this.pauseTime();
|
||||
|
||||
el.innerHTML = this.content = newData;
|
||||
this.content_text = newDataText;
|
||||
this.content_mime = 'text/html';
|
||||
|
||||
el.classList.add('paste-html');
|
||||
|
||||
this.content_type = "html";
|
||||
this.content_swap = 0;
|
||||
this.content_orig = this.content;
|
||||
|
||||
},
|
||||
isTEXT(e){
|
||||
console.log("Handling TEXT");
|
||||
const data = e.clipboardData.getData('text/plain');
|
||||
|
||||
const el = this.$('#pasteme');
|
||||
|
||||
pubsub.publish('timestamp-puase',true);
|
||||
this.pauseTime();
|
||||
|
||||
el.innerText = this.content = data;
|
||||
//el.value = this.content = data;
|
||||
this.content_mime = 'text/plain';
|
||||
|
||||
el.classList.add('paste-html');
|
||||
|
||||
this.content_type = "text";
|
||||
this.content_swap = false;
|
||||
this.content_orig = this.content;
|
||||
},
|
||||
isIMAGE(e, mime){
|
||||
const el = this.$('#pasteme');
|
||||
const f = e.getAsFile();
|
||||
|
||||
this.content_orig = this.content = f;
|
||||
this.content_mime = mime;
|
||||
this.content_type = "image";
|
||||
this.content_swap = false;
|
||||
|
||||
blobToDataURL(f,data=>{
|
||||
this.content = data;
|
||||
el.innerHTML = "<img src='" + data + "' style='max-width:100%'>";
|
||||
});
|
||||
|
||||
|
||||
|
||||
},
|
||||
pauseTime(){
|
||||
pubsub.publish('timestamp-puase',true);
|
||||
this.timestamp = Math.floor(new Date().getTime());
|
||||
pubsub.publish('timestamp-settime',this.timestamp);
|
||||
},
|
||||
onSwap(){
|
||||
console.log("Here we can swap to text");
|
||||
|
||||
@ -117,16 +237,60 @@
|
||||
|
||||
},
|
||||
onTags(tags){
|
||||
console.log(tags);
|
||||
this.tags = tags;
|
||||
console.log(++this.tc,tags);
|
||||
this.content_tags = tags;
|
||||
},
|
||||
onCancel(){
|
||||
this.cleanFields();
|
||||
},
|
||||
onSave(){
|
||||
console.log("Save button pressed");
|
||||
const TR = new TimeChainDataSqliteRecord();
|
||||
|
||||
TR.add("iueyriweusdfsd8w",Math.floor(new Date().getTime()),this.content,this.content_mime,"Comming SOon").then(res=>{
|
||||
console.log(res);
|
||||
})
|
||||
const ts = Math.floor(new Date().getTime());
|
||||
const id = nanoid();
|
||||
const hs = hash.sha256().update(this.content).digest('hex');
|
||||
const tags = JSON.parse(this.content_tags);
|
||||
|
||||
console.log("tags:",tags);
|
||||
|
||||
TR.add(id,ts,this.content,this.content_mime,hs).then(res=>{
|
||||
if(res != 1){
|
||||
throw new Error("Not about to save record");
|
||||
}
|
||||
}).then(()=>{
|
||||
if(!tags) return false;
|
||||
const TG = new TimeChainDataSqliteTag();
|
||||
const tasks = tags.map(t=>{
|
||||
return TG.add(t.value);
|
||||
});
|
||||
|
||||
return Promise.all(tasks).then(res=>{
|
||||
console.log(res);
|
||||
return true;
|
||||
});
|
||||
}).then(()=>{
|
||||
if(!tags) return false;
|
||||
const TL = new TimeChainDataSqliteTagLink();
|
||||
const tasks = tags.map(t=>{
|
||||
return TL.add(id,t.value);
|
||||
});
|
||||
return Promise.all(tasks).then(res=>{
|
||||
console.log(res);
|
||||
return true;
|
||||
});
|
||||
}).then(()=>{
|
||||
this.cleanFields();
|
||||
}).then(()=>{
|
||||
|
||||
setTimeout(()=>{
|
||||
pubsub.publish('timechain-list-update', true);
|
||||
},100);
|
||||
|
||||
}).catch(err => {
|
||||
alert("Error saving data!");
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,12 +1,107 @@
|
||||
<timechain-list>
|
||||
|
||||
<div class="timechain-list-title">
|
||||
List of shit
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="timechain-list">
|
||||
bla bla
|
||||
|
||||
<div class="timechange-item" each="{r in state.records}">
|
||||
|
||||
<div class="timechain-timestamp">
|
||||
<timestamp-static time={r.timestamp} />
|
||||
</div>
|
||||
|
||||
<div class="timechain-tag-list">
|
||||
<timechain-tag-list tags="{r.tags}" />
|
||||
</div>
|
||||
|
||||
<div class="timechain-item-text" if="{r.mime == 'text/plain'}">
|
||||
{r.content}
|
||||
</div>
|
||||
|
||||
<div class="timechain-item-text" if="{r.mime == 'text/html'}">
|
||||
<raw html="{r.content}" />
|
||||
</div>
|
||||
|
||||
<div class="timechain-item-image" if="{r.imageURL}">
|
||||
<img src="{r.imageURL}" style="max-width:100%">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.timechain-list {
|
||||
padding: 0 3em;
|
||||
}
|
||||
|
||||
.timechain-list .timechange-item {
|
||||
border: 1px dashed whitesmoke;
|
||||
padding: 0 1em 1em 1em;
|
||||
margin: 1em 0;
|
||||
background-color: whitesmoke;
|
||||
color: #333;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import Raw from './raw.riot'
|
||||
import TimestampStatic from './timestamp-static.riot'
|
||||
import TimechainTagList from './timechain-tag-list.riot'
|
||||
import pubsub from 'pubsub-js'
|
||||
|
||||
const {
|
||||
TimeChainDataSqliteRecord
|
||||
} = require('../data/sqlite-client');
|
||||
|
||||
export default {
|
||||
|
||||
state: {
|
||||
records: []
|
||||
},
|
||||
components: {
|
||||
Raw,
|
||||
TimestampStatic,
|
||||
TimechainTagList
|
||||
},
|
||||
onMounted(){
|
||||
console.log("List mounted");
|
||||
this.loadRecords();
|
||||
|
||||
this.event_load = pubsub.subscribe('timechain-list-update', this.loadRecords.bind(this));
|
||||
},
|
||||
|
||||
onUnmounted(){
|
||||
pubsub.unsubscribe(this.event_load);
|
||||
},
|
||||
|
||||
loadRecords(){
|
||||
const TR = new TimeChainDataSqliteRecord();
|
||||
|
||||
TR.find({},null,100,0).then(records=>{
|
||||
console.log("Records", records);
|
||||
this.update({records:records.data});
|
||||
}).then(()=>{
|
||||
this.processImages();
|
||||
})
|
||||
},
|
||||
|
||||
processImages(){
|
||||
var URLObj = window.URL || window.webkitURL;
|
||||
this.state.records.forEach(r=>{
|
||||
if(r.mime == "image/jpeg" || r.mime == "image/png"){
|
||||
r.imageURL = r.content;
|
||||
}
|
||||
});
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</timechain-list>
|
||||
44
src/ui/timechain-tag-list.riot
Normal file
44
src/ui/timechain-tag-list.riot
Normal file
@ -0,0 +1,44 @@
|
||||
<timechain-tag-list>
|
||||
|
||||
<div class="timechain-tags">
|
||||
<span class="timechain-tag" each="{t in state.tags}">{t}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
.timechain-tags {
|
||||
text-align: center;
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.timechain-tags .timechain-tag {
|
||||
padding: 0.3em;
|
||||
margin: 0em 0.2em;
|
||||
color: white;
|
||||
background-color: #333;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
|
||||
state: {
|
||||
tags: []
|
||||
},
|
||||
onMounted: function(props){
|
||||
if(!props.tags) return;
|
||||
this.update({
|
||||
tags: props.tags.split(',')
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</timechain-tag-list>
|
||||
@ -17,15 +17,23 @@
|
||||
|
||||
<script>
|
||||
import Tagify from '@yaireo/tagify'
|
||||
const pubsub = require('pubsub-js');
|
||||
|
||||
export default {
|
||||
onMounted(){
|
||||
const inputElm = this.$('#tagging');
|
||||
this.tagify = new Tagify(inputElm);
|
||||
inputElm.addEventListener('change', this.onChange.bind(this));
|
||||
this.event_clean = pubsub.subscribe('tag.clean', this.onClean.bind(this));
|
||||
},
|
||||
onUnmounted(){
|
||||
pubsub.unsubscribe(this.event_clean);
|
||||
},
|
||||
onChange(e){
|
||||
this.props.onchange(e.target.value);
|
||||
this.props?.ontag(e.target.value);
|
||||
},
|
||||
onClean(){
|
||||
this.tagify.removeAllTags();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
51
src/ui/timestamp-static.riot
Normal file
51
src/ui/timestamp-static.riot
Normal file
@ -0,0 +1,51 @@
|
||||
<timestamp-static>
|
||||
|
||||
<div class="timestamp-static">
|
||||
<div class="timestamp-text">
|
||||
<span class="timestamp-date">{state.time_date}</span>
|
||||
<span class="timestamp-time">{state.time_time}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.timestamp-static .timestamp-text {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin: 0.5em 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
.timestamp-static .timestamp-text .timestamp-date {
|
||||
font-weight: bold;
|
||||
padding-right: 0.8em;
|
||||
}
|
||||
|
||||
.timestamp-static .timestamp-text .timestamp-time {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
const dayjs = require('dayjs');
|
||||
export default {
|
||||
state: {
|
||||
time_date: 'Unknown',
|
||||
time_time: '',
|
||||
time_orig: null
|
||||
},
|
||||
onMounted(props){
|
||||
this.update({
|
||||
time_date: this.format(props.time, true),
|
||||
time_time: this.format(props.time, false),
|
||||
time_orig: props.time
|
||||
});
|
||||
},
|
||||
format(time, format_date){
|
||||
return format_date ? dayjs(new Date(time)).format("MMMM D, YYYY") : dayjs(new Date(time)).format("h:mm:ss A");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</timestamp-static>
|
||||
@ -27,7 +27,12 @@
|
||||
|
||||
pubsub.subscribe("timestamp-settime",(event,arg)=>{
|
||||
this.update({time_text:dayjs(new Date(arg)).format("MMMM D, YYYY h:mm:ss A")});
|
||||
})
|
||||
});
|
||||
|
||||
pubsub.subscribe("timestamp-resume", (event)=>{
|
||||
this.$('.timestamp-text').classList.remove('timestamp-puase');
|
||||
this.start();
|
||||
});
|
||||
},
|
||||
start(){
|
||||
this.event_handle = setTimeout(()=>{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user