main.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. <template>
  2. <div class="flow-main" :class="{dark:dark}">
  3. <div class="app-header">
  4. Flow
  5. <button @click="dark=!dark">{{ dark?'light':'dark' }}</button>
  6. </div>
  7. <div class="app-horizontal">
  8. <div class="app-flow-container">
  9. <div class="app-watermark">PROTOTYPE</div>
  10. <div class="app-info">
  11. <h4>HELP</h4>
  12. <ul>
  13. <li><b>Pan</b>: Drag with Middle Mouse or Ctrl+left mouse button</li>
  14. <li><b>Zoom</b>: Mouse wheel up and down to zoom in and out</li>
  15. <li><b>New Node</b>: Create a node by dragging a fn from left panel into area</li>
  16. <li><b>Remove Node</b>: Middle click in a node to remove a node</li>
  17. <li><b>Inspect node</b>: Double click on a node to get detailed information</li>
  18. <li><b>Move Node</b>: Mouse click and drag</li>
  19. <li><b>Links</b>: Press [shift] and Drag from a node/socket to a socket highlighted in green</li>
  20. <li><b>Links(alternative)</b>: Toggle socket visualisation in the panel and Drag from a socket to a socket highlighted in green</li>
  21. <li><b>Remove Link</b>: Simple click on the link when it turns red</li>
  22. </ul>
  23. <h4>TODO:</h4>
  24. <ul>
  25. <li>UX/UI: Undo changes</li>
  26. <li>UX/UI: Special nodes with display capabilities (images,datatables,...)</li>
  27. <li>UX/UI: Group nodes into a single box, exposing inputs and outputs</li>
  28. <li>UX/UI: Implement touch</li>
  29. <li>UX/UI: drop link in node to link to next compatible available input</li>
  30. <li>Registry: Synchronize registry with server(GET)</li>
  31. <li>Collaboration: Better concurrent editing/message passing (testing)</li>
  32. <li>Collaboration: Improve document synchronization</li>
  33. <li>FlowServer: Build the graph on the server and run</li>
  34. <li>FlowPkg: Create training mechanism</li>
  35. <li>FlowPkg: matrix pooling function example</li>
  36. </ul>
  37. <br>
  38. <small>&copy; Luis Figueiredo (luisf@hexasoftware.com)</small>
  39. </div>
  40. <!--:value="nodeData"
  41. @input="documentUpdate"-->
  42. <hx-split
  43. dir="horizontal"
  44. :resizeable="funcsActive"
  45. :split="funcsActive?funcsSize:'0px'"
  46. @onSplitResize="funcsSizeUpdate"
  47. >
  48. <div class="flow-panel__container">
  49. <div class="flow-panel__selector">
  50. <button @click="panel='palette'">Funcs</button>
  51. <button @click="panel='inspector';nodeInspect = nodeActive">Inspector</button>
  52. </div>
  53. <transition name="fade">
  54. <flow-funcs
  55. v-show="panel=='palette'"
  56. :registry="registry"
  57. @toggleResizeable="funcsResizeable=!funcsResizeable"
  58. @toggleStickySockets="managerStickySockets=!managerStickySockets"
  59. />
  60. </transition>
  61. <transition name="fade">
  62. <flow-inspector
  63. ref="inspector"
  64. v-show="panel=='inspector'"
  65. :registry="registry"
  66. :activity="activity"
  67. :node-inspect="nodeInspect"
  68. @nodeProcess="nodeProcess($event)"
  69. />
  70. </transition>
  71. </div>
  72. <flow-manager
  73. ref="flowManager"
  74. :activity="activity"
  75. :registry="registry"
  76. @funcsPanelToggle="funcsActive=!funcsActive"
  77. @nodeInspect="nodeInspectStart(...arguments)"
  78. @nodeDblClick="nodeInspectStart(...arguments,true)"
  79. @documentSave="documentSave"
  80. width="100%"
  81. height="100%"/>
  82. </hx-split>
  83. </div>
  84. <div class="app-chat">
  85. <app-chat/>
  86. </div>
  87. <!-- Node inspector -->
  88. <!-- Move this to a different place -->
  89. <!-- And rename it to inspector -->
  90. <!--<hx-modal
  91. class="flow-modal"
  92. v-if="nodeInspect"
  93. @close="nodeInspect=null"
  94. @keydown.esc="nodeInspect=null"
  95. >
  96. <div slot="header">Node inspector:</div>
  97. <div slot="body" class="flow-modal__body">
  98. <div class="flow-modal__info">
  99. <svg class="flow-view preview activity flow-node--detail flow-node--activity flow-linking" width="100%" height="100%" viewBox="0 0 300 200">
  100. <flow-panzoom>
  101. <flow-node
  102. style="pointer-events:none"
  103. ref="modalPreviewNode"
  104. :id="nodeInspect.id"
  105. transform="translate(150,100)"
  106. :match="{}"
  107. :label="nodeInspect.label"
  108. :inputs= "registry[nodeInspect.src].inputs"
  109. :output= "registry[nodeInspect.src].output"
  110. :activity= "activity[nodeInspect.id]"
  111. :nodeStyle= "registry[nodeInspect.src].style"
  112. />
  113. </flow-panzoom>
  114. </svg>
  115. <div class="flow-modal__params" v-if="nodeInspect.prop" >
  116. <h3>Node Parameters</h3>
  117. <div class="flow-modal__param" v-for="(v,k) in nodeInspect.prop">
  118. <label>{{ k }}</label>
  119. <input
  120. ref="nodeInspectProp"
  121. type="text"
  122. @keydown.prevent.stop.enter="nodeInspect=null;"
  123. @keydown.esc="nodeInspect=null"
  124. v-model="nodeInspect.prop[k]">
  125. </div>
  126. </div>
  127. <div class="flow-modal__properties">
  128. <h3>Node Properties</h3>
  129. <label>Description</label>
  130. <div class="property">Bogus description</div>
  131. <label>Help</label>
  132. <div class="property">Connect to input a thing and goes to output another thing</div>
  133. <div v-if="activity && activity[nodeInspect.id] && activity[nodeInspect.id].data" class="flow-modal__properties-result">
  134. <label>Result</label>
  135. <div class="property">{{ activity && activity[nodeInspect.id] && activity[nodeInspect.id].data }}</div>
  136. </div>
  137. <div v-if="activity && activity[nodeInspect.id] && activity[nodeInspect.id].error" class="flow-modal__properties-error">
  138. <label>Error</label>
  139. <div class="property">{{ activity && activity[nodeInspect.id] && activity[nodeInspect.id].error }}</div>
  140. </div>
  141. <div style="flex: 1 0 100%;">&nbsp;</div>
  142. <button class="flow-modal__button-run primary-inverse" @click="nodeProcess(nodeInspect)">Run</button>
  143. </div>
  144. </div>
  145. <label>label</label>
  146. <input
  147. ref="modalInput"
  148. type="text"
  149. @keydown.enter="nodeInspect=null;"
  150. @keydown.esc="nodeInspect=null"
  151. v-model="nodeInspect.label" >
  152. </div>
  153. <div class="flow-modal__footer" slot="footer">
  154. <button class="primary" @click="nodeInspect=false">OK</button>
  155. </div>
  156. </hx-modal>-->
  157. </div>
  158. </div>
  159. </template>
  160. <script>
  161. import AppChat from '@/components/chat'
  162. import FlowManager from '@/components/flow/manager'
  163. import FlowNode from '@/components/flow/node'
  164. import FlowPanzoom from '@/components/flow/panzoom'
  165. import FlowFuncs from './panel-funcs'
  166. import FlowInspector from './panel-inspector'
  167. import HxSplit from '@/components/shared/hx-split'
  168. import HxModal from '@/components/shared/hx-modal'
  169. import defRegistry from './defregistry'
  170. import 'reset-css/reset.css'
  171. import '@/assets/dark-theme.css'
  172. import '@/assets/style.css'
  173. // import nodeData from './nodedata'
  174. export default {
  175. components: {FlowManager, FlowNode, FlowPanzoom, FlowInspector, FlowFuncs, HxSplit, HxModal, AppChat},
  176. data () {
  177. return {
  178. registry: JSON.parse(JSON.stringify(defRegistry)),
  179. activity: {},
  180. panel: 'palette',
  181. nodeActive: null,
  182. nodeInspect: null,
  183. funcsSize: '250px',
  184. funcsActive: true,
  185. funcsResizeable: false,
  186. dark: false
  187. }
  188. },
  189. watch: {
  190. nodeInspect: {
  191. handler (val, oldVal) {
  192. if (!val === null && !oldVal) { return }
  193. if (!val) {
  194. this.$refs.flowManager.sendDocumentUpdate()
  195. return
  196. }
  197. this.$refs.flowManager.sendFlowEvent('nodeUpdate', [this.nodeInspect])
  198. },
  199. deep: true
  200. }
  201. },
  202. mounted () {
  203. // Handle incoming things
  204. this.$flowService.on('sessionJoin', (v) => {
  205. if (v.id !== this.$route.params.sessId) {
  206. this.$router.push('/' + v.id) // Swap to ID
  207. }
  208. })
  209. this.$flowService.on('registry', (v) => {
  210. let res = {}
  211. for (let k of Object.keys(v.data)) {
  212. const e = v.data[k]
  213. res[k] = {
  214. categories: e.categories,
  215. inputs: e.inputs,
  216. inputDesc: e.inputDesc,
  217. output: e.output,
  218. outputDesC: e.outputDesc,
  219. style: e.extra && e.extra.style
  220. }
  221. }
  222. this.registry = Object.assign({}, defRegistry, res)
  223. })
  224. this.$flowService.on('nodeActivity', (v) => {
  225. this.activity = v.data || {}
  226. })
  227. this.$flowService.on('sessionLog', (v) => {
  228. // Make this elsewhere
  229. console.log(v.data)
  230. })
  231. // Connected
  232. this.$flowService.connected(() => {
  233. // Make this in a service
  234. if (this.$route.params.sessId === undefined) {
  235. this.$flowService.sessionNew()
  236. return
  237. }
  238. this.$flowService.sessionLoad(undefined, this.$route.params.sessId)
  239. })
  240. },
  241. methods: {
  242. nodeInspectStart (node, changePane) { // node
  243. this.nodeActive = node
  244. if (changePane) this.panel = 'inspector'
  245. if (this.panel !== 'inspector') {
  246. return
  247. }
  248. this.nodeInspect = node
  249. // if (!changePane) { return }
  250. this.$nextTick(() => {
  251. // panel input
  252. if (!this.$refs.inspector) { return }
  253. const insp = this.$refs.inspector
  254. let targetInput = this.$refs.inspector.$refs.labelInput
  255. if (insp.$refs.props && insp.$refs.props.length > 0) {
  256. targetInput = insp.$refs.props[0]
  257. }
  258. targetInput.setSelectionRange(0, targetInput.value.length)
  259. targetInput.focus()
  260. })
  261. },
  262. nodeProcess (node) {
  263. this.$flowService.nodeProcess(node.id)
  264. },
  265. funcsSizeUpdate (ev, size) {
  266. this.funcsSize = size
  267. },
  268. documentSave () {
  269. this.$flowService.documentSave()
  270. }
  271. // Update individual nodes/links
  272. }
  273. }
  274. </script>
  275. <style>
  276. .flow-main {
  277. height:100%;
  278. display:flex;
  279. flex-direction: column;
  280. }
  281. .flow-main .app-flow-container {
  282. position:relative;
  283. flex:1;
  284. }
  285. .flow-main .flow-container {
  286. position:absolute;
  287. top:0;
  288. right:0;
  289. bottom:0;
  290. left:0;
  291. }
  292. .flow-main .app-header {
  293. padding:0 14px;
  294. height: 50px;
  295. display:flex;
  296. justify-content: space-between;
  297. align-items: center;
  298. }
  299. .flow-main .app-info {
  300. color: #aaa;
  301. font-size:10px;
  302. margin:20px;
  303. padding:20px;
  304. position:absolute;
  305. right:0;
  306. bottom:3%;
  307. }
  308. .flow-main .app-watermark {
  309. position:absolute;
  310. top:40%;
  311. text-align:center;
  312. width:100%;
  313. font-size:100px;
  314. text-shadow: 1px 1px 1px rgba(255,255,255,0.5), -1px -1px 1px rgba(0,0,0,0.05);
  315. }
  316. .split .splitter{
  317. flex-basis:0;
  318. position:relative;
  319. background: rgba(208,208,208,0.9);
  320. }
  321. .split:not(.resizeable) .content:first-child {
  322. transition: all var(--transition-speed);
  323. }
  324. .split.resizeable.horizontal .splitter::after {
  325. opacity:0.4;
  326. display:flex;
  327. justify-content: center;
  328. align-items: center;
  329. z-index:100;
  330. content:" ";
  331. position:absolute;
  332. top:20%;
  333. bottom:20%;
  334. left:0;
  335. width:10px;
  336. background: rgba(0,0,0,0.4);
  337. transition: var(--transition-speed);
  338. }
  339. .app-horizontal {
  340. height:100%;
  341. max-height:100%;
  342. display:flex;
  343. position:relative;
  344. flex-flow:row;
  345. overflow:hidden;
  346. }
  347. .app-chat {
  348. position:absolute;
  349. top:0;
  350. right:0;
  351. height:100%;
  352. }
  353. .flow-panel__container {
  354. display:flex;
  355. flex-flow:column;
  356. flex:1;
  357. overflow:hidden;
  358. }
  359. .flow-panel__selector {
  360. color: var(--normal);
  361. display:flex;
  362. align-content: stretch;
  363. flex-shrink: 0;
  364. height:50px;
  365. border-bottom:solid 1px var(--primary);
  366. }
  367. .flow-panel__selector button{
  368. flex:1;
  369. }
  370. /*
  371. .flow-modal__info {
  372. padding-bottom:20px;
  373. display:flex;
  374. flex-flow:row;
  375. }
  376. .flow-modal__info > * {
  377. margin-right:10px;
  378. flex:1;
  379. }
  380. .flow-modal__body label {
  381. font-size:14px;
  382. display:flex;
  383. flex-flow:row;
  384. font-weight:bold;
  385. padding-bottom:10px;
  386. }
  387. .flow-modal__info .property {
  388. padding-left:20px;
  389. font-size:12px;
  390. }
  391. .flow-modal__properties-error .property{
  392. color: red;
  393. }
  394. .flow-modal__button-run {
  395. align-self: flex-end;
  396. width:100%;
  397. }
  398. .flow-modal__footer {
  399. }*/
  400. </style>