adding a few fixes and new logic for tags

release-neutralino
HerrHase 2 years ago
parent 5d24497a5d
commit 24be1e9f6a

@ -72,6 +72,7 @@ class Connector {
// if event is in handlers-object mapped call it
if (this.handlers.hasOwnProperty(message.event)) {
console.log(event.data)
this.handlers[message.event].call(null, this, message.data)
}
}

@ -38,7 +38,25 @@ class AppsHandler {
appsStore.update(data)
.then((data) => {
connector.send('pouchdb.apps.success', data)
connector.send('pouchdb.apps.readyOne', data)
})
}
/**
* use update in appStore and send event with result to app
*
* @param {object} data
* @return {object}
*
*/
findOne(connector, data) {
const appsStore = new AppsStore()
appsStore.findOneById(data.id)
.then((data) => {
if (data) {
connector.send('pouchdb.apps.readyOne', data)
}
})
}

@ -9,6 +9,8 @@ const appsHandler = new AppsHandler
// create connector and add handlers for events
const connector = new Connector({
'pouchdb.apps.create': appsHandler.create,
'pouchdb.apps.update': appsHandler.update,
'pouchdb.apps.findOne': appsHandler.findOne,
'pouchdb.apps.find': appsHandler.find
})

@ -1,5 +1,5 @@
{
"name": "database",
"private": true,
"version": "1.0.0",
"main": "index.js",
"license": "MIT",

@ -8,7 +8,18 @@ import PouchdbHandler from './pouchdbHandler.js'
* @link https://gitea.node001.net/HerrHase/tellme-bot.git
*
*/
class AppsDatabase extends PouchdbHandler {
class AppsStore extends PouchdbHandler {
constructor() {
super()
// add index for apps
this.createIndex([
'name',
'description',
'tags'
])
}
/**
*
@ -17,12 +28,11 @@ class AppsDatabase extends PouchdbHandler {
*
*/
create(data) {
data['type'] = 'apps'
return this.db.post(data)
.then((response) => {
console.log(response)
return response
}).catch((error) => {
console.log(error)
})
}
@ -35,8 +45,15 @@ class AppsDatabase extends PouchdbHandler {
*/
findOneById(id) {
const query = {
'fields': [
'_id',
'name',
'command',
'description',
'thumbnail',
'tags'
],
'selector': {
'type': 'apps',
'_id' : id
}
}
@ -47,6 +64,8 @@ class AppsDatabase extends PouchdbHandler {
} else {
return documents.docs[0]
}
}).catch((error) => {
console.log(error)
})
}
@ -58,16 +77,33 @@ class AppsDatabase extends PouchdbHandler {
*/
find() {
const query = {
'fields': [
'_id',
'name',
'command',
'description',
'thumbnail',
'tags',
'started_date'
],
'selector': {
'type': 'apps'
'name': {
'$exists': true
}
}
}
return this.db.find(query).then((documents) => {
if (documents.warning) {
console.log(documents.warning)
}
return documents.docs
}).catch((error) => {
console.log(error)
})
}
}
export default AppsDatabase
export default AppsStore

@ -2,11 +2,11 @@ import PouchDB from 'pouchdb'
import PouchDBfind from 'pouchdb-find'
/**
* PouchdbHandler
* PouchdbHandler, for create
*
* @author Björn Hase, me@herr-hase.wtf
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.node001.net/HerrHase/super-hog
* @author Björn Hase
* @license https://www.gnu.org/licenses/gpl-3.0.en.html GPL-3
* @link https://gitea.node001.net/HerrHase/potato-launcher.git
*
*/
@ -22,14 +22,15 @@ class PouchdbHandler {
this.db = new PouchDB('.storage/pouchdb/apps', {
revs_limit: 0
})
}
// create fields for index
const fields = [
'type',
'name',
'description',
'tags'
]
/**
* adding index for current class
*
* @param {array} fields
*
*/
createIndex(fields) {
// adding index
try {
@ -41,8 +42,9 @@ class PouchdbHandler {
})
} catch (error) {
console.log(error);
console.log(error)
}
}
}

@ -0,0 +1,126 @@
import PouchdbHandler from './pouchdbHandler.js'
/**
* apps
*
* @author Björn Hase
* @license hhttps://www.gnu.org/licenses/gpl-3.0.en.html GPL-3
* @link https://gitea.node001.net/HerrHase/tellme-bot.git
*
*/
class TagsDatabase extends PouchdbHandler {
constructor() {
super()
// add index for apps
this.createIndex([
'name'
])
}
/**
*
*
* @param {object} data
*
*/
async create(tags) {
const query = {
'selector': {
'name': {
'$in': tags
}
}, 'fields': [
'name'
]
}
// check for existings tags and remove
// adding tags that are not removed
this.db.find(query).then((documents) => {
if (documents.docs && documents.docs.length > 0) {
documents.docs.forEach(data, () => {
const index = tags.indexOf(data.name)
if (index >= 0) {
tags.splice(index, 1)
}
})
}
if (tags.length > 0) {
tags.forEach((tag) => {
await this.db.post({
'name': tag
})
})
}
})
}
/**
*
*
* @param {object} data
*
*/
async remove(tags) {
const query = {
'selector': {
'name': {
'$in': tags
}
}, 'fields': [
'name'
]
}
// check for existings tags and remove
// adding tags that are not removed
this.db.find(query).then((documents) => {
if (documents.docs && documents.docs.length > 0) {
documents.docs.forEach(data, () => {
const index = tags.indexOf(data.name)
if (index >= 0) {
tags.splice(index, 1)
}
})
}
if (tags.length > 0) {
tags.forEach((tag) => {
await this.db.post({
'name': tag
})
})
}
})
}
/**
* find apps
*
* @return {mixed}
*
*/
find() {
const query = {
'fields': [
'name'
]
}
return this.db.find(query).then((documents) => {
return documents.docs
})
}
}
export default AppsDatabase

@ -24,7 +24,7 @@
],
"modes": {
"window": {
"title": "shiny-dashboard",
"title": "Potato Launcher",
"fullScreen": false,
"alwaysOnTop": false,
"icon": "/resources/icons/appIcon.png",
@ -37,7 +37,7 @@
}
},
"cli": {
"binaryName": "shiny-dashboard",
"binaryName": "potato-launcher",
"resourcesPath": "/build/",
"extensionsPath": "/extensions/",
"clientLibrary": "/resources/js/neutralino.js",
@ -46,7 +46,7 @@
},
"extensions": [
{
"id": "js.neutralino.database",
"id": "js.neutralino.pouchdb",
"command": "node ${NL_PATH}/extensions/pouchdb/index.js"
}
]

@ -9,6 +9,7 @@
<div class="potato-main">
<div class="container-full p-top-4">
<potato-apps-view></potato-apps-view>
<tiny-notification></tiny-notification>
</div>
</div>

@ -3,6 +3,7 @@ import * as riot from 'riot'
import TinyLoading from '@tiny-components/loading/src/loading.riot'
import TinySidebarFormHeader from './forms/header.riot'
import TinySidebarFormFooter from './forms/footer.riot'
import TinyNotification from '@tiny-components/notification/src/notification.riot'
import FieldError from '@tiny-components/validator/src/fieldError.riot'
@ -16,18 +17,46 @@ riot.register('field-error', FieldError)
riot.register('tiny-loading', TinyLoading)
riot.register('tiny-sidebar-form-header', TinySidebarFormHeader)
riot.register('tiny-sidebar-form-footer', TinySidebarFormFooter)
riot.register('tiny-notification', TinyNotification)
riot.register('potato-field-tags', potatoFieldTags)
riot.register('potato-apps-view', potatoAppsView)
riot.register('potato-close-button', potatoCloseButton)
// adding events for Neutralino
Neutralino.events.on('ready', () => {
Neutralino.events.on('ready', async () => {
// @TODO thats mad, it is only a workaround, don't judge me, i will do better
// its no possible to create a hole path, solving this with array and a recursive function
try {
const result = await Neutralino.filesystem.getStats('./.storage')
if (result) {
try {
await Neutralino.filesystem.createDirectory('./.storage/pouchdb')
} catch(error) {
}
}
} catch(error) {
if (error.code === 'NE_FS_NOPATHE') {
try {
await Neutralino.filesystem.createDirectory('./.storage')
await Neutralino.filesystem.createDirectory('./.storage/pouchdb')
} catch(error) {
}
}
}
riot.mount('potato-apps-view')
riot.mount('tiny-notification')
})
// let it rain
Neutralino.init()
// workaround to prevent a stack overflow
window._arrayBufferToBase64 = function _arrayBufferToBase64(buffer) {
var binary = '';

@ -1,5 +1,5 @@
<potato-apps-form-button>
<button class="button button--icon m-bottom-0" onclick={ (event) => { handleClick(event) } }>
<button class="button button--icon button--hover-icon-contrast m-bottom-0" onclick={ (event) => { handleClick(event) } }>
<svg class="icon icon--big" aria-hidden="true">
<use xlink:href="symbol-defs.svg#icon-add"></use>
</svg>
@ -11,7 +11,7 @@
import formStore from './../stores/form.js'
/**
* open sidebar form
* open sidebar form
*
* @author Björn Hase
* @license hhttps://www.gnu.org/licenses/gpl-3.0.en.html GPL-3

@ -1,5 +1,5 @@
<potato-close-button>
<button class="button button--icon button--danger m-left-5 m-bottom-0" onclick={ (event) => { handleClose(event) } }>
<button class="button button--icon button--danger button--hover-icon-contrast m-left-4 m-bottom-0" onclick={ (event) => { handleClose(event) } }>
<svg class="icon icon--big" aria-hidden="true">
<use xlink:href="symbol-defs.svg#icon-log-out"></use>
</svg>

@ -1,35 +1,40 @@
<potato-field-tags>
<div class="field-tags">
<select name="{ state.options.name }[]" multiple style="display: none;">
<option each={ tag in state.tags } value={ tag }></option>
</select>
<ul class="field-tags__list">
<label class="field-label">
<div class="field-input-group">
<input class="field-text" type="text" onkeypress={ handleKeyup } />
<button class="button button--add m-bottom-0 button--hover-icon-contrast" type="button" onclick={ () => { handleAdd() }}>
<svg class="icon"><use xlink:href="symbol-defs.svg#icon-add" /></svg>
</button>
</div>
</label>
<ul class="field-tags__list m-top-4 m-bottom-0" if={ state.tags.length > 0 }>
<li class="badge field-tags__item field-tags__item--active" each={ tag in state.tags }>
<span class="field-tags__label">{ tag }</span>
<button class="button m-bottom-0" type="button" onclick={ () => { handleRemove(tag) } }>
<button class="button m-bottom-0 button--hover-icon-contrast" type="button" onclick={ () => { handleRemove(tag) } }>
<svg class="icon"><use xlink:href="symbol-defs.svg#icon-remove" /></svg>
</button>
</li>
<li class="field-tags__item field-tags__item--add">
<div class="field-input-group">
<input class="field-text" type="text" onkeypress={ handleKeyup } />
<button class="button m-bottom-0" type="button" onclick={ handleAdd }>
<svg class="icon"><use xlink:href="symbol-defs.svg#icon-add" /></svg>
</button>
</div>
<div if={ state.errors && state.errors.length > 0 } class="field-error">
<ul>
<li each={ error in state.errors }>
{ error }
</li>
</ul>
</div>
</li>
</ul>
<div if={ state.errors && state.errors.length > 0 } class="field-error">
<ul>
<li each={ error in state.errors }>
{ error }
</li>
</ul>
</div>
</div>
<script>
import * as riot from 'riot'
import tagsStore from './../../stores/tags.js'
/**
* component field adding / remove tags for a form
@ -46,7 +51,6 @@
{
options: {
name: 'tags',
separator: ','
},
tags: [ ],
errors: [ ]
@ -58,23 +62,26 @@
*/
onBeforeMount()
{
if (this.props.options) {
this.state.options = Object.assign(this.state.options, this.props.options)
}
},
// if props.tags is set and it is not a array, split it with a
// separator and add to tags
if (this.props.tags) {
let tags = this.props.tags
/**
*
*
*/
onMounted() {
tagsStore.on('update', (data) => {
if (!Array.isArray(tags)) {
tags = tags.split(this.state.options.separator)
// if no data is send, reset array
if (!data) {
data = []
}
this.state.tags = tags;
}
if (this.props.options) {
this.state.options = Object.assign(this.state.options, this.props.options)
}
this.state.tags = data
this.update()
})
},
/**
@ -90,7 +97,7 @@
event.preventDefault()
// trigger to add tag
this.$('.field-tags__item--add .button').click()
this.$('.button--add').click()
}
},

@ -5,6 +5,8 @@
<!-- header -->
<tiny-sidebar-form-header title="New" close={ (event) => { handleClose(event) }}></tiny-sidebar-form-header>
<div class="sidebar__form">
<!-- body -->
<form class="form" novalidate method="post" onsubmit={ (event) => ( state.validator.submit(event) ) }>
<div class="sidebar__body">
@ -23,7 +25,7 @@
command*
<div class="field-input-group">
<input class="field-text" type="text" name="command" value="{ state.current.command ? state.current.command : '' }" />
<button class="button m-bottom-0" type="button" onclick={ handleSelectProgram }>
<button class="button button--hover-icon-contrast m-bottom-0" type="button" onclick={ handleSelectProgram }>
<svg class="icon">
<use xlink:href="symbol-defs.svg#icon-folder" />
</svg>
@ -38,7 +40,7 @@
<div class="field-group">
<label class="field-label">
tags
<potato-field-tags name="tags" value={ state.current.tags ? state.current.tags : '' }></potato-field-tags>
<potato-field-tags name="tags"></potato-field-tags>
</label>
<field-error name="tags[]"></field-error>
</div>
@ -54,24 +56,24 @@
<!-- media -->
<div class="field-group">
<label class="field-label">
<button class="button button--upload m-top-3 w-100" type="button" onclick={ (event) => { handleSelectMedia(event) }}>
Add Image
<svg class="icon m-left-3">
Thumbnail<br />
<button class="button button--hover-icon-contrast m-top-3" type="button" onclick={ (event) => { handleSelectThumbnail(event) }}>
<svg class="icon">
<use xlink:href="symbol-defs.svg#icon-image" />
</svg>
</button>
</label>
<div class="field-media" if={ state.current.media }>
<button class="button button--small" type="button" onclick={ (event) => { handleRemoveMedia(event) }}>
<svg class="icon">
<use xlink:href="symbol-defs.svg#icon-delete" />
</svg>
</button>
<div class="field-media" if={ state.current.thumbnail }>
<div class="field-media__img">
<img src={ toImage(state.current.media) } />
<button class="button button--hover-icon-contrast" type="button" onclick={ (event) => { handleRemoveThumbnail(event) }}>
<svg class="icon">
<use xlink:href="symbol-defs.svg#icon-delete" />
</svg>
</button>
<img class="w-100" src={ toImage(state.current.thumbnail) } />
</div>
</div>
<field-error name="media"></field-error>
<field-error name="thumbnail"></field-error>
</div>
<tiny-loading active={ state.loading }></tiny-loading>
@ -80,6 +82,7 @@
<!-- footer -->
<tiny-sidebar-form-footer id={ state.current.id ? state.current.id : false } loading={ state.loading }></tiny-sidebar-form-footer>
</form>
</div>
</div>
</div>
@ -97,6 +100,9 @@
// store for sidebar
import appsStore from './../stores/apps.js'
// store for tags
import tagsStore from './../stores/tags.js'
// validator
import FormValidator from '@tiny-components/validator/src/formValidator.js'
@ -118,12 +124,20 @@
*
*
*/
async onMounted() {
onMounted() {
// adding event for open sidebar
formStore.on('open', () => {
this.state.open = true
this.update()
formStore.on('open', (id) => {
// if id is send, load apps from pouchdb
if (id) {
Neutralino.extensions.dispatch('js.neutralino.pouchdb', 'pouchdb.apps.findOne', {
'id': id
})
}
this.state.open = true
this.update()
})
// creating formValidator
@ -144,6 +158,27 @@
this.handleSuccess(event, data)
})
// receive from extensions database
Neutralino.events.on('pouchdb.apps.success', (event) => {
appsStore.trigger('updated')
// stop loading
this.state.loading = false
this.update()
})
// receive from extensions database
Neutralino.events.on('pouchdb.apps.readyOne', (event) => {
this.state.current = event.detail
tagsStore.trigger('update', this.state.current.tags)
// stop loading
this.state.loading = false
this.update()
})
},
@ -160,7 +195,19 @@
'defaultPath': '/home/'
})
this.current.command = entry
this.state.current.command = entry
this.update()
},
/**
*
*
*
*/
handleRemoveThumbnail(event) {
event.preventDefault()
this.state.current.thumbnail = null
this.update()
},
@ -176,20 +223,11 @@
this.state.loading = true
this.update()
// receive from extensions database
Neutralino.events.on('pouchdb.apps.success', (event) => {
appsStore.trigger('updated')
// stop loading
this.state.loading = false
this.update()
})
data.media = this.state.current.media
// adding thumbnail
data.thumbnail = this.state.current.thumbnail
// send to extensions database
Neutralino.extensions.dispatch('js.neutralino.database', 'pouchdb.apps.create', data)
Neutralino.extensions.dispatch('js.neutralino.pouchdb', 'pouchdb.apps.create', data)
},
/**
@ -218,16 +256,20 @@
this.update()
},
toImage(media) {
return 'data:image/png;base64,' + media
},
/**
*
*
* @param {object} media
*
*/
toImage(media) {
return 'data:image/png;base64,' + media
},
reset() {
this.state.current = {
}
}
}
}

@ -9,7 +9,7 @@
<button class="button m-bottom-0" type="submit" disabled={ props.loading } close>
Save
<svg class="icon fill-success p-left-3" aria-hidden="true">
<use xlink:href="symbol-defs.svg#icon-arrow-right"></use>
<use xlink:href="symbol-defs.svg#icon-check"></use>
</svg>
</button>
</div>

@ -4,8 +4,8 @@
{ props.title }
</div>
<div class="bar__end">
<button class="button button--transparent" type="button" onclick={ props.close }>
<svg class="icon fill-danger fill-text-hover" aria-hidden="true">
<button class="button button--icon button--hover-icon-contrast m-top-3 m-bottom-3" type="button" onclick={ props.close }>
<svg class="icon fill-danger" aria-hidden="true">
<use xlink:href="/symbol-defs.svg#icon-close"></use>
</svg>
</button>

@ -7,6 +7,8 @@
*
*/
import * as riot from 'riot'
export default {
state: {
@ -22,9 +24,9 @@ export default {
*
*/
handleClose() {
this.state.open = false
this.reset()
this.state.open = false
this.update()
},

@ -7,16 +7,17 @@
*
*/
import observable from '@riotjs/observable'
import observable from '@riotjs/observable'
export default observable({
export default observable({
/**
*
* @param {object} data
*
*/
open() {
this.trigger('open')
}
})
open(id = null) {
this.trigger('open', id)
}
})

@ -0,0 +1,14 @@
/**
* Store for apps
*
* @author Björn Hase
* @license hhttps://www.gnu.org/licenses/gpl-3.0.en.html GPL-3
* @link https://gitea.node001.net/HerrHase/potato-launcher.git
*
*/
import observable from '@riotjs/observable'
export default observable({
})

@ -4,7 +4,7 @@
<header class="header m-bottom-3">
<div class="display-flex justify-content-space-between m-top-3">
<div>
<button class="button button--icon m-bottom-0">
<button class="button button--icon button--hover-icon-contrast m-bottom-0">
<svg class="icon icon--big">
<use xlink:href="symbol-defs.svg#icon-filter" />
</svg>
@ -22,7 +22,7 @@
<div class="col-12 col-md-3 col-lg-2" each={ app in state.apps }>
<div class="apps__item" onclick={ (event) => { handleClick(event, app) }} style="{ addBackgroundStyles(app) }">
<div class="apps__header p-4 right">
<button class="button button--transparent p-2" onclick={ (event) => { handleEdit(event, app) }}>
<button class="button button--icon button--hover-icon-contrast" onclick={ (event) => { handleEdit(event, app) }}>
<svg class="icon">
<use xlink:href="symbol-defs.svg#icon-settings" />
</svg>
@ -53,6 +53,9 @@
import potatoAppsForm from './../forms/apps.riot'
import potatoAppsFormButton from './../components/appsFormButton.riot'
import appsStore from './../stores/apps.js'
import formStore from './../stores/form.js'
import notificationStore from '@tiny-components/notification/src/notificationStore.js'
// let it rain
riot.register('potato-apps-form', potatoAppsForm)
@ -62,7 +65,7 @@
riot.mount('potato-apps-form-buttons')
/**
*
*
*
* @author Björn Hase
* @license hhttps://www.gnu.org/licenses/gpl-3.0.en.html GPL-3
@ -94,7 +97,7 @@
// send event to get all after
appsStore.on('updated', () => {
Neutralino.extensions.dispatch('js.neutralino.database', 'pouchdb.apps.find')
Neutralino.extensions.dispatch('js.neutralino.pouchdb', 'pouchdb.apps.find')
})
// trigger event to get all apps
@ -109,10 +112,17 @@
* @param {object} app
*
*/
handleClick(event, app) {
Neutralino.os.execCommand(app.command).catch((error) => {
async handleClick(event, app) {
try {
const state = await Neutralino.os.execCommand(app.command)
// if there is a error message display send a notification
if (state.stdErr) {
notificationStore.danger(state.stdErr)
}
} catch(error) {
console.log(error)
})
}
},
/**
@ -123,15 +133,20 @@
*
*/
handleEdit(event, app) {
console.log(app)
event.stopPropagation()
formStore.open(app._id)
},
/**
*
*
*/
addBackgroundStyles(app) {
let styles = ''
let styles = false
if (app.media) {
styles = "background-image: url('data:image/jpg;base64," + app.media + "')"
styles = "background-image: url('data:image/jpg;base64," + app.thumbnail + "')"
}
return styles

@ -0,0 +1,3 @@
.justify-content-space-between {
justify-content: space-between;
}

@ -0,0 +1,9 @@
/**
* modifications for badge
*
*
*/
.badge {
padding: .5rem .6rem;
}

@ -0,0 +1,6 @@
/**
* modifications for bar
*
*
*
*/

@ -7,4 +7,12 @@
&--icon {
padding: 0.5rem 0.6rem;
}
&--hover-icon-contrast {
&:hover {
.icon {
fill: white;
}
}
}
}

@ -9,7 +9,6 @@
&__list {
list-style: none;
padding: 0;
margin: 0;
}
&__item {
@ -34,4 +33,8 @@
}
}
}
&__label {
vertical-align: bottom;
}
}

@ -1,7 +1,14 @@
/**
* modifications for sidebar
*
*
*/
.field {
&-error {
ul {
list-style: square;
margin: 0;
padding-left: 1.25em;
}
}
@ -14,4 +21,17 @@
margin-top: 0.7em;
}
}
&-media {
&__img {
position: relative;
button {
position: absolute;
right: 0;
top: 0;
margin: 1em;
}
}
}
}

@ -0,0 +1,23 @@
/**
* modifications for sidebar
*
*
*/
.sidebar {
&__form {
height: 100%;
overflow-y: scroll;
padding-bottom: calc(72px + 1.5em); // 72px is the minium height for footer
}
&__footer {
bottom: -1px;
}
.bar {
// alpha in neutrolino.js not working
border-radius: 0;
}
}

@ -1,12 +1,13 @@
@import
'~@tiny-components/plain-ui/src/scss/plain-ui',
'helpers',
'components/apps',
'components/badge',
'components/sidebar',
'components/field',
'components/field-tags',
'components/buttons',
'components/icons';
.justify-content-space-between {
justify-content: space-between;
}
Loading…
Cancel
Save