Last week there was a temporary error with email sending that lead to GitLab notification emails not being delivered. The problem should be solved now - however, failed notification emails are not being resent!

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>
This diff is collapsed.
<template lang="pug">
q-list.default-source-container
q-list-header
q-item
small Standard Cell Types
q-item-separator
template(v-for="(item, i) in items")
template(v-if="i > 0")
q-item-separator
q-item(
draggable="true",
@dragstart.native="event => {handleItemDragStart(event, item)}")
q-field(
:icon="typeToIconName(item.type)",
:helper="item.help",
:error="item.error",
:error-label="item.errorMessage",
style="width: 100%")
q-input(
:float-label="item.label",
:type="item.inputType",
:min-rows="1",
:max-height="500",
:value="item.value",
:model="item.value",
@input="value => {handleItemChanged(value, item)}",
@change="value => {handleItemChanged(value, item)}")
</template>
<script>
const typeToIconName = {
'image': 'photo',
'iframe': 'picture in picture',
'internal-link': 'link',
'video': 'local movies',
'text': 'subject',
'title': 'title'
}
export default {
data () {
return {
items: [
{
inputType: 'text',
type: 'Title',
label: 'Title Cell',
help: '',
error: false,
errorMessage: '',
value: ''
},
{
inputType: 'textarea',
type: 'Text',
label: 'Text Cell',
help: '',
error: false,
errorMessage: '',
value: ''
},
{
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: ''
},
{
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: ''
},
{
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: ''
},
{
inputType: 'url',
type: 'IFrame',
label: 'IFrame Cell',
help: 'Insert some URL',
error: false,
errorMessage: 'Needs to be a valid URL',
value: ''
}
]
}
},
methods: {
typeToIconName (type) {
let iconName = typeToIconName[type.toLowerCase()]
if (!iconName) {
iconName = 'broken image'
}
return iconName
},
handleItemDragStart (event, item) {
// FIXME: manually setting the item.value because data-binding fails for some reason and change is triggered too late (after drag started)
const inputField = event.target.querySelector('input')
if (inputField) {
inputField.blur()
this.handleItemChanged(inputField.value, item)
}
const resourceCell = {
uuid: null,
type: item.type,
x: 1,
y: 1,
width: 1,
height: 1,
content: item.value
}
event.dataTransfer.setData('text/plain', JSON.stringify(resourceCell))
},
handleItemChanged (value, item) {
if (item.inputType !== 'url') {
item.value = value
}
else {
item.error = !(/^http[s]?:\/\/.+/.test(value))
if (!item.error) {
item.value = value
}
}
}
}
}
</script>
<style scoped lang="stylus">
.q-list-header
padding-left 0
.q-input
padding-left 1em
padding-right 1em
.q-input:before
.q-input:after
display none
.q-input
padding-left 0
</style>
<template lang="pug">
q-list.default-source-container
q-list-header
q-input(
v-model="term", type="textarea", :min-rows="1",
:max-height="200", :style="{'line-height': '1.5em', width: '100%'}")
q-item-separator
template(v-if="results.length === 0")
q-item This is the magic box, just enter:
q-item ● A tag starting with a hash-symbol: #[span #my tag]
q-item ● A URL pointing to a video, image or internal or external page
q-item ● Short or long text
// loading, nothing here
template(v-if="loading")
q-item
q-item-side
q-spinner(style="margin-right: 1em")
q-item-main Loading videos for »{{term}}«
q-item-separator
template(v-else-if="nothingFound")
q-item No Videos Found!
// videos, annotations
template(v-if="results")
template(v-for="(result, i) in results")
template(v-if="i > 0")
q-item-separator
q-item(draggable="true", @dragstart.native="event => {handleItemDragStart(event, result)}")
q-item-side
q-icon(:name="typeToIconName(result.body.type)", style="font-size: 1.8rem")
q-item-main
a(@click.prevent="event => {handleItemClick(event, result)}")
template(v-if="result.body.source") {{result.body.source.substring(0, 100)}}
template(v-if="result.body.source.length > 100") ...
template(v-else-if="result.body.value") {{result.body.value.substring(0, 100)}}
template(v-if="result.body.value.length > 100") ...
template(v-else) Hm, no title?
</template>
<script>
import url from 'url'
import { ObjectUtil } from 'mbjs-utils'
const hostToTypeMap = {
'vimeo.com': 'Video',
'www.vimeo.com': 'Video',
'youtube.com': 'Video',
'www.youtube.com': 'Video'
}