main.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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 && funcsResizeable"
  45. :split="funcsActive?funcsSize:'0px'"
  46. @onSplitResize="funcsSizeUpdate"
  47. >
  48. <flow-panel
  49. :registry="registry"
  50. @toggleResizeable="funcsResizeable=!funcsResizeable"
  51. @toggleStickySockets="managerStickySockets=!managerStickySockets"
  52. />
  53. <flow-manager
  54. ref="flowManager"
  55. :activity="activity"
  56. :registry="registry"
  57. @funcsPanelToggle="funcsActive=!funcsActive"
  58. @nodeInspect="nodeInspectStart(...arguments)"
  59. @documentSave="documentSave"
  60. width="100%"
  61. height="100%"/>
  62. </hx-split>
  63. </div>
  64. <div class="app-chat">
  65. <app-chat/>
  66. </div>
  67. <!-- Node inspector -->
  68. <!-- Move this to a different place -->
  69. <!-- And rename it to inspector -->
  70. <hx-modal
  71. class="flow-modal"
  72. v-if="nodeInspect"
  73. @close="nodeInspect=null"
  74. @keydown.esc="nodeInspect=null"
  75. >
  76. <div slot="header">Node inspector:</div>
  77. <div slot="body" class="flow-modal__body">
  78. <div class="flow-modal__info">
  79. <svg class="flow-view preview activity flow-detail flow-linking" width="100%" height="100%" viewBox="0 0 300 200">
  80. <flow-panzoom>
  81. <flow-node
  82. style="pointer-events:none"
  83. ref="modalPreviewNode"
  84. :id="nodeInspect.id"
  85. transform="translate(150,100)"
  86. :match="{}"
  87. :label="nodeInspect.label"
  88. :inputs= "registry[nodeInspect.src].inputs"
  89. :output= "registry[nodeInspect.src].output"
  90. :activity= "activity[nodeInspect.id]"
  91. :nodeStyle= "registry[nodeInspect.src].style"
  92. />
  93. </flow-panzoom>
  94. </svg>
  95. <div class="flow-modal__params" v-if="nodeInspect.prop" >
  96. <h3>Node Parameters</h3>
  97. <div class="flow-modal__param" v-for="(v,k) in nodeInspect.prop">
  98. <label>{{ k }}</label>
  99. <input
  100. ref="nodeInspectProp"
  101. type="text"
  102. @keydown.prevent.stop.enter="nodeInspect=null;"
  103. @keydown.esc="nodeInspect=null"
  104. v-model="nodeInspect.prop[k]">
  105. </div>
  106. </div>
  107. <div class="flow-modal__properties">
  108. <h3>Node Properties</h3>
  109. <label>Description</label>
  110. <div class="property">Bogus description</div>
  111. <label>Help</label>
  112. <div class="property">Connect to input a thing and goes to output another thing</div>
  113. <label>Result</label>
  114. <div class="property">{{ activity && activity[nodeInspect.id] && activity[nodeInspect.id].data }}</div>
  115. </div>
  116. </div>
  117. <label>label</label>
  118. <input
  119. ref="modalInput"
  120. type="text"
  121. @keydown.enter="nodeInspect=null;"
  122. @keydown.esc="nodeInspect=null"
  123. v-model="nodeInspect.label" >
  124. </div>
  125. <div class="flow-modal__footer" slot="footer">
  126. <button class="secondary-inverse" @click="nodeProcess(nodeInspect);nodeInspect = null">Run</button>
  127. <button @click="nodeInspect=false">OK</button>
  128. </div>
  129. </hx-modal>
  130. </div>
  131. </div>
  132. </template>
  133. <script>
  134. import AppChat from '@/components/chat'
  135. import FlowManager from '@/components/flow/manager'
  136. import FlowNode from '@/components/flow/node'
  137. import FlowPanzoom from '@/components/flow/panzoom'
  138. import FlowPanel from './panel'
  139. import HxSplit from '@/components/shared/hx-split'
  140. import HxModal from '@/components/shared/hx-modal'
  141. import 'reset-css/reset.css'
  142. import '@/assets/dark-theme.css'
  143. import '@/assets/style.css'
  144. // import nodeData from './nodedata'
  145. const defRegistry = {
  146. 'Input': {
  147. categories: ['core'],
  148. output: 'any',
  149. style: { color: '#686', textColor: '#fff', shape: 'circle' },
  150. props: {} // should be sent in the node
  151. },
  152. 'Variable': {
  153. categories: ['core'],
  154. output: 'any',
  155. style: { color: '#88a', textColor: '#000' },
  156. props: {init: ''}
  157. },
  158. 'Const': {
  159. categories: ['core'],
  160. output: 'any',
  161. style: {
  162. color: '#555',
  163. textColor: '#333',
  164. shape: 'circle'
  165. },
  166. props: {value: ''}
  167. }
  168. }
  169. export default {
  170. components: {FlowManager, FlowPanel, FlowNode, FlowPanzoom, HxSplit, HxModal, AppChat},
  171. data () {
  172. return {
  173. registry: JSON.parse(JSON.stringify(defRegistry)),
  174. activity: {},
  175. /* {
  176. // Fixed default stuff
  177. 'Test': {
  178. group: 'Generic',
  179. output: 'any',
  180. style: {
  181. shape: 'thing'
  182. }
  183. },
  184. 'MatMul': { group: 'Machine learning', inputs: [ '[]float32', '[]float32' ], output: '[]float32', style: { color: '#a44', textColor: 'white' } },
  185. 'Activator': { group: 'Machine learning', inputs: [ '[]float32' ], output: '[]float32', style: { color: '#a44', textColor: 'white', shape: 'circle' } },
  186. 'test': { group: 'Text', inputs: [ '[]float32', 'string' ], output: 'string', style: {'color': '#a93'} },
  187. 'reverse': { group: 'Text', inputs: [ 'string' ], output: 'string', style: {'color': '#a93'} },
  188. 'fetch': { group: 'json', output: 'json', style: {'color': '#99a'} },
  189. 'jsonExtract': { group: 'json', inputs: ['json'], output: 'string', style: {'color': '#99a'} },
  190. 'string': { group: 'Visualization', inputs: ['string'], style: {'color': '#9a9'} },
  191. 'lineGraph': { group: 'Visualization', inputs: ['[]float32', '[]float32'], style: {'color': '#9a9'} }
  192. }, */
  193. nodeInspect: false,
  194. funcsSize: '250px',
  195. funcsActive: true,
  196. funcsResizeable: false,
  197. dark: false
  198. }
  199. },
  200. watch: {
  201. nodeInspect: {
  202. handler (val, oldVal) {
  203. if (!val === null && !oldVal) { return }
  204. if (!val) {
  205. this.$refs.flowManager.sendDocumentUpdate()
  206. return
  207. }
  208. this.$refs.flowManager.sendFlowEvent('nodeUpdate', [this.nodeInspect])
  209. },
  210. deep: true
  211. }
  212. },
  213. mounted () {
  214. // Handle incoming things
  215. this.$flowService.on('sessionJoin', (v) => {
  216. if (v.id !== this.$route.params.sessId) {
  217. this.$router.push('/' + v.id) // Swap to ID
  218. }
  219. })
  220. this.$flowService.on('registry', (v) => {
  221. let res = {}
  222. for (let k of Object.keys(v.data)) {
  223. const e = v.data[k]
  224. res[k] = {
  225. categories: e.categories,
  226. inputs: e.inputs,
  227. inputDesc: e.inputDesc,
  228. output: e.output,
  229. outputDesC: e.outputDesc,
  230. style: e.extra && e.extra.style
  231. }
  232. }
  233. this.registry = Object.assign({}, defRegistry, res)
  234. })
  235. this.$flowService.on('nodeActivity', (v) => {
  236. this.activity = v.data || {}
  237. })
  238. // Connected
  239. this.$flowService.connected(() => {
  240. // Make this in a service
  241. if (this.$route.params.sessId === undefined) {
  242. this.$flowService.sessionNew()
  243. return
  244. }
  245. this.$flowService.sessionLoad(undefined, this.$route.params.sessId)
  246. })
  247. },
  248. methods: {
  249. nodeInspectStart (node) { // node
  250. this.nodeInspect = node
  251. this.$nextTick(() => {
  252. if (!this.$refs.modalInput) { return }
  253. let targetInput = this.$refs.modalInput
  254. if (this.$refs.nodeInspectProp && this.$refs.nodeInspectProp.length > 0) {
  255. targetInput = this.$refs.nodeInspectProp[0]
  256. }
  257. targetInput.setSelectionRange(0, targetInput.value.length)
  258. targetInput.focus()
  259. })
  260. },
  261. nodeProcess (node) {
  262. this.$flowService.nodeProcess(node.id)
  263. },
  264. funcsSizeUpdate (ev, size) {
  265. this.funcsSize = size
  266. },
  267. documentSave () {
  268. this.$flowService.documentSave()
  269. }
  270. // Update individual nodes/links
  271. }
  272. }
  273. </script>
  274. <style>
  275. .flow-main {
  276. height:100%;
  277. display:flex;
  278. flex-direction: column;
  279. }
  280. .flow-main .app-flow-container {
  281. position:relative;
  282. flex:1;
  283. }
  284. .flow-main .flow-container {
  285. position:absolute;
  286. top:0;
  287. right:0;
  288. bottom:0;
  289. left:0;
  290. }
  291. .flow-main .app-header {
  292. padding:0 14px;
  293. height: 50px;
  294. display:flex;
  295. justify-content: space-between;
  296. align-items: center;
  297. }
  298. .flow-main .app-info {
  299. color: #aaa;
  300. font-size:10px;
  301. margin:20px;
  302. padding:20px;
  303. position:absolute;
  304. right:0;
  305. bottom:3%;
  306. }
  307. .flow-main .app-watermark {
  308. position:absolute;
  309. top:40%;
  310. text-align:center;
  311. width:100%;
  312. font-size:100px;
  313. text-shadow: 1px 1px 1px rgba(255,255,255,0.5), -1px -1px 1px rgba(0,0,0,0.05);
  314. }
  315. .split .splitter{
  316. flex-basis:0;
  317. position:relative;
  318. background: rgba(208,208,208,0.9);
  319. }
  320. .split:not(.resizeable) .content:first-child {
  321. transition: all 0.3s;
  322. }
  323. .split.resizeable.horizontal .splitter::after {
  324. display:flex;
  325. justify-content: center;
  326. align-items: center;
  327. z-index:100;
  328. content:" ";
  329. position:absolute;
  330. top:20%;
  331. bottom:20%;
  332. left:0;
  333. width:10px;
  334. background: rgba(0,0,0,0.4);
  335. transition: all 0.3s;
  336. }
  337. .app-horizontal {
  338. height:100%;
  339. max-height:100%;
  340. display:flex;
  341. position:relative;
  342. flex-flow:row;
  343. overflow:hidden;
  344. }
  345. .app-chat {
  346. position:absolute;
  347. top:0;
  348. right:0;
  349. height:100%;
  350. }
  351. .flow-modal__info {
  352. padding-bottom:20px;
  353. display:flex;
  354. flex-flow:row;
  355. }
  356. /* Columns */
  357. .flow-modal__info > * {
  358. margin-right:10px;
  359. flex:1;
  360. }
  361. .flow-modal__body label {
  362. font-size:14px;
  363. display:flex;
  364. flex-flow:row;
  365. font-weight:bold;
  366. padding-bottom:10px;
  367. }
  368. .flow-modal__info .property {
  369. padding-left:20px;
  370. font-size:12px;
  371. }
  372. .flow-modal__footer {
  373. display:flex;
  374. justify-content: space-between;
  375. }
  376. </style>