Commit 2fe19a0b authored by Anton's avatar Anton

Intial

parents
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
# Node.js
node_modules
bower_components
*.log
*.db
*.nedb
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
cmake-build-release/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Book build output
_book
# eBook build output
*.epub
*.mobi
*.pdf
# JetBrains
.idea
# nyc coverage
.nyc_output
# Quasar Components - MoSys
> Set of components used by the Motion Bank MoSys app
<template lang="pug">
.cell-item-inner(:class="{'display-preview': preview, 'display-full': display}")
template(v-if="cellTypeName")
component(
:is="cellTypeName",
:cell="cell",
:class="'cell-content cell-type-' + cell.type.toLowerCase()",
:display="display",
:preview="preview",
:messenger="messenger")
template(v-else)
div Empty cell
slot
</template>
<script>
import cellTypes from './cells'
export default {
props: ['cell', 'display', 'preview', 'messenger', 'annotation'],
data () {
return {
selected: false
}
},
computed: {
cellTypeName () {
return this.cell.type ? this.cellTypeToClassName(this.cell.type) : ''
}
},
mounted () {
if (this.cellTypeName && cellTypes.hasOwnProperty(this.cellTypeName) === false) {
throw new Error('This cell type is missing: ' + this.cell.type)
}
},
methods: {
cellTypeToClassName (type) {
return 'Cell' + type.slice(0, 1).toUpperCase() + type.slice(1).replace(/[^a-z0-9]/ig, '')
},
handleClick () {
this.selected = !this.selected
},
handleDragStart (event) {
event.dataTransfer.setData('text/plain', JSON.stringify(this.cell))
}
},
components: cellTypes
}
</script>
<style scoped lang="stylus">
.display-preview .cell-content
pointer-events none
.cell-content
h1, h2, h3, h4, h5, h6
font-size: 1em
font-weight: normal
a, a:hover, a:visited
color: black
text-decoration: underline
</style>
<template lang="pug">
q-list
q-list-header
q-item
small Cell Content Editor
q-item-separator
template(v-for="(cell, index) in cells")
template(v-if="index > 0")
q-item-separator
q-item
q-field(
v-if="cell.type in itemSpecs",
:icon="typeToIconName[cell.type]",
:helper="itemSpecs[cell.type].help",
:error="itemSpecs[cell.type].error",
:error-label="itemSpecs[cell.type].errorMessage",
style="width: 100%")
q-input(
:float-label="itemSpecs[cell.type].label",
:type="itemSpecs[cell.type].inputType",
:min-rows="1",
:max-height="500",
:value="cell.content",
@change="value => {handleItemChanged(value, cell)}")
q-field(v-else) This cell type is not supported yet: {{ cell.type }}
</template>
<script>
export default {
props: ['cells'],
data () {
return {
typeToIconName: {
'Image': 'photo',
'IFrame': 'picture in picture',
'Internal-Link': 'link',
'Video': 'local movies',
'Text': 'subject',
'Title': 'title'
},
itemSpecs: {
'Title': {
inputType: 'text',
type: 'Title',
label: 'Title Cell',
help: '',
error: false,
errorMessage: '',
value: ''
},
'Text': {
inputType: 'textarea',
type: 'Text',
label: 'Text Cell',
help: '',
error: false,
errorMessage: '',
value: ''
},
'Video': {
inputType: 'url',
type: 'Video',
label: 'Video Cell',
help: 'Insert a URL to: a video file or a Vimeo / YouTube video page',
error: false,
errorMessage: 'Needs to be a valid URL',
value: ''
},
'Image': {
inputType: 'url',
type: 'Image',
label: 'Image Cell',
help: 'Insert a URL to an image file',
error: false,
errorMessage: 'Needs to be a valid URL',
value: ''
},
'Internal-Link': {
inputType: 'url',
type: 'Internal-Link',
label: 'Link Cell',
help: 'Insert a URL to a page in this system',
error: false,
errorMessage: 'Needs to be a valid URL',
value: ''
},
'IFrame': {
inputType: 'url',
type: 'IFrame',
label: 'IFrame Cell',
help: 'Insert some URL',
error: false,
errorMessage: 'Needs to be a valid URL',
value: ''
},
'Annotation-List': {
inputType: 'text',
type: 'Annotation-List',
label: 'Annotation List Cell',
help: 'Insert a Video UUID',
error: false,
errorMessage: '',
value: ''
}
}
}
},
// mounted () {
// console.log(this.$props.cells)
// },
methods: {
handleItemChanged (value, cell) {
if (cell.inputType !== 'url') {
this.updateCellContent(value, cell)
}
else {
cell.error = !(/^http[s]?:\/\/.+/.test(value))
if (!cell.error) {
this.updateCellContent(value, cell)
}
}
},
updateCellContent (value, cell) {
const _this = this
// can't remember why the extra sourceUuid is needed …
// maybe if this is an annotation made in piecemaker (video),
// that is wrapped into a cell-annotation
const destUuid = cell.sourceUuid || cell.uuid
if (destUuid) {
this.$store.dispatch('annotations/find', {'uuid': destUuid})
.then(annotations => {
const a = annotations.items.shift()
cell.content = value
a.body.value = JSON.stringify(cell)
_this.$store.dispatch('annotations/patch', [destUuid, {body: a.body, target: a.target}])
})
}
}
}
}
</script>
<style scoped lang="stylus"></style>
<template lang="pug">
div.cell-grid-container
div.cell-grid(:style="gridStyle")
template(v-for="(cell, index) in cells")
.cell-item(
:style="getCellStyle(cell)",
:title="cell.title")
cell(:cell="cell", display="display", :messenger="messenger")
q-page-sticky(position="top-right", :offset="[18, 18]", v-if="$store.state.auth.user")
q-btn(round, color="primary", small, @click="$router.push(`/mosys/grids/${$route.params.id}/annotate`)")
q-icon(name="edit")
</template>
<script>
import Vue from 'vue'
import Cell from './Cell'
const MessengerComponent = Vue.component('grid-editor-messenger', {})
export default {
components: {
Cell
},
props: ['gridUuid'],
data () {
return {
grid: undefined,
annotations: [],
cells: [],
gridMetadata: {},
gridDimensions: {gridWidth: 0, gridHeight: 0, cellWidth: 0, cellHeight: 0},
gridStyle: {},
messenger: new MessengerComponent()
}
},
async mounted () {
const _this = this
window.addEventListener('resize', this.updateGridDimensions)
// this.messenger.$on('video-loaded', (/* origin */) => {
// // console.log('video loaded', origin.origin)
// })
// this.messenger.$on('video-time-changed', (/* time, globalTime, origin */) => {
// // console.log(videoUuid, time)
// })
this.grid = await this.$store.dispatch('maps/get', this.$route.params.id)
await this.fetchMetadataAnnotations()
this.updateGridDimensions()
_this.fetchCellAnnotations()
},
methods: {
fetchCellAnnotations () {
const _this = this,
query = { 'body.type': '2DCell', 'target.id': this.grid.id }
this.$store.dispatch('annotations/find', query)
.then(annotations => {
_this.annotations = annotations.items
_this.cells = _this.annotations.map(annotation => {
let cell = JSON.parse(annotation.body.value)
if (cell) {
cell.uuid = annotation.uuid
return cell
}
return null
}).filter(cell => cell)
})
},
async fetchMetadataAnnotations () {
const _this = this
const query = { 'body.type': '2DGridMetadata', 'target.id': this.grid.id }
const annotations = await this.$store.dispatch('annotations/find', query)
let annotation = annotations.items.shift()
if (annotation) {
let metadata = JSON.parse(annotation.body.value)
metadata.uuid = annotation.uuid
if (metadata) {
_this.gridMetadata = metadata
}
}
else {
_this.gridMetadata = {
columns: 10,
rows: 6,
ratio: 16 / 9.0
}
_this.updateGridMetadataStore()
}
},
async updateCellStore (cell) {
const _this = this
let annotation = this.getGridCellAnnotation(cell)
if (cell.uuid) {
await _this.$store.dispatch('annotations/patch', [cell.uuid, annotation])
}
else {
await _this.$store.dispatch('annotations/post', annotation)
}
},
async updateGridMetadataStore () {
const _this = this
let mapAnnotation = this.getGridMetadataAnnotation(this.grid.id, this.gridMetadata)
if (_this.gridMetadata.uuid) {
await _this.$store.dispatch('annotations/patch', [_this.gridMetadata.uuid, mapAnnotation])
}
await _this.$store.dispatch('annotations/post', mapAnnotation)
_this.updateGridDimensions()
},
updateGridDimensions () {
let elWidth = this.$el.offsetWidth
let elHeight = this.$el.offsetHeight
let cellSizeRatio = this.gridMetadata.ratio
let gridHeight = elHeight
let cellHeight = gridHeight / this.gridMetadata.rows
let cellWidth = elWidth / Math.round(elWidth / (cellHeight * cellSizeRatio))
let gridWidth = cellWidth * this.gridMetadata.columns
let cellsPerWidth = elWidth / cellWidth
let cellWidthMini = elWidth / this.gridMetadata.columns
let gridHeightMini = cellWidthMini / cellSizeRatio
this.gridDimensions = {
full: {
width: gridWidth,
height: gridHeight,
cell: {
width: cellWidth,
height: cellHeight
},
cells_per_width: cellsPerWidth
},
mini: {
width: elWidth,
height: gridHeightMini * this.gridMetadata.rows,
cell: {
width: cellWidthMini,
height: gridHeightMini
}
}
}
if (elWidth > 800) {
this.gridStyle = {
width: this.gridDimensions.full.width + 'px',
height: '100%',
'grid-auto-columns': this.gridDimensions.full.cell.width + 'px',
'grid-auto-rows': this.gridDimensions.full.cell.height + 'px'
}
}
else {
this.gridStyle = {
width: '100%',
height: this.gridDimensions.mini.height + 'px',
'grid-auto-columns': this.gridDimensions.mini.cell.width + 'px',
'grid-auto-rows': this.gridDimensions.mini.cell.height + 'px'
}
}
},
getGridMetadataAnnotation (id, metadata) {
return {
body: {
type: '2DGridMetadata',
purpose: 'linking',
value: JSON.stringify(metadata)
},
target: {
id,
type: 'Map'
}
}
},
getCellStyle (cell) {
return {'grid-column-start': cell.x, 'grid-column-end': `span ${cell.width}`, 'grid-row-start': cell.y, 'grid-row-end': `span ${cell.height}`}
}
}
}
</script>
<style scoped lang="stylus">
.cell-grid
display grid
.cell-item
position relative
overflow: hidden
grid-column-start: 1
grid-column-end: span 1
grid-row-start: 1
grid-row-end: span 1
.cell-item-inner
width 100%
height 100%
</style>
<template lang="pug">
div.cell-grid-container
div.cell-grid(
@dragenter="handleGridDragOver",
@dragover="handleGridDragOver",
@dragleave="handleGridDragEnd",
@drop="handleGridDrop",
@contextmenu="handleGridContextMenu",
:style="gridStyle")
q-context-menu(ref="gridmenu")
q-list(link, separator, no-border, style="min-width: 150px; max-height: 300px;")
q-item(
v-for="action in gridContextMenuActions",
:key="action.label",
v-close-overlay,
@click.native="event => {action.handler(event)}")
q-item-main(:label="action.label")
template(v-if="!resizingGrid")
template(v-for="(cell, index) in cells")
.cell-item(
v-if="cellUIStates[cell.uuid] && !cellUIStates[cell.uuid].beingDragged",
draggable="true",
@dragstart="event => {handleCellDragStart(event, cell)}",
@dragend="event => {handleCellDragEnd(event, cell)}",
@contextmenu="handleCellContextMenu",
:style="getCellStyle(cell)",
:title="cell.title",
@click.prevent="event => {handleCellClick(event, cell)}",
:class="{selected: cellUIStates[cell.uuid] ? cellUIStates[cell.uuid].selected : false}",
:key="`cell-${index}`")
cell(:cell="cell", preview)
div.cell-item-resize-handle(
draggable="true",
@dragstart="event => {handleCellResizerDragStart(event, cell)}",
@dragend="event => {handleCellResizerDragEnd(event, cell)}",
@dragexit="event => {handleCellResizerDragEnd(event, cell)}")
q-icon(name="network cell")
q-context-menu
q-list(link, separator, no-border, style="min-width: 150px; max-height: 300px;")
q-item(
v-for="action in cellContextMenuActions",
:key="action.label",
v-close-overlay,
@click.native="event => {action.handler(event, cell)}")
q-item-main(:label="action.label")
template(v-for="(tmpCell, index) in tmpCells")
.cell-item.cell-item-tmp(:style="getCellStyle(tmpCell)", :key="`cell-tmp-${index}`")
cell(:cell="tmpCell")
template(v-else)
.cell-item(:style="getCellStyle({x:0,y:0,width:1,height:1})", key="cell-grid-resizer")
div.cell-item-resize-handle(
draggable="true",
@dragstart="event => {handleGridResizerDragStart(event)}",
@dragend="event => {handleGridResizerDragEnd(event)}",
@dragexit="event => {handleGridResizerDragEnd(event)}")
q-icon(name="network cell")
div.fixed-top-right(style="right:18px; top:68px", v-if="!$store.state.mosysGridEditorStore.showSources")
q-btn(round, color="primary", small, @click="handleGridButtonClickEdit", style="margin-right: 0.5em")
q-icon(name="add")
q-btn(round, color="primary", small, @click="$router.push(`/mosys/grids/${$route.params.id}`)")
q-icon(name="remove red eye")
</template>
<script>
import { ObjectUtil } from 'mbjs-utils'
import Cell from './Cell'
const nullImage = new Image()
nullImage.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
export default {
components: {
Cell
},
props: ['gridUuid'],
data () {
return {
cellContextMenuActions: {
// edit: {
// label: 'Edit',
// handler: this.handleCellContextMenuEdit
// },
delete: {
label: 'Delete',
handler: this.handleCellContextMenuDelete
},
insert_column_left: {
label: 'Insert Column Left',
handler: this.handleGridContextMenuInsertColumnLeft
},
insert_row_above: {
label: 'Insert Row Above',
handler: this.handleGridContextMenuInsertRowAbove
}
},
gridContextMenuActions: {
// add_cell: {
// label: 'Add Cell',
// handler: this.handleGridContextMenuAddCell
// },
insert_column_left: {
label: 'Insert Column Left',
handler: this.handleGridContextMenuInsertColumnLeft
},
delete_column: {
label: 'Delete Column',
handler: this.handleGridContextMenuDeleteColumn
},
insert_row_above: {
label: 'Insert Row Above',
handler: this.handleGridContextMenuInsertRowAbove
},
delete_row: {
label: 'Delete Row',
handler: this.handleGridContextMenuDeleteRow
},
edit_grid_dimensions: {
label: 'Change Grid',
handler: () => { this.resizingGrid = !this.resizingGrid }
}
},
grid: undefined,
gridMetadata: {},
cells: [],
tmpCells: [],
cellUIStates: {},
gridDimensions: {gridWidth: 0, gridHeight: 0, cellWidth: 0, cellHeight: 0},
gridStyle: {},
contextMenuClickPosition: {},
resizingGrid: false
}
},
async mounted () {
window.addEventListener('resize', this.updateGridDimensions)
await this.fetchData()
},
beforeDestroy () {
window.removeEventListener('resize', this.updateGridDimensions)
},
watch: {
cells () {
this.updateCellUIStates()
},
gridMetadata () {
this.updateGridDimensions()
},
async gridUuid () {
await this.fetchData()
}
},
methods: {
async fetchData () {
if (this.gridUuid) {
this.grid = await this.$store.dispatch('maps/get', this.gridUuid)
await this.fetchMetadataAnnotations()
this.updateGridDimensions()
await this.fetchCellAnnotations()
}
},
handleGridResizerDragStart (event) {
event.dataTransfer.setDragImage(nullImage, 0, 0)
},
async handleGridResizerDragEnd () {
await this.updateGridMetadataStore()
},
handleCellResizerDragStart (event, cell) {
event.dataTransfer.setDragImage(nullImage, 0, 0)
this.cellUIStates[cell.uuid].beingResized = true
let tmpCell = this.getTmpCell(cell)
this.tmpCells.push(tmpCell)
},
handleCellResizerDragEnd (event, cell) {
let position = this.getGridPositionForEvent(event)
cell.width = Math.max(1, 1 + position.x - cell.x)
cell.height = Math.max(1, 1 + position.y - cell.y)
this.cellUIStates[cell.uuid].beingResized = false
this.tmpCells = []
this.updateCellStore(cell)
},
handleCellClick (event, cell) {
this.cellUIStates[cell.uuid].selected = !this.cellUIStates[cell.uuid].selected
this.updateSelectedCells()
},
handleCellDragStart (event, cell) {
if (this.cellUIStates[cell.uuid].beingResized) {
}
else {
event.dataTransfer.setData('text/plain', JSON.stringify(cell))
event.dataTransfer.setDragImage(nullImage, 0, 0)
let elContainerBoundingBox = this.$el.getBoundingClientRect()
let elBoundingBox = event.srcElement.getBoundingClientRect()
let offset = {
x: (event.clientX - elContainerBoundingBox.x) - (elBoundingBox.x - elContainerBoundingBox.x),
y: (event.clientY - elContainerBoundingBox.y) - (elBoundingBox.y - elContainerBoundingBox.y)
}
this.cellUIStates[cell.uuid].draggingOffset = offset
this.cellUIStates[cell.uuid].beginDragged = true
}