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:
Jason Tudisco 2022-02-07 04:42:42 -06:00
parent c617c63510
commit 11b15e1b9b
12 changed files with 494 additions and 11749 deletions

11723
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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');

View File

@ -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
View 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
View File

@ -0,0 +1,15 @@
<raw>
<script>
export default {
setInnerHTML() {
this.root.innerHTML = this.props.html
},
onMounted() {
this.setInnerHTML()
},
onUpdated() {
this.setInnerHTML()
}
}
</script>
</raw>

View File

@ -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);
});
}
}

View File

@ -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>

View 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>

View File

@ -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>

View 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>

View File

@ -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(()=>{