You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
6.2 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. export const modal = content => {
  2. const wrapper = document.createElement('a')
  3. wrapper.href = '/'
  4. wrapper.className = 'fullview'
  5. const fullview = document.createElement('div')
  6. fullview.innerHTML = content
  7. wrapper.appendChild(fullview)
  8. return wrapper
  9. }
  10. export class Display {
  11. constructor(dispatch, target) {
  12. this.target = target
  13. const findA = target => {
  14. while (target.nodeName !== 'A') {
  15. target = target.parentNode
  16. if (!target) return
  17. }
  18. return target
  19. }
  20. target.addEventListener('click', e => {
  21. let target = findA(e.target)
  22. if (!target) return
  23. window.history.pushState(null, "", target.href)
  24. dispatch(target.getAttribute('href'))
  25. e.preventDefault()
  26. })
  27. target.addEventListener('dragstart', e => {
  28. let target = findA(e.target)
  29. if (!target || !target.draggable) return
  30. // https://github.com/Bernardo-Castilho/dragdroptouch/pull/37
  31. // e.dataTransfer.clearData()
  32. for (const x of e.dataTransfer.types) { e.dataTransfer.clearData(x) }
  33. e.dataTransfer.setData('application/prs.x', target.dataset['id'])
  34. e.dataTransfer.effectAllowed = 'link'
  35. })
  36. target.addEventListener('dragenter', e => {
  37. let target = findA(e.target)
  38. if (!target) return
  39. e.preventDefault()
  40. })
  41. target.addEventListener('dragover', e => {
  42. let target = findA(e.target)
  43. if (!target) return
  44. e.preventDefault()
  45. })
  46. target.addEventListener('drop', e => {
  47. let target = findA(e.target)
  48. if (!target || !target.draggable) return
  49. dispatch({ action: 'link', from: e.dataTransfer.getData("application/prs.x"), to: target.dataset['id']})
  50. && e.preventDefault()
  51. })
  52. this.graph = new Springy.Graph()
  53. this.layout = new Springy.Layout.ForceDirected(
  54. this.graph,
  55. 15,
  56. 1000.0, // Node repulsion
  57. 0.5 // Damping
  58. )
  59. }
  60. renderIntro(intro) {
  61. this.target.appendChild(modal(intro))
  62. }
  63. render(state) {
  64. const target = document.createElement('div')
  65. target.className = 'wrapper'
  66. const field = document.createElement('ul')
  67. field.className = 'items'
  68. const graph = this.graph
  69. for (const item of state.items) {
  70. graph.addNode(new Springy.Node(item.id, item))
  71. }
  72. for (const item of state.links) {
  73. graph.addEdge(new Springy.Edge(item.id, graph.nodeSet[item.from], graph.nodeSet[item.to], {
  74. text: state.items[item.from].linkable.filter(i => state.items[item.to].linkable.includes(i)).join(', ')
  75. }))
  76. }
  77. let currentBB = this.layout.getBoundingBox()
  78. let width = this.target.scrollWidth || 100
  79. let height = this.target.scrollHeight || 100
  80. const ballRadius = Math.min(document.body.clientWidth, document.body.clientHeight) * 0.06
  81. var toScreen = function(p) {
  82. var size = currentBB.topright.subtract(currentBB.bottomleft);
  83. var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * (width - ballRadius * 2) + ballRadius;
  84. var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * (height - ballRadius * 2) + ballRadius;
  85. return new Springy.Vector(sx, sy);
  86. };
  87. var renderer = new Springy.Renderer(
  88. this.layout,
  89. () => {
  90. currentBB = this.layout.getBoundingBox()
  91. width = this.target.scrollWidth
  92. height = this.target.scrollHeight
  93. field.innerHTML = ''
  94. },
  95. function drawEdge(edge, p1, p2) {
  96. const dom = document.createElement('span')
  97. dom.innerText = edge.data.text
  98. p1 = toScreen(p1)
  99. p2 = toScreen(p2)
  100. if (p1.y > p2.y) [p1, p2] = [p2, p1]
  101. const a = p2.x - p1.x
  102. const b = p2.y - p1.y
  103. const negative = (a < 0) != (b < 0)
  104. let rad = Math.atan(b / a)
  105. if (negative) rad = rad + Math.PI
  106. if (rad > Math.PI / 2) {
  107. rad += Math.PI
  108. ;[p2, p1] = [p1, p2]
  109. }
  110. dom.className = 'line'
  111. dom.style.transform = 'rotate(' + rad + 'rad) translateY(-1em)'
  112. dom.style.left = p1.x + 'px'
  113. dom.style.top = p1.y + 'px'
  114. dom.style.right = p2.x + 'px'
  115. dom.style.bottom = p2.y + 'px'
  116. dom.style.width = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)) + 'px'
  117. // FIXME eigentlich falsches parent
  118. field.appendChild(dom)
  119. },
  120. function drawNode(item, p) {
  121. const target = toScreen(p)
  122. const dom = document.createElement('li')
  123. const a = document.createElement('a')
  124. const action = item.data.state == 'face-down' ? 'flip' : 'show'
  125. a.href = `/${item.id}/${action}`
  126. a.draggable = false
  127. if (item.data.state !== 'face-down') {
  128. a.style.backgroundImage = `url("/img/${item.data.icon}")`
  129. a.dataset['id'] = item.id
  130. a.draggable = true
  131. }
  132. dom.className = item.data.state
  133. dom.style.position = 'absolute'
  134. dom.style.left = (target.x - ballRadius) + 'px'
  135. dom.style.top = (target.y - ballRadius) + 'px'
  136. dom.appendChild(a)
  137. field.appendChild(dom)
  138. }, undefined, undefined,
  139. () => {
  140. const dd = new diffDOM.DiffDOM()
  141. const diff = dd.diff(this.target.children[0], field)
  142. dd.apply(this.target.children[0], diff)
  143. }
  144. );
  145. target.appendChild(field)
  146. if (state.items.filter(i => i.state != 'face-up').length == 0) {
  147. const a = document.createElement('a')
  148. a.className = 'continue'
  149. a.innerText = 'fertig'
  150. a.href = '/end'
  151. target.appendChild(a)
  152. }
  153. if (state.show !== null) {
  154. const data = state.items[state.show]
  155. const name = data.name
  156. const desc = data.desc
  157. const img = '/img/' + data.img
  158. const text = (data.text || []).map(v => '<p>' + v + '</p>').join('')
  159. const wrapper = modal(`
  160. <header>
  161. <img src=${img} />
  162. <h1>${name}</h1>
  163. <p class=desc>${desc}</p>
  164. </header>
  165. <div class=fulltext-wrapper><div class=fulltext>${text}
  166. <blockquote>${data.quote || ''}</blockquote></div></div>
  167. `)
  168. target.appendChild(wrapper)
  169. }
  170. if (state.special) {
  171. target.appendChild(modal(state.special))
  172. }
  173. const dd = new diffDOM.DiffDOM()
  174. const diff = dd.diff(this.target, target)
  175. dd.apply(this.target, diff)
  176. renderer.start();
  177. }
  178. }