diff --git a/app/src/components/NodesLayer.vue b/app/src/components/NodesLayer.vue index fa2c456a44b56d7903e5d3d0b65f2a9bf6c01a7a..dd6a645e0578b88e88f395746735ee73f0ab1513 100644 --- a/app/src/components/NodesLayer.vue +++ b/app/src/components/NodesLayer.vue @@ -1,65 +1,133 @@ <template> <div ref="nodes" class="node"> <div v-for="(value, index) in configPositions" v-bind:key="index"> - <vue-draggable-resizable - class="innernode" - v-if="nodeid == value.node_id" - :w="value.width" - :h="value.height" - :x="value.x_pos" - :y="value.y_pos" - :z="value.z_index" - @activated="onActivated" - @dragging="onDrag" - @resizing="onResize" - @dragstop="onDragstop" - @resizestop="onResizestop" - :drag-cancel="'.drag-cancel'" - style="background-color: rgb(205, 234, 255);" - > - <form> - <div v-if="readmode == false"> - <div v-for="value in myNodes" v-bind:key="value.node_id"> - <!-- <div v-if="readmode == false"> --> - <textarea - v-if="nodeid == value.node_id" - @focus="editTrue(true)" - @blur="editTrue(false)" - autofocus - @input="editNode" - v-model="value.node_text" - :id="nodeid" - class="drag-cancel" - ref="nodetext" - placeholder="Idea goes here!" - ></textarea> + <div v-if="toolmode == 'move'"> + <!-- make draggable false as we are panning around --> + <vue-draggable-resizable + class="innernode" + v-if="nodeid == value.node_id" + :w="value.width" + :h="value.height" + :x="value.x_pos" + :y="value.y_pos" + :z="value.z_index" + :draggable="false" + style="background-color: rgb(205, 234, 255);" + > + <form> + <div v-if="readmode == false"> + <div v-for="value in myNodes" v-bind:key="value.node_id"> + <!-- <div v-if="readmode == false"> --> + <textarea + v-if="nodeid == value.node_id" + @focus="editTrue(true)" + @blur="editTrue(false)" + autofocus + @input="editNode" + v-model="value.node_text" + :id="nodeid" + class="drag-cancel" + ref="nodetext" + placeholder="Idea goes here!" + ></textarea> + </div> </div> - </div> - <!-- FIXME: What is this doing below now ? Looks old --> - <div v-else> - <p :id="nodeid" :inner-html.prop="nodetext | marked"> - {{ nodeid }} - </p> - </div> - - <h3>Reactions</h3> - <div v-for="(emojis, index) in configEmoji" :key="index"> - <p class="allemoji" v-if="nodeid == emojis.node_id"> - {{ emojis.emoji_text }} - </p> - </div> - - <p class="info">*markdown supported</p> - <div class="btn-row"> - <BaseButton buttonClass="danger" @click="deleteFlag()" - >Delete</BaseButton - > - <BaseButton class="read" buttonClass="action" @click="readFlag()">{{ - mode - }}</BaseButton> - </div> - </form> - </vue-draggable-resizable> + <!-- FIXME: What is this doing below now ? Looks old --> + <div v-else> + <p :id="nodeid" :inner-html.prop="nodetext | marked"> + {{ nodeid }} + </p> + </div> + + <h3>Reactions</h3> + + <div v-for="(emojis, index) in configEmoji" :key="index"> + <p class="allemoji" v-if="nodeid == emojis.node_id"> + {{ emojis.emoji_text }} + </p> + </div> + + <p class="info">*markdown supported</p> + <div class="btn-row"> + <BaseButton buttonClass="danger" @click="deleteFlag()" + >Delete</BaseButton + > + <BaseButton + class="read" + buttonClass="action" + @click="readFlag()" + >{{ mode }}</BaseButton + > + </div> + </form> + </vue-draggable-resizable> + </div> + + <div v-else> + <vue-draggable-resizable + class="innernode" + v-if="nodeid == value.node_id" + :w="value.width" + :h="value.height" + :x="value.x_pos" + :y="value.y_pos" + :z="value.z_index" + @activated="onActivated" + @dragging="onDrag" + @resizing="onResize" + @dragstop="onDragstop" + @resizestop="onResizestop" + :drag-cancel="'.drag-cancel'" + style="background-color: rgb(205, 234, 255);" + > + <form> + <div v-if="readmode == false"> + <div v-for="value in myNodes" v-bind:key="value.node_id"> + <!-- <div v-if="readmode == false"> --> + <textarea + v-if="nodeid == value.node_id" + @focus="editTrue(true)" + @blur="editTrue(false)" + autofocus + @input="editNode" + v-model="value.node_text" + :id="nodeid" + class="drag-cancel" + ref="nodetext" + placeholder="Idea goes here!" + ></textarea> + </div> + </div> + <!-- FIXME: What is this doing below now ? Looks old --> + <div v-else> + <p :id="nodeid" :inner-html.prop="nodetext | marked"> + {{ nodeid }} + </p> + </div> + + <h3>Reactions</h3> + + <div v-for="(emojis, index) in configEmoji" :key="index"> + <p class="allemoji" v-if="nodeid == emojis.node_id"> + {{ emojis.emoji_text }} + </p> + </div> + + <p class="info">*markdown supported</p> + <div class="btn-row"> + <BaseButton buttonClass="danger" @click="deleteFlag()" + >Delete</BaseButton + > + <BaseButton + class="read" + buttonClass="action" + @click="readFlag()" + >{{ mode }}</BaseButton + > + </div> + </form> + </vue-draggable-resizable> + </div> </div> </div> </template> @@ -76,19 +144,19 @@ export default { nodetext: String, nodewidth: Number, nodeheight: Number, - deleted: Boolean + deleted: Boolean, }, data() { return { pickupz: 99, readmode: false, - mode: 'Read' + mode: 'Read', } }, filters: { - marked: marked + marked: marked, }, // FIXME: how do we know how to focus on the newest node ? @@ -106,9 +174,10 @@ export default { // }, computed: mapState({ - myNodes: state => state.myNodes, - configPositions: state => state.configPositions, - configEmoji: state => state.configEmoji + myNodes: (state) => state.myNodes, + configPositions: (state) => state.configPositions, + configEmoji: (state) => state.configEmoji, + toolmode: (state) => state.ui.mode, }), methods: { onActivated() { @@ -146,7 +215,7 @@ export default { y, width, height, - zindex + zindex, }) }, onDrag(x, y) { @@ -172,7 +241,7 @@ export default { y, width, height, - zindex + zindex, }) }, @@ -198,8 +267,8 @@ export default { this.readmode = true this.mode = 'Edit' } - } - } + }, + }, } </script> diff --git a/app/src/components/OtherNodeslayer.vue b/app/src/components/OtherNodeslayer.vue index c874492943468b072f092973739b87603f1988ad..d2f9d824201ca1add9cdda33d889ebe222a860f6 100644 --- a/app/src/components/OtherNodeslayer.vue +++ b/app/src/components/OtherNodeslayer.vue @@ -1,91 +1,190 @@ <template> <div ref="othernodes" class="node"> <div v-for="(value, index) in configPositions" v-bind:key="index"> - <vue-draggable-resizable - v-if="nodeid == value.node_id" - :w="value.width" - :h="value.height" - :x="value.x_pos" - :y="value.y_pos" - :z="value.z_index" - @activated="onActivated" - @dragging="onDrag" - @resizing="onResize" - @dragstop="onDragstop" - @resizestop="onResizestop" - style="border: 1px solid black; background-color: rgb(205, 234, 255);" - > - <p :id="nodeid" :inner-html.prop="nodetext | marked">{{ nodeid }}</p> - <h3>Reactions</h3> - <div v-for="(emojis, index) in configEmoji" :key="index"> - <p class="allemoji" v-if="nodeid == emojis.node_id"> - {{ emojis.emoji_text }} - </p> - </div> - <div class="react" v-if="nodeid != undefined"> - <h2>React</h2> - <div class="eeee"> - <input :value="nodeid" name="id" readonly hidden /> - <input - id="emojifield" - class="regular-input" - v-model="input" - readonly - /> + <div v-if="toolmode == 'move'"> + <!-- make draggable false as we are panning around --> + <vue-draggable-resizable + v-if="nodeid == value.node_id" + :w="value.width" + :h="value.height" + :x="value.x_pos" + :y="value.y_pos" + :z="value.z_index" + @activated="onActivated" + @dragging="onDrag" + @resizing="onResize" + :draggable="false" + @dragstop="onDragstop" + @resizestop="onResizestop" + style="border: 1px solid black; background-color: rgb(205, 234, 255);" + > + <p :id="nodeid" :inner-html.prop="nodetext | marked">{{ nodeid }}</p> + <h3>Reactions</h3> + <div v-for="(emojis, index) in configEmoji" :key="index"> + <p class="allemoji" v-if="nodeid == emojis.node_id"> + {{ emojis.emoji_text }} + </p> + </div> + <div class="react" v-if="nodeid != undefined"> + <h2>React</h2> + <div class="eeee"> + <input :value="nodeid" name="id" readonly hidden /> + <input + id="emojifield" + class="regular-input" + v-model="input" + readonly + /> - <emoji-picker @emoji="append" :search="search"> - <div - class="emoji-invoker" - slot="emoji-invoker" - slot-scope="{ events: { click: clickEvent } }" - @click.stop="clickEvent" - > - <svg - height="24" - viewBox="0 0 24 24" - width="24" - xmlns="http://www.w3.org/2000/svg" + <emoji-picker @emoji="append" :search="search"> + <div + class="emoji-invoker" + slot="emoji-invoker" + slot-scope="{ events: { click: clickEvent } }" + @click.stop="clickEvent" > - <path d="M0 0h24v24H0z" fill="none" /> - <path - d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z" - /> - </svg> - </div> - <div slot="emoji-picker" slot-scope="{ emojis, insert, display }"> + <svg + height="24" + viewBox="0 0 24 24" + width="24" + xmlns="http://www.w3.org/2000/svg" + > + <path d="M0 0h24v24H0z" fill="none" /> + <path + d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z" + /> + </svg> + </div> <div - class="emoji-picker" - :style="{ top: display.y + 'px', left: display.x + 'px' }" + slot="emoji-picker" + slot-scope="{ emojis, insert, display }" > - <div class="emoji-picker__search"> - <input type="text" v-model="search" v-focus /> + <div + class="emoji-picker" + :style="{ top: display.y + 'px', left: display.x + 'px' }" + > + <div class="emoji-picker__search"> + <input type="text" v-model="search" v-focus /> + </div> + <div> + <div + v-for="(emojiGroup, category) in emojis" + :key="category" + > + <h5>{{ category }}</h5> + <div class="emojis"> + <span + v-for="(emoji, emojiName) in emojiGroup" + :key="emojiName" + @click="insert(emoji)" + :title="emojiName" + >{{ emoji }}</span + > + </div> + </div> + </div> </div> - <div> - <div - v-for="(emojiGroup, category) in emojis" - :key="category" - > - <h5>{{ category }}</h5> - <div class="emojis"> - <span - v-for="(emoji, emojiName) in emojiGroup" - :key="emojiName" - @click="insert(emoji)" - :title="emojiName" - >{{ emoji }}</span - > + </div> + </emoji-picker> + <BaseButton buttonClass="action" @click="sentReact()" + >Send Reaction</BaseButton + > + </div> + </div> + </vue-draggable-resizable> + </div> + + <div v-else> + <!-- make draggable false as we are panning around --> + <vue-draggable-resizable + v-if="nodeid == value.node_id" + :w="value.width" + :h="value.height" + :x="value.x_pos" + :y="value.y_pos" + :z="value.z_index" + @activated="onActivated" + @dragging="onDrag" + @resizing="onResize" + @dragstop="onDragstop" + @resizestop="onResizestop" + style="border: 1px solid black; background-color: rgb(205, 234, 255);" + > + <p :id="nodeid" :inner-html.prop="nodetext | marked">{{ nodeid }}</p> + <h3>Reactions</h3> + <div v-for="(emojis, index) in configEmoji" :key="index"> + <p class="allemoji" v-if="nodeid == emojis.node_id"> + {{ emojis.emoji_text }} + </p> + </div> + <div class="react" v-if="nodeid != undefined"> + <h2>React</h2> + <div class="eeee"> + <input :value="nodeid" name="id" readonly hidden /> + <input + id="emojifield" + class="regular-input" + v-model="input" + readonly + /> + + <emoji-picker @emoji="append" :search="search"> + <div + class="emoji-invoker" + slot="emoji-invoker" + slot-scope="{ events: { click: clickEvent } }" + @click.stop="clickEvent" + > + <svg + height="24" + viewBox="0 0 24 24" + width="24" + xmlns="http://www.w3.org/2000/svg" + > + <path d="M0 0h24v24H0z" fill="none" /> + <path + d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z" + /> + </svg> + </div> + <div + slot="emoji-picker" + slot-scope="{ emojis, insert, display }" + > + <div + class="emoji-picker" + :style="{ top: display.y + 'px', left: display.x + 'px' }" + > + <div class="emoji-picker__search"> + <input type="text" v-model="search" v-focus /> + </div> + <div> + <div + v-for="(emojiGroup, category) in emojis" + :key="category" + > + <h5>{{ category }}</h5> + <div class="emojis"> + <span + v-for="(emoji, emojiName) in emojiGroup" + :key="emojiName" + @click="insert(emoji)" + :title="emojiName" + >{{ emoji }}</span + > + </div> </div> </div> </div> </div> - </div> - </emoji-picker> - <BaseButton buttonClass="action" @click="sentReact()" - >Send Reaction</BaseButton - > + </emoji-picker> + <BaseButton buttonClass="action" @click="sentReact()" + >Send Reaction</BaseButton + > + </div> </div> - </div> - </vue-draggable-resizable> + </vue-draggable-resizable> + </div> </div> </div> </template> @@ -99,32 +198,33 @@ export default { name: 'otherNodeslayer', components: { - EmojiPicker + EmojiPicker, }, props: { nodeid: String, nodetext: String, nodewidth: Number, - nodeheight: Number + nodeheight: Number, }, data() { return { input: '', search: '', - pickupz: 99 + pickupz: 99, } }, filters: { - marked: marked + marked: marked, }, mounted() {}, computed: mapState({ - otherNodes: state => state.otherNodes, - configPositions: state => state.configPositions, - configEmoji: state => state.configEmoji + otherNodes: (state) => state.otherNodes, + configPositions: (state) => state.configPositions, + configEmoji: (state) => state.configEmoji, + toolmode: (state) => state.ui.mode, }), methods: { onActivated() { @@ -162,7 +262,7 @@ export default { y, width, height, - zindex + zindex, }) }, onDrag(x, y) { @@ -188,7 +288,7 @@ export default { y, width, height, - zindex + zindex, }) }, append(emoji) { @@ -199,19 +299,19 @@ export default { nodeid = this.nodeid this.$store.dispatch('addEmoji', { nodeid, - emojitext + emojitext, }) this.input = '' - } + }, }, directives: { focus: { inserted(el) { el.focus() - } - } - } + }, + }, + }, } </script> diff --git a/app/src/experimental/PanZoomContainer.vue b/app/src/experimental/PanZoomContainer.vue index 30218df02efcbdb98934f493cbffd30606c5734f..5e2d17321a59b88a49ae82a7b6abfe6f2c35cea6 100644 --- a/app/src/experimental/PanZoomContainer.vue +++ b/app/src/experimental/PanZoomContainer.vue @@ -1,38 +1,6 @@ -<style scoped> -.outer { - height: 100%; - width: 100%; - position: relative; - overflow: hidden; - background-color: rgb(245, 245, 245); -} -.inner { - width: 2000px; - height: 2000px; - position: absolute; - top: 0; - left: 0; - transform-origin: 0 0; - background-size: 40px 40px; - background-color: rgb(245, 245, 245); - border: 1px solid rgba(0, 0, 0, 0.25); - background-image: radial-gradient( - circle, - rgba(0, 0, 0, 0.5) 1px, - rgba(0, 0, 0, 0) 1px - ); -} -.inner.active { - touch-action: none; - -ms-touch-action: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; -} -</style> - <template> <div + v-if="mode == 'move'" ref="container" class="outer" v-on:wheel.capture="onWheel" @@ -54,7 +22,23 @@ transform: `translate(${translation.x}px, ${translation.y}px) scale(${scale})`, }" > - {{ JSON.stringify(interaction) }} + <!-- {{ JSON.stringify(interaction) }} --> + <!-- view paramaters not being utilised ? --> + <!-- {{ mode }} --> + <slot /> + </div> + </div> + + <div v-else ref="container" class="outer"> + <div + ref="innerContainer" + v-bind:class="{ inner: true, active: true }" + v-bind:style="{ + width: `${width}px`, + height: `${height}px`, + transform: `translate(${translation.x}px, ${translation.y}px) scale(${scale})`, + }" + > <slot /> </div> </div> @@ -78,6 +62,7 @@ export default { computed: { ...mapState({ interaction: (state) => state.ui.interaction, + mode: (state) => state.ui.mode, }), }, props: { @@ -219,3 +204,36 @@ export default { }, } </script> +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +.outer { + height: 100%; + width: 100%; + position: relative; + overflow: hidden; + background-color: rgb(245, 245, 245); +} +.inner { + width: 2000px; + height: 2000px; + position: absolute; + top: 0; + left: 0; + transform-origin: 0 0; + background-size: 40px 40px; + background-color: rgb(245, 245, 245); + /* border: 1px solid rgba(0, 0, 0, 0.25); */ + background-image: radial-gradient( + circle, + rgba(0, 0, 0, 0.5) 1px, + rgba(0, 0, 0, 0) 1px + ); +} +.inner.active { + touch-action: none; + -ms-touch-action: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} +</style> diff --git a/app/src/experimental/uiStore.js b/app/src/experimental/uiStore.js index b1d4cb3e632e15c3f860f042297b62629b22fd48..0c730720f9b8f9f6039bdfcc369cb7e84b792a35 100644 --- a/app/src/experimental/uiStore.js +++ b/app/src/experimental/uiStore.js @@ -17,7 +17,7 @@ const store = { links: [], nodes: [], }, - mode: 'move', + mode: 'select', scale: 1, translation: { x: 0, diff --git a/app/src/views/Sandbox.vue b/app/src/views/Sandbox.vue index c662b58680041141fc5609e53d829f3d972609b8..26fd910ad419d29f389ffc8645c075037d45cf54 100644 --- a/app/src/views/Sandbox.vue +++ b/app/src/views/Sandbox.vue @@ -6,8 +6,6 @@ v-bind:scale="scale" v-bind:translation="translation" > - <h1>Nodes</h1> - <OtherNodeslayer v-for="value in otherNodes" v-bind:key="value.node_id" @@ -21,12 +19,12 @@ v-bind:nodetext="value.node_text" /> </PanZoomContainer> - <SelectionLayer + <!-- <SelectionLayer v-if="domContainerReady" v-bind:shape="interaction.shape" v-bind:width="elementWidth" v-bind:height="elementHeight" - /> + /> --> <ModeToolbar /> <ViewToolbar /> </div> @@ -38,11 +36,13 @@ import NodesLayer from '@/components/NodesLayer' import OtherNodeslayer from '@/components/OtherNodeslayer.vue' import ModeToolbar from '@/experimental/ModeToolbar' import ViewToolbar from '@/experimental/ViewToolbar' -import SelectionLayer from '@/experimental/layers/SelectionLayer' +// import SelectionLayer from '@/experimental/layers/SelectionLayer' +import { shortcutsMixin } from '@/components/mixins/shortcutsMixin.js' import { mapGetters, mapState } from 'vuex' export default { name: 'Sandbox', + mixins: [shortcutsMixin], data: function () { return { elementWidth: undefined, @@ -61,6 +61,7 @@ export default { translation: (state) => state.ui.translation, myNodes: (state) => state.myNodes, otherNodes: (state) => state.otherNodes, + shortcutstate: (state) => state.shortcutstate, }), ...mapGetters({ activeMode: 'ui/activeMode', @@ -71,6 +72,19 @@ export default { window.addEventListener('resize', this.handleResize) this.handleResize() }, + + created() { + if (typeof window !== 'undefined') { + document.addEventListener('keydown', this.handleKeyPress) + } + }, + + beforeDestroy() { + if (typeof window !== 'undefined') { + document.removeEventListener('keydown', this.handleKeyPress) + } + }, + destroyed() { window.removeEventListener('resize', this.handleResize) }, @@ -80,12 +94,17 @@ export default { this.elementWidth = offsetWidth this.elementHeight = offsetHeight }, + + // This is here to support the shortcuts + addNode() { + this.$store.dispatch('addNode') + }, }, components: { ModeToolbar, ViewToolbar, PanZoomContainer, - SelectionLayer, + // SelectionLayer, NodesLayer, OtherNodeslayer, },