Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

223 lignes
6.3 KiB

  1. <template>
  2. <nav class="[&>*]:m-3 text-black w-48">
  3. <div class="flex flex-col ui">
  4. <button @click="resetView">home</button>
  5. <button @click="addPoint">Add Point</button>
  6. <div class="flex flex-col my-6">
  7. <button
  8. v-for="marker in sortedMarkers"
  9. :key="marker.order"
  10. @click="selectMarker(marker)"
  11. :class="marker.id === selectedMarker.id ? 'text-green-500 text-bold' : ''"
  12. >
  13. {{ marker.name }}
  14. </button>
  15. </div>
  16. <div v-if="isAMarkerSelected">
  17. <input
  18. ref="textInputMarkerName"
  19. type="text"
  20. v-model="selectedMarker.name"
  21. class="w-40 my-4"
  22. />
  23. <textarea v-model="selectedMarker.annotation" class="w-40 h-40"></textarea>
  24. <button @click="saveMarker">Save</button>
  25. <button @click="unselectMarker">Cancel</button>
  26. </div>
  27. </div>
  28. </nav>
  29. </template>
  30. <script setup lang="ts">
  31. import type { Story, Marker } from '@/types/virages'
  32. import { onMounted, ref, inject, nextTick, type Ref, computed } from 'vue'
  33. import OpenSeadragon from 'openseadragon'
  34. import { useIsMobile } from '@/composables/IsMobile'
  35. import { useViewer } from '@/composables/Viewer'
  36. import { useStoryStore } from '@/stores/story'
  37. import { useEscapeKey } from '@/composables/EscapeKey'
  38. const props = defineProps<{ story: Story }>()
  39. const Viewer = inject<Ref<OpenSeadragon.Viewer>>('Viewer')
  40. const selectedMarker = ref<Marker>({} as Marker)
  41. const textInputMarkerName = ref<HTMLInputElement | null>(null)
  42. const {
  43. zoomTo,
  44. goHome,
  45. enableMouseNav,
  46. disableMouseNav,
  47. injectMarker,
  48. addClickHandler,
  49. removeClickHandler,
  50. getZoom,
  51. getBounds,
  52. pointFromPixel,
  53. } = useViewer(Viewer)
  54. const store = useStoryStore()
  55. const isAMarkerSelected = computed(() => {
  56. return selectedMarker.value.id !== undefined
  57. })
  58. const sortedMarkers = computed(() => {
  59. return [...props.story.markers].sort((a, b) => a.order - b.order)
  60. })
  61. const resetView = () => {
  62. goHome()
  63. unselectMarker()
  64. }
  65. const addPoint = () => {
  66. unselectMarker()
  67. createMarkerOnClick()
  68. document.querySelector('.openseadragon-canvas')?.classList.add('cursor-crosshair')
  69. }
  70. const saveMarker = () => {
  71. unselectMarker()
  72. }
  73. const selectMarker = (marker: Marker) => {
  74. document.querySelectorAll('.marker-selected').forEach((el) => {
  75. el.classList.remove('marker-selected')
  76. })
  77. const theMarker = document.querySelector('.marker-id-' + marker.id)
  78. theMarker?.classList.add('marker-selected')
  79. const markerRectangle = new OpenSeadragon.Rect(
  80. marker.bounds.x,
  81. marker.bounds.y,
  82. marker.bounds.width,
  83. marker.bounds.height,
  84. )
  85. Viewer?.value.viewport.fitBoundsWithConstraints(markerRectangle, false)
  86. selectedMarker.value = marker
  87. nextTick(() => {
  88. const isMobile = useIsMobile()
  89. if (!isMobile) {
  90. textInputMarkerName.value?.focus()
  91. }
  92. })
  93. }
  94. const unselectMarker = () => {
  95. selectedMarker.value = {} as Marker
  96. document.querySelector('.marker-selected')?.classList.remove('marker-selected')
  97. }
  98. const createMarker = (marker: Marker) => {
  99. const overlay = document.createElement('button')
  100. overlay.className = `marker-id-${marker.id} marker`
  101. overlay.title = marker.name
  102. overlay.onfocus = function () {
  103. selectMarker(marker)
  104. }
  105. overlay.addEventListener('click', function () {
  106. selectMarker(marker)
  107. })
  108. overlay.addEventListener('mouseover', function () {
  109. disableMouseNav()
  110. })
  111. overlay.addEventListener('mouseout', function () {
  112. enableMouseNav()
  113. })
  114. injectMarker(marker, overlay)
  115. }
  116. const createMarkerOnClick = () => {
  117. const addPointhandler = addClickHandler((event) => {
  118. const newMarker: Marker = {
  119. id: props.story.markers.length,
  120. order: props.story.markers.length,
  121. name: '',
  122. bounds: getBounds(),
  123. point: pointFromPixel(event.position),
  124. zoom: getZoom(),
  125. annotation: '',
  126. }
  127. store.addMarker(props.story, newMarker)
  128. createMarker(newMarker)
  129. document.querySelector('.openseadragon-canvas')?.classList.remove('cursor-crosshair')
  130. selectMarker(newMarker)
  131. removeClickHandler(addPointhandler)
  132. })
  133. }
  134. const editMarkerOnDragOrZoom = () => {
  135. Viewer?.value.addHandler('canvas-drag', (event) => {
  136. if (isAMarkerSelected.value) {
  137. Viewer.value.gestureSettingsMouse.dragToPan = false
  138. const point = Viewer?.value.viewport.pointFromPixel(event.position)
  139. selectedMarker.value.point = point
  140. selectedMarker.value.bounds = Viewer?.value.viewport.getBounds()
  141. const overlay = document.querySelector('.marker-id-' + selectedMarker.value.id)
  142. Viewer?.value.updateOverlay(overlay as Element, point)
  143. }
  144. })
  145. Viewer?.value.addHandler('canvas-release', () => {
  146. Viewer.value.gestureSettingsMouse.dragToPan = true
  147. })
  148. Viewer?.value.addHandler('zoom', (event) => {
  149. if (isAMarkerSelected.value) {
  150. selectedMarker.value.zoom = event.zoom
  151. selectedMarker.value.bounds = Viewer?.value.viewport.getBounds()
  152. }
  153. })
  154. }
  155. const loadStory = (markers: Marker[]) => {
  156. markers.forEach((marker) => {
  157. createMarker(marker)
  158. })
  159. }
  160. const initScalebar = () => {
  161. Viewer.value.scalebar({
  162. type: OpenSeadragon.ScalebarType.MAP,
  163. pixelsPerMeter: 1000000,
  164. minWidth: '74px',
  165. location: OpenSeadragon.ScalebarLocation.BOTTOM_RIGHT,
  166. color: 'black',
  167. fontColor: 'black',
  168. backgroundColor: 'rgba(255, 255, 255, 0.5)',
  169. barThickness: 1,
  170. stayInsideImage: false,
  171. xOffset: 20,
  172. yOffset: 20,
  173. })
  174. }
  175. useEscapeKey(() => {
  176. unselectMarker()
  177. resetView()
  178. })
  179. onMounted(() => {
  180. nextTick(() => {
  181. Viewer?.value.clearOverlays()
  182. // Viewer.value.viewport.defaultZoomLevel = 1
  183. loadStory(props.story.markers)
  184. editMarkerOnDragOrZoom()
  185. initScalebar()
  186. Viewer.value.bookmarkUrl()
  187. zoomTo(6) // COMPOSABLE WORKS
  188. })
  189. })
  190. </script>
  191. <style>
  192. .ui button {
  193. @apply border-2 border-black px-4 py-2 rounded-xl bg-slate-300 hover:bg-neutral-100 m-1;
  194. }
  195. .ui input,
  196. .ui textarea {
  197. @apply border-2 border-black px-4 py-2;
  198. }
  199. .marker {
  200. @apply w-8 h-8 rounded-full shadow-2xl bg-green-500 bg-opacity-50 hover:bg-green-600 hover:bg-opacity-100 hover:cursor-pointer pointer-events-auto hover:scale-150 transition-all border-2 border-white border-8 transform -translate-y-1/2 -translate-x-1/2;
  201. }
  202. .marker-selected {
  203. @apply border-blue-400 border-8 bg-white animate-pulse hover:cursor-move hover:bg-white;
  204. }
  205. </style>