Progress on user image UI
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<v-layout
|
||||
<div
|
||||
class="dialog-stack"
|
||||
align-center
|
||||
justify-center
|
||||
>
|
||||
<transition name="backdrop-fade">
|
||||
<div
|
||||
@@ -13,42 +11,61 @@
|
||||
</transition>
|
||||
<transition-group
|
||||
name="dialog-list"
|
||||
class="dialog-sizer"
|
||||
class="dialog-transition-group"
|
||||
:class="{shake}"
|
||||
tag="div"
|
||||
@enter="enter"
|
||||
@leave="leave"
|
||||
>
|
||||
<v-card
|
||||
v-for="(dialog, index) in dialogs"
|
||||
:key="dialog._id"
|
||||
:ref="index"
|
||||
class="dialog"
|
||||
:data-element-id="dialog.elementId"
|
||||
:data-index="index"
|
||||
:style="getDialogStyle(index)"
|
||||
:elevation="6"
|
||||
>
|
||||
<transition name="slide">
|
||||
<component
|
||||
:is="dialog.component"
|
||||
v-bind="dialog.data"
|
||||
class="dialog-component"
|
||||
@pop="popDialogStack($event)"
|
||||
/>
|
||||
</transition>
|
||||
</v-card>
|
||||
<template v-for="(dialog, index) in dialogs">
|
||||
<component
|
||||
:is="dialog.component"
|
||||
v-if="isUnsizedDialog(dialog.component)"
|
||||
:key="dialog._id"
|
||||
:ref="index"
|
||||
v-bind="dialog.data"
|
||||
class="unsized-dialog dialog-component"
|
||||
:data-element-id="dialog.elementId"
|
||||
:data-index="index"
|
||||
:style="getDialogStyle(index)"
|
||||
:elevation="6"
|
||||
@pop="popDialogStack($event)"
|
||||
/>
|
||||
<v-card
|
||||
v-else
|
||||
:key="dialog._id"
|
||||
:ref="index"
|
||||
class="dialog"
|
||||
:data-element-id="dialog.elementId"
|
||||
:data-index="index"
|
||||
:style="getDialogStyle(index)"
|
||||
:elevation="6"
|
||||
>
|
||||
<transition name="slide">
|
||||
<component
|
||||
:is="dialog.component"
|
||||
v-bind="dialog.data"
|
||||
class="sized-dialog dialog-component"
|
||||
@pop="popDialogStack($event)"
|
||||
/>
|
||||
</transition>
|
||||
</v-card>
|
||||
</template>
|
||||
</transition-group>
|
||||
</v-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import '/imports/client/ui/dialogStack/dialogStackWindowEvents';
|
||||
import mockElement from '/imports/client/ui/dialogStack/mockElement';
|
||||
import DialogComponentIndex from '/imports/client/ui/dialogStack/DialogComponentIndex';
|
||||
import timeout from '/imports/api/utility/timeout';
|
||||
|
||||
const OFFSET = 16;
|
||||
const MOCK_DURATION = 400; // Keep in sync with css transition of .dialog
|
||||
// Use in combination with browser's animation speed override to do slow-mod debugging
|
||||
const animationSpeed = 1;
|
||||
|
||||
const unsizedDialogs = new Set(['image-preview-dialog']);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -64,16 +81,17 @@
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
dialogs(newDialogs) {
|
||||
async dialogs(newDialogs) {
|
||||
const el = document.documentElement;
|
||||
if (newDialogs.length) {
|
||||
this.top = el.scrollTop;
|
||||
if (el.scrollHeight > el.clientHeight){
|
||||
el.style.overflowY = 'hidden';
|
||||
el.scrollTop = this.top;
|
||||
el.classList.add('lock-scroll');
|
||||
}
|
||||
} else {
|
||||
el.style.overflowY = null;
|
||||
await timeout(400 / animationSpeed);
|
||||
el.classList.remove('lock-scroll');
|
||||
el.scrollTop = this.top;
|
||||
}
|
||||
}
|
||||
@@ -82,6 +100,9 @@
|
||||
popDialogStack(result){
|
||||
this.$store.dispatch('popDialogStack', result);
|
||||
},
|
||||
isUnsizedDialog(component) {
|
||||
return unsizedDialogs.has(component);
|
||||
},
|
||||
backdropClicked(event) {
|
||||
// If the target was not the backdrop, ignore
|
||||
if (event.target !== event.currentTarget) return;
|
||||
@@ -107,8 +128,8 @@
|
||||
if (index >= length) return;
|
||||
const num = length - 1;
|
||||
const left = (num - index) * -OFFSET;
|
||||
const top = (num - index) * -OFFSET;
|
||||
return `left:${left}px; top:${top}px;`;
|
||||
const top = (num - index) * -OFFSET;
|
||||
return `left: calc(${left}px + 50%); top: calc(${top}px + 50%)`;
|
||||
},
|
||||
getTopElementByDataId(elementId, offset = 0){
|
||||
let stackLength = this.$store.state.dialogStack.dialogs.length - offset;
|
||||
@@ -122,7 +143,7 @@
|
||||
document.querySelector(`[data-id='${elementId}']`);
|
||||
}
|
||||
},
|
||||
enter(target, done){
|
||||
async enter(target, done){
|
||||
if (!target || !target.attributes['data-element-id']){
|
||||
done();
|
||||
return;
|
||||
@@ -143,34 +164,35 @@
|
||||
sourceTransition: source.style.transition,
|
||||
}
|
||||
|
||||
// Instantly mock the source
|
||||
target.style.transition = 'none';
|
||||
mockElement({ source, target });
|
||||
|
||||
// Wait one frame before hiding the source so we know our mock is in place
|
||||
await new Promise(requestAnimationFrame);
|
||||
|
||||
// hide the source
|
||||
source.style.transition = 'none';
|
||||
source.style.opacity = '0';
|
||||
this.hiddenElements.push(source);
|
||||
|
||||
// Instantly mock the source
|
||||
target.style.transition = 'none';
|
||||
mockElement({source, target});
|
||||
|
||||
// on the next animation frame, repair the styles
|
||||
requestAnimationFrame(() => {
|
||||
target.style.transform = originalStyle.transform;
|
||||
target.style.backgroundColor = originalStyle.backgroundColor;
|
||||
target.style.borderRadius = originalStyle.borderRadius;
|
||||
target.style.transition = originalStyle.transition;
|
||||
target.style.boxShadow = originalStyle.boxShadow;
|
||||
source.style.transition = originalStyle.sourceTransition;
|
||||
setTimeout(done, MOCK_DURATION);
|
||||
});
|
||||
// repair the styles so that our mock is undone revealing the dialog
|
||||
target.style.transform = originalStyle.transform;
|
||||
target.style.backgroundColor = originalStyle.backgroundColor;
|
||||
target.style.borderRadius = originalStyle.borderRadius;
|
||||
target.style.transition = originalStyle.transition;
|
||||
target.style.boxShadow = originalStyle.boxShadow;
|
||||
source.style.transition = originalStyle.sourceTransition;
|
||||
setTimeout(done, 300 / animationSpeed);
|
||||
},
|
||||
leave(target, done){
|
||||
// Give minimongo time to update documents we might need to animate to
|
||||
setTimeout(() => this.doLeave(target, done));
|
||||
},
|
||||
doLeave(target, done){
|
||||
async doLeave(target, done){
|
||||
let elementId;
|
||||
let hiddenElement = this.hiddenElements.pop();
|
||||
let returnElementId = this.$store.state.dialogStack.currentReturnElement;
|
||||
let returnElementId = await this.$store.state.dialogStack.currentReturnElement;
|
||||
if (returnElementId) {
|
||||
elementId = returnElementId;
|
||||
} else {
|
||||
@@ -183,37 +205,50 @@
|
||||
let source = this.getTopElementByDataId(elementId);
|
||||
if (!source){
|
||||
console.warn(`Can't find source for ${elementId}`);
|
||||
if (hiddenElement) hiddenElement.style.opacity = null;
|
||||
if (hiddenElement) hiddenElement.style.opacity = '';
|
||||
else console.warn('No hidden element to reveal', hiddenElement);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
let index = target.attributes['data-index'].value;
|
||||
|
||||
// Disable clicking the dialog while it's animating
|
||||
target.style.pointerEvents = 'none';
|
||||
|
||||
// Make the dialog mock the source
|
||||
if (index != 0){
|
||||
// If we aren't the only dialog, we'll need compensate for offset
|
||||
mockElement({source, target, offset: {x: OFFSET, y: OFFSET}})
|
||||
} else {
|
||||
mockElement({source, target});
|
||||
}
|
||||
|
||||
// If the source and the hidden Element are different
|
||||
// hide the source and reveal the hidden element
|
||||
let originalSourceTransition = source.style.transition;
|
||||
if (hiddenElement !== source){
|
||||
source.style.transition = 'none';
|
||||
source.style.opacity = '0';
|
||||
hiddenElement.style.opacity = null;
|
||||
if (hiddenElement) hiddenElement.style.opacity = '';
|
||||
// wait a frame for these to apply without transitions
|
||||
await new Promise(requestAnimationFrame);
|
||||
}
|
||||
setTimeout(() => {
|
||||
source.style.opacity = null;
|
||||
source.style.transition = 'none';
|
||||
target.style.transition = `opacity ${MOCK_DURATION / 4}ms, pointer-events 0s`
|
||||
requestAnimationFrame(() => {
|
||||
source.style.transition = originalSourceTransition;
|
||||
target.style.opacity = '0';
|
||||
target.style.pointerEvents = 'none';
|
||||
target.style.setProperty('box-shadow', 'none', 'important');
|
||||
setTimeout(done, MOCK_DURATION / 4);
|
||||
});
|
||||
}, MOCK_DURATION);
|
||||
|
||||
// Wait for the mock to finish
|
||||
await timeout(300 / animationSpeed);
|
||||
|
||||
// reveal the source immediately
|
||||
source.style.opacity = '';
|
||||
source.style.transition = 'none';
|
||||
|
||||
// Wait for the opacity swap to finish
|
||||
await timeout(100 / animationSpeed);
|
||||
|
||||
// Fix the transition of the source
|
||||
source.style.transition = originalSourceTransition;
|
||||
|
||||
// Done
|
||||
done();
|
||||
},
|
||||
noScroll(e){
|
||||
e.preventDefault();
|
||||
@@ -224,7 +259,7 @@
|
||||
|
||||
<style scoped>
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
@@ -232,6 +267,7 @@
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
z-index: 6;
|
||||
pointer-events: initial;
|
||||
opacity: 1;
|
||||
}
|
||||
.backdrop-fade-enter-active, .backdrop-fade-leave-active {
|
||||
transition: opacity 0.3s;
|
||||
@@ -248,19 +284,8 @@
|
||||
pointer-events: none;
|
||||
z-index: 6;
|
||||
}
|
||||
.dialog-sizer {
|
||||
position: relative;
|
||||
width: 80%;
|
||||
width: calc(100% - 64px);
|
||||
max-width: 1000px;
|
||||
height: 80%;
|
||||
height: calc(100% - 64px);
|
||||
max-height: 800px;
|
||||
z-index: 7;
|
||||
flex: initial;
|
||||
}
|
||||
|
||||
.dialog-sizer.shake {
|
||||
.shake {
|
||||
animation: shake 0.2s;
|
||||
}
|
||||
|
||||
@@ -270,44 +295,69 @@
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
/* sm */
|
||||
@media only screen and (max-width: 960px) and (min-width: 601px){
|
||||
.dialog-sizer {
|
||||
width: calc(100% - 32px);
|
||||
height: calc(100% - 32px);
|
||||
}
|
||||
}
|
||||
/* xs */
|
||||
@media only screen and (max-width: 600px) {
|
||||
.dialog-sizer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.dialog-list-enter .dialog-component, .dialog-list-leave-to .dialog-component {
|
||||
opacity: 0;
|
||||
}
|
||||
.dialog-list-enter-active .dialog-component {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.dialog-list-leave-active .dialog-component {
|
||||
transition: opacity 0.3s 0.1s;
|
||||
}
|
||||
.dialog-list-enter-active {
|
||||
transition: all 0.4s, box-shadow 0.1s;
|
||||
}
|
||||
.dialog-list-leave-active {
|
||||
transition: all 0.4s, box-shadow 0.1s 0.3s, opacity 0.1s, pointer-events 0s;
|
||||
}
|
||||
.dialog {
|
||||
transform-origin: top left;
|
||||
position: absolute;
|
||||
.dialog-transition-group {
|
||||
position: relative;
|
||||
z-index: 7;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
Fade in and out the dialog contents as it is animating
|
||||
*/
|
||||
.dialog-list-enter .sized-dialog, .dialog-list-leave-to .sized-dialog {
|
||||
opacity: 0;
|
||||
}
|
||||
.dialog-list-enter-active .sized-dialog, .dialog-list-leave-active .sized-dialog {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
/*
|
||||
Enter and leave with no shadow
|
||||
*/
|
||||
.dialog-list-enter, .dialog-list-leave-to {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Leave to no opacity
|
||||
*/
|
||||
.dialog-list-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dialog.dialog-list-enter-active, .unsized-dialog.dialog-list-enter-active {
|
||||
transition: all 0.3s, box-shadow 0.1s, opacity 0s, pointer-events 0s;
|
||||
}
|
||||
.dialog.dialog-list-leave-active, .unsized-dialog.dialog-list-leave-active {
|
||||
transition: all 0.3s, box-shadow 0.1s 0.3s, opacity 0.1s 0.3s, pointer-events 0s;
|
||||
}
|
||||
|
||||
/**
|
||||
Only the top dialog should be clickable
|
||||
*/
|
||||
.dialog:last-child, .unsized-dialog:last-child {
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-height: 800px;
|
||||
max-width: 1000px;
|
||||
}
|
||||
.dialog, .unsized-dialog {
|
||||
transform-origin: center;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
transition: all .3s ease;
|
||||
transition: all 0.3s ease;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
@media only screen and (min-width: 601px){
|
||||
.dialog-stack {
|
||||
padding: 32px;
|
||||
}
|
||||
}
|
||||
.dialog > * {
|
||||
height: 100%;
|
||||
|
||||
Reference in New Issue
Block a user