Commit d41b3714 authored by Christian Hansen's avatar Christian Hansen Committed by Anton Koch

mobile: working dummy "move new cell"-handler when main "move new...

mobile: working dummy "move new cell"-handler when main "move new cell"-handler is outside the viewport
parent 43d60647
......@@ -2,7 +2,7 @@
.cell-item-inner(:class="{'display-preview': preview, 'display-full': display}")
template(v-if="cell")
component(
component.animated-opacity(
:is="cell.component",
:cell="cell",
:class="cellClasses",
......@@ -10,8 +10,13 @@
:preview="preview",
:visible="inViewPort")
template(v-else-if="temp")
// strong.text-dark New Cell
template(v-else)
div Empty cell
div.q-pa-sm(:class="[display === 'display' ? 'text-grey-3' : 'text-grey-1']")
q-spinner
// | Empty cell
slot
......@@ -21,7 +26,7 @@
import cellTypes from './cells'
export default {
props: ['annotation', 'display', 'preview'],
props: ['annotation', 'display', 'preview', 'temp'],
data () {
return {
cell: undefined,
......@@ -93,4 +98,10 @@
color: black
text-decoration: underline
@keyframes animated-opacity {
from { opacity: 0 }
to { opacity: 1 }
}
.animated-opacity
animation animated-opacity ease 500ms
</style>
<template lang="pug">
q-list
div.q-px-md.q-pb-md.q-pt-sm
template(v-for="(cell, index) in cellsData", :key="cell._uuid")
q-list.q-py-none.ani-opacity(v-for="(cell, index) in cellsData", :key="cell._uuid")
//q-item-separator(v-if="index > 0")
//----- header
// q-item.q-py-sm(:class="[isMobile ? 'q-pr-sm' : 'q-pr-none']")
q-item.q-pa-none.q-mb-sm(:class="[isMobile ? '' : 'q-py-xs q-mt-sm']")
q-item-main.text-dark
strong {{ itemSpecs[cell.component][0].type }}
<!--q-item(v-if="cell.type in itemSpecs", v-for="spec in itemSpecs[cell.type]", :key="`${spec.type}-${spec.path}`")-->
q-item(v-for="spec in itemSpecs[cell.component]", :key="`${spec.component}-${spec.path}`",
v-if="cell.data.path === spec.path")
q-item-side.mobile-only.text-dark.text-right
q-item-main
// delete-button
q-btn.q-pa-sm(@click="deleteCell(cells[index])", size="xs", flat, style="border-radius: .5rem;")
q-icon(name="delete", size="20px")
// preview images
q-item-tile
img.full-width(v-if="cell.component === 'CellImage'", :src="cell.data.content")
// close-button
// :class="[isMobile ? 'q-mr-xs' : '']")
q-btn.q-pa-sm.on-right(@click="closeHandler()", size="xs", flat,
style="border: 1px solid #ddd; border-radius: .5rem;")
q-icon(name="clear", size="20px")
q-item-tile
//----- preview image
q-item.q-pa-none.q-mx-none.q-mb-md(v-if="cell.component === 'CellImage'",
:class="[isMobile ? '' : 'q-mt-sm']")
img(:src="cell.data.content", style="max-height: 50vh; max-width: 100%; margin: 0 auto;")
//----- input-field
q-item.q-pa-none(v-for="spec in itemSpecs[cell.component]", :key="`${spec.component}-${spec.path}`",
v-if="cell.data.path === spec.path",
:class="[isMobile ? '' : 'q-mt-sm']")
q-item-main.bg-grey-2.q-py-sm(style="border-radius: .35rem;")
q-item-tile.q-px-md
q-field(
:icon="typeToIconName[spec.type]",
:helper="spec.help",
:error="spec.error",
:error-label="spec.errorMessage",
......@@ -29,31 +46,38 @@
:options="spec.selectOptions",
@select="value => handleItemChanged(value, cell, spec.path)")
template(v-else)
//q-input(
// :float-label="spec.label",
// :type="spec.inputType",
// :min-rows="1",
// :max-height="500",
// :value="cell[spec.path]",
// @change="value => handleItemChanged(value, cell, spec.path)")
q-input(
:float-label="spec.label",
:type="spec.inputType",
:min-rows="1",
:max-height="500",
:value="cell.data[spec.path]",
@change="value => handleItemChanged(value, cell, spec.path)")
q-item-separator(v-if="index < cellsData.length - 1")
@change="value => handleItemChanged(value, cell, spec.path)",
hide-underline)
//----- media-/annotation-button
// q-item.q-pa-none(v-if="['CellMedia', 'CellAnnotationList'].includes(cell.component)")
q-item.q-pa-none.q-mt-md(v-if="cell.component === 'CellMedia' || cell.component === 'CellAnnotationList'")
//----- media
q-btn.bg-primary.text-white.media-button(@click="", flat)
q-icon(name="local movies", size="22px")
//----- annotations
q-btn.bg-primary.text-white.media-button.q-ml-sm(@click="", flat)
q-icon(name="comment", size="22px")
// q-item(v-else)
q-field This cell type is not supported yet: {{ cell.component }}.{{ spec.path }}
</template>
<script>
import { mapGetters } from 'vuex'
export default {
props: ['cells'],
// props: ['cells'],
data () {
return {
isMobile: undefined,
cellsData: [],
typeToIconName: {
'Image': 'photo',
......@@ -66,7 +90,7 @@
},
itemSpecs: {
'CellTitle': [{ // in usage
inputType: 'text',
inputType: 'textarea',
type: 'Title',
label: 'Title Cell',
help: '',
......@@ -95,10 +119,10 @@
value: '',
path: 'content'
}],
'CellVideo': [{ // in usage
'CellMedia': [{ // in usage
inputType: 'url',
type: 'Video',
label: 'Video Cell',
type: 'Media',
label: 'Media Cell',
help: 'Insert a URL to: a video file or a Vimeo / YouTube video page',
error: false,
errorMessage: 'Needs to be a valid URL',
......@@ -259,15 +283,30 @@
}
}
},
computed: {
...mapGetters({
// showEditingCells: 'mosys/getShowEditingCells'
cells: 'mosys/getEditingCells'
})
},
watch: {
async cells (val) {
this.getCellsData(val)
}
},
mounted () {
this.isMobile = this.$q.platform.is.mobile
this.getCellsData(this.cells)
},
methods: {
deleteCell (cell) {
this.$emit('removeCell', cell)
this.closeHandler()
},
closeHandler () {
this.$store.commit('mosys/setEditCellModal', false)
// this.$store.commit('mosys/setEditingCells', '')
},
async getCellsData (cells) {
this.cellsData = []
for (let i = 0; i < cells.length; i++) {
......@@ -303,4 +342,17 @@
}
</script>
<style scoped lang="stylus"></style>
<style scoped lang="stylus">
.ani-opacity
animation ani-opacity ease 220ms
@keyframes ani-opacity {
from { opacity: 0 }
to {opacity: 1}
}
.media-button
width: 42px
border-radius: .35rem
</style>
<template lang="pug">
.handler-new-cell.justify-center.row.bg-white(ref="targetHandler")
q-btn.text-dark(@click="onDoubleTap", flat, size="sm", no-ripple, style="max-width: 100%;")
slot
</template>
<script>
export default {
props: ['element', 'doubleTap'],
data () {
return {
clickTimer: null
}
},
mounted () {
if (this.element) {
let observer = new IntersectionObserver(this.observerCallback)
observer.observe(this.$refs.targetHandler)
this.observer = observer
}
},
methods: {
onDoubleTap () {
if (this.doubleTap) {
if (this.clickTimer == null) {
this.clickTimer = setTimeout(function () {
clearTimeout(this.clickTimer)
this.clickTimer = null
// alert('single')
}, 300)
}
else {
clearTimeout(this.clickTimer)
this.clickTimer = null
this.doubleTap.el.scrollLeft = this.doubleTap.oLeft - 16
// alert('double')
}
}
},
observerCallback (entries) {
let
target = entries[0],
arg = {element: this.element, intersecting: target.isIntersecting, offsetLeft: undefined}
if (!target.isIntersecting) Object.assign(arg, {offsetLeft: target.boundingClientRect.left})
this.$emit('onIntersectionChange', arg)
}
},
destroyed () {
if (this.observer) this.observer.disconnect()
},
name: 'CellHandler'
}
</script>
<style scoped lang="stylus">
handler-width = 40px
.handler-new-cell
width handler-width
height handler-width
</style>
<style lang="stylus">
.handler-new-cell
.q-focus-helper
display none!important
</style>
<template lang="pug">
.text-dark
strong {{ type }}
.cell-info(:class="setClass()")
strong.text-grey-3 {{ shortenType(type) }}
p.faded
span.q-pr-md(v-if="cell.styleClass") Class: .{{ cell.styleClass }}
span.q-pr-md(v-if="cell.creator") Author: {{ cell.creator.name }}
// span.q-pr-md(v-if="cell.author") Author: {{ cell.author.name }}
// span.q-pr-md(v-if="cell.author") {{ cell.author.name }}
</template>
<script>
......@@ -11,6 +12,14 @@
props: {
cell: Object,
type: String
},
methods: {
setClass () {
return this.cell.data.content.length > 0 ? 'text-dark' : 'text-red'
},
shortenType (type) {
return type.substr(0, type.length - 5)
}
}
}
</script>
This diff is collapsed.
<template lang="pug">
q-tabs.grid-editor-sources-tabs.shadow-11(v-model="selectedTab")
q-tab(slot="title", name="tab-default-cells", default, icon="add")
q-btn(slot="title", icon="close",
small, flat, round, class="fixed", style="right: 2px; margin-top: 3px",
@click="event => {$store.commit('mosys/hideAddCells')}")
q-tab-pane(name="tab-default-cells")
grid-editor-default-source
</template>
<script>
import GridEditorDefaultSource from './GridEditorDefaultSource'
import { mapGetters } from 'vuex'
export default {
components: {
GridEditorDefaultSource
},
data () {
return {
selectedTab: ''
}
},
computed: {
...mapGetters({
})
},
watch: {
},
beforeDestroy () {
this.$store.commit('mosys/hideAddCells')
}
}
</script>
<style lang="stylus">
.grid-editor-sources-tabs
background-color white
.q-tabs-panes
display flex
flex-direction column
overflow auto
</style>
This diff is collapsed.
<template lang="pug">
cell-editor(:cells="editingCells")
</template>
<script>
import CellEditor from './CellEditor'
import { mapGetters } from 'vuex'
// import { userHasFeature } from 'mbjs-quasar/src/lib'
export default {
components: {
CellEditor
},
data () {
return {
}
},
computed: {
...mapGetters({
// user: 'auth/getUserState',
editingCells: 'mosys/getEditingCells'
})
/*
userHasDocuments () {
return userHasFeature(this.user, 'documents')
}
*/
},
mounted () {
},
beforeDestroy () {
// this.$store.commit('mosys/hideEditingCells')
},
methods: {
}
}
</script>
<style lang="stylus" scoped>
</style>
<style lang="stylus">
@import '~variables'
</style>
<template lang="pug">
q-tabs.grid-editor-sources-tabs.shadow-11(v-model="selectedTab", color="dark")
q-tab(slot="title", name="tab-default-cells", default) Cells
q-tab(slot="title", name="tab-piecemaker") Piecemaker
q-tab(slot="title", name="tab-documents", v-if="userHasDocuments") Documents
q-btn(slot="title", icon="close",
small, flat, round, class="fixed", style="right: 2px; margin-top: 3px",
@click="handleCloseTabs()")
q-tab-pane(name="tab-default-cells")
// template(v-if="this.selectedCells.length > 0")
cell-editor(:cells="selectedCells")
// template(v-else)
grid-editor-default-source
q-tab-pane(name="tab-piecemaker", class="relative-position")
grid-editor-source-piece-maker
q-tab-pane(name="tab-documents", v-if="userHasDocuments")
grid-editor-source-documents
</template>
<script>
import CellEditor from './CellEditor'
import GridEditorDefaultSource from './GridEditorDefaultSource'
import GridEditorSourceDocuments from './GridEditorSourceDocuments'
import GridEditorSourcePieceMaker from './GridEditorSourcePieceMaker'
import { mapGetters } from 'vuex'
import { userHasFeature } from 'mbjs-quasar/src/lib'
export default {
components: {
CellEditor,
GridEditorDefaultSource,
GridEditorSourceDocuments,
GridEditorSourcePieceMaker
},
data () {
return {
selectedTab: ''
}
},
computed: {
...mapGetters({
user: 'auth/getUserState',
editingCells: 'mosys/getEditingCells',
selectedCells: 'mosys/getSelectedCells'
}),
userHasDocuments () {
return userHasFeature(this.user, 'documents')
},
currentStoreTab () {
return this.$store.state.mosys.sourcesTabName
}
},
watch: {
currentStoreTab () {
if (this.selectedTab !== this.currentStoreTab) {
this.selectedTab = this.currentStoreTab
}
},
selectedTab () {
if (this.selectedTab !== this.currentStoreTab) {
this.$store.commit('mosys/setSourcesTab', this.selectedTab)
}
}
},
beforeDestroy () {
this.$store.commit('mosys/hideSources')
},
methods: {
handleCloseTabs () {
this.$root.$emit('mosys_saveScrollPosition')
this.$store.commit('mosys/hideSources')
}
}
}
</script>
<style lang="stylus">
.grid-editor-sources-tabs
background-color white
.q-tabs-panes
display flex
flex-direction column
overflow auto
</style>
<template lang="pug">
.q-px-none.q-pt-md.text-center.text-dark.text-weight-bold
slot
q-item-separator.q-ma-none.q-mt-md.bg-grey-3
</template>
<script>
export default {
name: 'slideHeader'
}
</script>
<style scoped lang="stylus">
</style>
<template lang="pug">
div.relative-position.full-height
q-carousel.source-editor-carousel.full-height(v-model="slide", arrows, color="dark")
//---------- slide 1
q-carousel-slide.q-pa-none
slide-header
| Select source
.slide-body(:class="[isMobile ? 'mobile' : 'desktop']")
// TODO: if documents, check userHasFeature
template(v-for="(button, index) in buttons")
q-item-separator.q-ma-none.bg-grey-3(v-if="index > 0")
.q-py-lg.q-pl-lg(
@click="selectSource(button.value)",
:class="{'text-dark text-weight-bold': selectedSource === button.value}")
| {{ button.label }}
//---------- slide 2
q-carousel-slide.q-pa-none
slide-header
template(v-if="selectedSource === 'piecemaker'") Select timeline
template(v-else) Select {{ selectedSource.slice(0, -1) }}
.slide-body(:class="[isMobile ? 'mobile' : 'desktop']")
template(v-if="selectedSource === 'cells'")
source-cells
template(v-if="selectedSource === 'piecemaker'")
source-piecemaker(@nextSlide="onNextSlide()")
template(v-if="selectedSource === 'documents'")
source-documents
//---------- slide 3
q-carousel-slide.q-pa-none(v-if="selectedSource === 'piecemaker'")
slide-header(v-if="storeCurrentTimeline")
| {{ storeCurrentTimeline.title }}
.slide-body(:class="[isMobile ? 'mobile' : 'desktop']")
source-piecemaker-details
.absolute-top-left.fit.bg-white.q-pa-md(v-if="cachedNewCell")
| Place new
span.lowercase &nbsp;{{ cachedNewCell.component.substr(4, cachedNewCell.component.length - 4) }}
</template>
<script>
import SlideHeader from './SlideHeader'
import SourceCells from './sources/SourceCells'
import SourceDocuments from './sources/SourceDocuments'
import SourcePiecemaker from './sources/SourcePiecemaker'
import SourcePiecemakerDetails from './sources/SourcePiecemakerDetails'
import { mapGetters } from 'vuex'
import { userHasFeature } from 'mbjs-quasar/src/lib'
export default {
components: {
SlideHeader,
SourceCells,
SourceDocuments,
SourcePiecemaker,
SourcePiecemakerDetails
},
data () {
return {
buttons: [{
label: 'Cells', value: 'cells'
}, {
label: 'Piecemaker', value: 'piecemaker'
}, {
label: 'Documents', value: 'documents'
}],
slide: 0,
selectedSource: 'cells',
isMobile: this.$q.platform.is.mobile
}
},
computed: {
...mapGetters({
user: 'auth/getUserState',
storeCurrentTimeline: 'mosys/getCurrentTimeline',
cachedNewCell: 'mosys/getNewCell'
}),
userHasDocuments () {
return userHasFeature(this.user, 'documents')
}
},
beforeDestroy () {
this.$store.commit('mosys/hideSources')
},
methods: {
onNextSlide () {
this.slide += 1
},
selectSource (target) {
this.selectedSource = target
this.slide = 1
}
}
}
</script>
<style scoped lang="stylus">
.slide-body
overflow-y scroll
&.mobile
height calc(80vh - 52px)
&.desktop
height calc(calc(100vh - 59px) - 52px)
</style>
<template lang="pug">
// annotation list
div.annotation-list(:class="{'display-preview': preview, 'display-full': display}")
template(v-if="display")
template(v-if="video && video.body")
//q-list-header
q-item {{videoMeta.title}}
.annotation-list(:class="{'display-preview': preview, 'display-full': display}")
//----------------------------------------------------------------------------------------------------- display mode
template(v-if="display")
template(v-if="video && video.body")
//--------------------------------------------------------------------------------------------------------------
template(v-if="cell.displayType === 'tabs'")
.annotation-tabs-container.column
.annotation-tab(
......@@ -20,11 +20,14 @@
.date {{formatSelectorForList(annotation)}}
markdown-display.content(:content="annotation.body.value")
//-------------------------------------------------------------------------------------------------- annotations
template(v-else)
q-item.q-caption.q-py-md.fit.justify-center(v-if="annotations.length === 0") No annotations yet.
template(v-for="(annotation, index) in annotations")
q-item-separator(v-if="index > 0")
q-item-separator.bg-grey-9(v-if="index > 0")
//----- comment?
template(v-if="cell.allow_annotations && $store.state.auth.user && showAnnotationInput(index)")
q-item.annotation-input-container
q-input.annotation-input(type="textarea",
......@@ -36,20 +39,25 @@
stack-label="Leave Comment")
q-item-separator
q-item.annotation
a(:class="{'active': index === currentIndex}",
@click.prevent="event => {handleAnnotationClick(event, annotation, index)}")
.row.no-wrap
.date {{formatSelectorForList(annotation)}}
markdown-display.content(:content="annotation.body.value", :options="mdOptions")
//----- annotations
q-item.q-caption.q-py-md(@click.native="clickAnnotation(annotation, index)",
:class="{'bg-white': index === currentIndex}")
q-item-main
//----- time
q-item-tile(:class="[index === currentIndex ? 'text-grey-5': 'text-grey-8']")
| {{formatSelectorForList(annotation)}}
//----- annotation
q-item-tile.q-mt-sm(:class="[index === currentIndex ? 'text-dark': 'text-grey-3']")
markdown-display(:content="annotation.body.value", :options="mdOptions")
template(v-else)
strong Loading annotations
q-item-separator
//-------------------------------------------------------------------------------------------------------- edit mode
template(v-else)
cell-info(:cell="cell", type="Annotation List")
.q-pa-sm
cell-info(:cell="cell", type="Annotation List")
</template>
......@@ -174,13 +182,14 @@ export default {
}
this.newAnnotationText = ''
},
handleAnnotationClick (event, annotation, index) {
clickAnnotation (annotation, index) {
this.$root.$emit('annotation-trigger', annotation, this.annotationTimes[index])
},
handleTabClick (event, annotation, index) {
this.activeTabIdx = index
this.$root.$emit('annotation-trigger', annotation, this.annotationTimes[index])
},
/*
isAnnotationActive (annotation, index) {
let i = 0
while (i < this.annotations.length) {
......@@ -190,6 +199,7 @@ export default {
}
return false
},
*/
formatSelectorForList (annotation) {
const annotationDate = DateTime.fromMillis(annotation.target.selector._valueMillis)
return Interval.fromDateTimes(this.videoTime, annotationDate)
......@@ -254,6 +264,7 @@ export default {