diff --git a/app/imports/ui/StoryBook.vue b/app/imports/ui/StoryBook.vue
index 0d31dc4f..a253731e 100644
--- a/app/imports/ui/StoryBook.vue
+++ b/app/imports/ui/StoryBook.vue
@@ -41,6 +41,7 @@
import AbilityListTile from '/imports/ui/components/AbilityListTile.Story.vue';
import AttributeCard from '/imports/ui/components/AttributeCard.Story.vue';
import ColumnLayout from "/imports/ui/components/ColumnLayout.Story.vue";
+ import DialogStack from '/imports/ui/dialogStack/DialogStack.Story.vue';
import HealthBar from '/imports/ui/components/HealthBar.Story.vue';
import HitDiceListTile from '/imports/ui/components/HitDiceListTile.Story.vue';
import SkillListTile from '/imports/ui/components/SkillListTile.Story.vue';
@@ -50,6 +51,7 @@
AbilityListTile,
AttributeCard,
ColumnLayout,
+ DialogStack,
HealthBar,
HitDiceListTile,
SkillListTile,
diff --git a/app/imports/ui/dialogStack/TestDialog.vue b/app/imports/ui/dialogStack/DialogBase.Story.vue
similarity index 78%
rename from app/imports/ui/dialogStack/TestDialog.vue
rename to app/imports/ui/dialogStack/DialogBase.Story.vue
index 798deb55..9f519dc7 100644
--- a/app/imports/ui/dialogStack/TestDialog.vue
+++ b/app/imports/ui/dialogStack/DialogBase.Story.vue
@@ -4,7 +4,7 @@
Test Dialog
- Open Dialog
+ Open Dialog
@@ -14,9 +14,10 @@
import DialogBase from "/imports/ui/dialogStack/DialogBase.vue";
const component = {
methods: {
- openDialog(event){
+ openDialog(elementId){
store.commit("pushDialogStack", {
component,
+ elementId,
});
}
},
diff --git a/app/imports/ui/dialogStack/DialogStack.Story.vue b/app/imports/ui/dialogStack/DialogStack.Story.vue
new file mode 100644
index 00000000..e5068bd6
--- /dev/null
+++ b/app/imports/ui/dialogStack/DialogStack.Story.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/imports/ui/dialogStack/DialogStack.vue b/app/imports/ui/dialogStack/DialogStack.vue
index 5a883b6c..1e78c570 100644
--- a/app/imports/ui/dialogStack/DialogStack.vue
+++ b/app/imports/ui/dialogStack/DialogStack.vue
@@ -5,14 +5,23 @@
@click="backdropClicked"
:class="dialogs.length ? '' : 'hidden' "
>
-
+
+
-
+
@@ -22,8 +31,11 @@
import "/imports/ui/dialogStack/dialogStackWindowEvents.js";
import store from "/imports/ui/vuexStore.js";
import anime from "animejs";
+ import mockElement from '/imports/ui/dialogStack/mockElement.js';
+ import Vue from "vue";
- const offset = 16;
+ const OFFSET = 16;
+ const MOCK_DURATION = 8000; // Keep in sync with css transition of .dialog
export default {
computed: {
@@ -42,10 +54,57 @@
const length = store.state.dialogStack.dialogs.length;
if (index >= length) return;
const num = length - 1;
- const left = (num - index) * -offset;
- const top = (num - index) * -offset;
+ const left = (num - index) * -OFFSET;
+ const top = (num - index) * -OFFSET;
return `left:${left}px; top:${top}px;`;
},
+ enter(target, done){
+ let elementId = target.attributes['data-element-id'].value;
+ let source = document.getElementById(elementId);
+ // Get the original styles so we can repair them later
+ let originalStyle = {
+ transform: target.style.transform,
+ background: target.style.background,
+ borderRadius: target.style.borderRadius,
+ transition: target.style.transition,
+ boxShadow: target.style.boxShadow,
+ sourceTransition: source.style.transition,
+ }
+
+ // hide the source
+ source.style.transition = "none";
+ source.style.visibility = "hidden";
+
+ // Instantly mock the source
+ target.style.transition = 'none';
+ mockElement({source, target});
+
+ // After a full tick, repair the original styles
+ Vue.nextTick(() => {
+ target.style.transform = originalStyle.transform;
+ target.style.background = originalStyle.background;
+ target.style.borderRadius = originalStyle.borderRadius;
+ target.style.transition = originalStyle.transition;
+ target.style.boxShadow = originalStyle.boxShadow;
+ source.style.transition = originalStyle.sourceTransition;
+ setTimeout(done, MOCK_DURATION);
+ });
+ },
+ leave(target, done){
+ let elementId = target.attributes['data-element-id'].value;
+ let source = document.getElementById(elementId);
+ let index = target.attributes['data-index'].value;
+ 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});
+ }
+ setTimeout(() => {
+ source.style.visibility = null;
+ done();
+ }, MOCK_DURATION);
+ }
},
};
@@ -58,6 +117,7 @@
right: 0;
bottom: 0;
pointer-events: none;
+ z-index: 3;
}
.dialog-sizer {
position: relative;
@@ -79,17 +139,25 @@
.backdrop.hidden {
display: none
}
- .dialog-list-move {
- transition: transform 400ms;
- }
- .dialog-list-leave-active {
-
- }
+ .dialog-list-enter .dialog-component, .dialog-list-leave-to .dialog-component {
+ opacity: 0;
+ }
+ .dialog-list-enter-active .dialog-component {
+ transition: opacity 4s;
+ }
+ .dialog-list-leave-active .dialog-component {
+ transition: opacity 4s 4s;
+ }
.dialog {
+ transition: all 8s;
+ transform-origin: top left;
position: absolute;
height: 100%;
width: 100%;
pointer-events: initial;
+ z-index: 1;
+ overflow: hidden;
+ background: white;
}
.dialog > * {
height: 100%;
diff --git a/app/imports/ui/dialogStack/dialogStackStore.js b/app/imports/ui/dialogStack/dialogStackStore.js
index 2677ee72..8c6fe155 100644
--- a/app/imports/ui/dialogStack/dialogStackStore.js
+++ b/app/imports/ui/dialogStack/dialogStackStore.js
@@ -7,14 +7,15 @@ const dialogStackStore = {
currentResult: null,
},
mutations: {
- pushDialogStack(state, {component, data, element, returnElement, callback}){
+ pushDialogStack(state, {component, data, elementId, returnElement, callback}){
// Generate a new _id so that Vue knows how to shuffle the array
+ console.log({elementId});
const _id = Random.id();
state.dialogs.push({
_id,
component,
data,
- element,
+ elementId,
returnElement,
callback,
});
diff --git a/app/imports/ui/dialogStack/mockElement.js b/app/imports/ui/dialogStack/mockElement.js
new file mode 100644
index 00000000..07f37e45
--- /dev/null
+++ b/app/imports/ui/dialogStack/mockElement.js
@@ -0,0 +1,58 @@
+import { parse, stringify } from 'css-box-shadow';
+
+// Only supports border radius defined like "20px" or "100%"
+const transformedRadius = (radiusString, deltaWidth, deltaHeight) => {
+ if (/^\d+\.?\d*px$/.test(radiusString)){
+ //The radius is defined in pixel units, so get the radius as a number
+ const rad = +radiusString.match(/\d+\.?\d*/)[0];
+ // Set the x and y radius of the "to" element, compensating for scale
+ return `${rad / deltaWidth}px / ${rad / deltaHeight}px`;
+ } else if (/^\d+\.?\d*%$/.test(radiusString)) {
+ //The radius is defined as a percentage, so just use it as is
+ return radiusString;
+ }
+};
+
+const transformedBoxShadow = (shadowString, deltaWidth, deltaHeight) => {
+ if (shadowString[0] === 'r'){
+ let strings = shadowString.match(/rgba\([^)]+\)[^,]+/g);
+ strings = strings.map(string => {
+ // TODO move color to end
+ strings.match(/(rgba\([^)]+\))([^,]+)/)
+ });
+ }
+ let scaleAverage = (deltaWidth + deltaHeight) / 2;
+ let shadows = parse(shadowString);
+ console.log({shadowString, shadows});
+ shadows.forEach(shadow => {
+ shadow.offsetX /= deltaWidth;
+ shadow.offsetY /= deltaHeight;
+ shadow.blurRadius /= scaleAverage;
+ shadow.spreadRadius /= scaleAverage;
+ })
+ console.log({newShadows: shadows});
+ return stringify(shadows);
+}
+
+export default function mockElement({source, target, offset = {x: 0, y: 0}}){
+ if (!source || !target) throw `Can't mock without ${source ? 'target' : 'source'}` ;
+ sourceRect = source.getBoundingClientRect();
+ targetRect = target.getBoundingClientRect();
+
+ // Get how must the target change to become the source
+ const deltaWidth = sourceRect.width / targetRect.width;
+ const deltaHeight = sourceRect.height / targetRect.height;
+ const deltaLeft = sourceRect.left - targetRect.left + offset.x;
+ const deltaTop = sourceRect.top - targetRect.top + offset.y;
+ // Mock the source
+ target.style.transform = `translate(${deltaLeft}px, ${deltaTop}px) ` +
+ `scale(${deltaWidth}, ${deltaHeight})`;
+
+ target.style.background = getComputedStyle(source).background;
+ target.style.borderRadius = transformedRadius(
+ getComputedStyle(source).borderRadius, deltaWidth, deltaHeight
+ );
+ //target.style.boxShadow = transformedBoxShadow(
+ // getComputedStyle(source).boxShadow, deltaWidth, deltaHeight
+ //);
+};
diff --git a/app/imports/ui/router.js b/app/imports/ui/router.js
index 4b0f6c55..82dd19a7 100644
--- a/app/imports/ui/router.js
+++ b/app/imports/ui/router.js
@@ -8,7 +8,6 @@ import CharacterSheetPage from "/imports/ui/pages/CharacterSheetPage.vue";
import SignIn from "/imports/ui/pages/SignIn.vue" ;
import Register from "/imports/ui/pages/Register.vue" ;
import Account from "/imports/ui/pages/Account.vue" ;
-import TestDialog from "/imports/ui/dialogStack/TestDialog.vue";
// Not found
import NotFound from '/imports/ui/pages/NotFound.vue';
@@ -45,9 +44,6 @@ RouterFactory.configure(factory => {
},{
path: "/account",
component: Account,
- },{
- path: "/test-dialog",
- component: TestDialog,
},
]);
//Development routes
diff --git a/app/package-lock.json b/app/package-lock.json
index 62c448ee..6c9fe581 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -243,6 +243,11 @@
}
}
},
+ "css-box-shadow": {
+ "version": "1.0.0-3",
+ "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
+ "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
diff --git a/app/package.json b/app/package.json
index 2bbd6a11..a98ee859 100644
--- a/app/package.json
+++ b/app/package.json
@@ -18,6 +18,7 @@
"bcrypt": "^1.0.3",
"bower": "^1.7.9",
"core-js": "^2.6.2",
+ "css-box-shadow": "^1.0.0-3",
"fibers": "^2.0.2",
"meteor-node-stubs": "^0.3.3",
"qrcode": "^1.3.3",