main.vue 12 KB

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