ExperimentBaseExtracts.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <template>
  2. <div>
  3. <slot></slot>
  4. </div>
  5. </template>
  6. <script>
  7. import ExperimentBase from '@/mixins/ExperimentBase'
  8. import { mapGetters } from 'vuex'
  9. import { API_ROUTES, findNearestUpper, findNearestLower } from '@/functions'
  10. import { EXPERIMENT as experimentMsgId } from '@/../config.messagesId'
  11. export default {
  12. name: 'ExperimentBaseExtracts',
  13. mixins: [ExperimentBase],
  14. data() {
  15. return {
  16. // Updated when `setExtractConfig` is called
  17. extractConfig: {
  18. x: null,
  19. y: null
  20. },
  21. extracts: [],
  22. extractsInfos: null,
  23. showHoverBorder: null,
  24. lockConfig: null,
  25. comment: null,
  26. dialogMore: false,
  27. dialogLess: false
  28. }
  29. },
  30. computed: {
  31. ...mapGetters(['getHostURI'])
  32. },
  33. methods: {
  34. // Load extracts from the API
  35. async getExtracts(quality = 'min') {
  36. const URI = `${this.getHostURI}${API_ROUTES.getImageExtracts(this.sceneName, quality, this.extractConfig.x, this.extractConfig.y)}`
  37. const { data } = await fetch(URI)
  38. .then(async res => {
  39. res.json = await res.json()
  40. return res
  41. })
  42. .then(res => {
  43. if (!res.ok) throw new Error(res.json.message + res.json.data ? `\n${res.json.data}` : '')
  44. return res.json
  45. })
  46. data.extracts = data.extracts.map(x => this.getHostURI + x)
  47. return data
  48. },
  49. // There was an error loading extracts in v-img
  50. // (extracts have probably been removed from the server)
  51. // //
  52. // Get all extracts qualities and remove duplicates
  53. // Then do all the API extract-generation API calls then reload the page
  54. async extractsRemovedFromServerFallback() {
  55. this.loadingMessage = 'Synchronizing with the server...'
  56. try {
  57. const qualities = [...new Set(this.extracts.map(x => x.quality))]
  58. await Promise.all(qualities.map(x => this.getExtracts(x)))
  59. }
  60. catch (err) {
  61. console.error(err)
  62. this.loadingErrorMessage = 'Failed to synchronize with the server. Try reloading the page.'
  63. }
  64. finally {
  65. this.loadingMessage = null
  66. }
  67. },
  68. // Convert a simple API extracts object to get more informations
  69. getExtractFullObject(extractsApiObj) {
  70. return extractsApiObj.extracts.map((url, i) => ({
  71. link: url,
  72. quality: extractsApiObj.info.image.quality,
  73. zone: i + 1,
  74. index: i,
  75. nextQuality: findNearestUpper(extractsApiObj.info.image.quality, this.qualities),
  76. precQuality: findNearestLower(extractsApiObj.info.image.quality, this.qualities),
  77. loading: false
  78. }))
  79. },
  80. // Config was updated, load extracts and save progression
  81. async setExtractConfig(config, configuratorRef) {
  82. if (!config) return
  83. this.loadingMessage = 'Loading configuration extracts...'
  84. this.loadingErrorMessage = null
  85. try {
  86. this.extractConfig.x = config.x
  87. this.extractConfig.y = config.y
  88. this.extractConfig.quality = config.quality
  89. const data = await this.getExtracts(config.quality || undefined)
  90. this.extractsInfos = data.info
  91. // Put extracts in cache if not already there
  92. if (this.extracts.length === 0) this.extracts = this.getExtractFullObject(data)
  93. // If there is a configurator, retract it
  94. if (configuratorRef) configuratorRef.setVisibility(false)
  95. }
  96. catch (err) {
  97. console.error('Failed to load new configuration', err)
  98. this.loadingErrorMessage = 'Failed to load new configuration. ' + err.message
  99. }
  100. finally {
  101. this.loadingMessage = null
  102. this.saveProgress()
  103. }
  104. },
  105. // An action was triggered, load extracts and save progression
  106. async extractAction(event, extractObj) {
  107. const { index, nextQuality, precQuality, quality } = extractObj
  108. const qualityIndex = this.qualities.indexOf(quality)
  109. let action, newQuality
  110. console.log(qualityIndex)
  111. if (event.button === 0) action = 'needMore' // Left click
  112. if (event.button === 2) action = 'needLess' // Right click
  113. if (event.button === 0 && event.ctrlKey) action = 'need10More' // ctrl + Right click
  114. if (event.button === 2 && event.ctrlKey) action = 'need10Less' // ctrl + Left click
  115. if (action === 'needLess') newQuality = precQuality
  116. if (action === 'needMore') newQuality = nextQuality
  117. if (action === 'need10More') {
  118. if (qualityIndex + 10 >= this.qualities.length - 1)
  119. newQuality = this.qualities[this.qualities.length - 1]
  120. else
  121. newQuality = this.qualities[qualityIndex + 10]
  122. }
  123. if (action === 'need10Less') {
  124. if (qualityIndex - 10 <= 0)
  125. newQuality = this.qualities[0]
  126. else
  127. newQuality = this.qualities[qualityIndex - 10]
  128. }
  129. // Do not load a new extract if same quality
  130. if (newQuality === quality) {
  131. // display alert once limit is reached
  132. if (action === 'needLess' || action === 'need10Less') {
  133. this.dialogLess = true
  134. }
  135. if (action === 'needMore' || action === 'need10More') {
  136. this.dialogMore = true
  137. }
  138. return
  139. }
  140. // Set loading state
  141. this.extracts[index].loading = true
  142. try {
  143. const collectedData = this.getClickDataObject(event, extractObj, action)
  144. this.sendMessage({ msgId: experimentMsgId.DATA, msg: collectedData })
  145. // Loading new extract
  146. const data = await this.getExtracts(newQuality)
  147. this.extracts[index].link = data.extracts[index]
  148. this.extracts[index].quality = data.info.image.quality
  149. this.extracts[index].nextQuality = findNearestUpper(data.info.image.quality, this.qualities)
  150. this.extracts[index].precQuality = findNearestLower(data.info.image.quality, this.qualities)
  151. this.extracts[index].loading = false
  152. }
  153. catch (err) {
  154. // TODO: toast message if fail
  155. console.error('Failed to load extract', err)
  156. }
  157. finally {
  158. this.extracts[index].loading = false
  159. this.saveProgress()
  160. }
  161. },
  162. getClickDataObject(event, extractObj, action) {
  163. const { index } = extractObj
  164. const clientSideData = {
  165. extractSize: {
  166. width: event.target.clientWidth,
  167. height: event.target.clientHeight
  168. },
  169. imageSize: {
  170. width: event.target.clientWidth * this.extractConfig.x,
  171. height: event.target.clientHeight * this.extractConfig.y
  172. },
  173. clickPosition: {
  174. extract: {
  175. x: event.offsetX,
  176. y: event.offsetY
  177. },
  178. image: {
  179. x: event.offsetX + (this.extracts[index].index % this.extractConfig.x) * event.target.clientWidth,
  180. y: event.offsetY + (Math.floor(this.extracts[index].index / this.extractConfig.x)) * event.target.clientHeight
  181. }
  182. }
  183. }
  184. const calculatedRealData = {}
  185. calculatedRealData.extractSize = {
  186. width: this.extractsInfos.extractsSize.width,
  187. height: this.extractsInfos.extractsSize.height
  188. }
  189. calculatedRealData.imageSize = {
  190. width: this.extractsInfos.image.metadata.width,
  191. height: this.extractsInfos.image.metadata.height
  192. }
  193. calculatedRealData.clickPosition = {
  194. extract: {
  195. x: Math.floor((calculatedRealData.imageSize.width * clientSideData.clickPosition.extract.x) / clientSideData.imageSize.width),
  196. y: Math.floor((calculatedRealData.imageSize.height * clientSideData.clickPosition.extract.y) / clientSideData.imageSize.height)
  197. },
  198. image: {
  199. x: Math.floor((calculatedRealData.imageSize.width * clientSideData.clickPosition.image.x) / clientSideData.imageSize.width),
  200. y: Math.floor((calculatedRealData.imageSize.height * clientSideData.clickPosition.image.y) / clientSideData.imageSize.height)
  201. }
  202. }
  203. // Sending event to WebSocket server
  204. const loggedObj = {
  205. experimentName: this.experimentName,
  206. sceneName: this.sceneName,
  207. extractConfig: this.extractConfig,
  208. clickedExtract: {
  209. link: this.extracts[index].link,
  210. quality: this.extracts[index].quality,
  211. nextQuality: this.extracts[index].nextQuality,
  212. precQuality: this.extracts[index].precQuality,
  213. zone: this.extracts[index].zone,
  214. index: this.extracts[index].index
  215. },
  216. action,
  217. clientSideData,
  218. calculatedRealData
  219. }
  220. return loggedObj
  221. },
  222. // Finish an experiment, sending full data to the server
  223. // Don't forget to surcharge this function when using this mixin to add more data
  224. finishExperiment() {
  225. const obj = {
  226. experimentName: this.experimentName,
  227. sceneName: this.sceneName,
  228. extractConfig: this.extractConfig,
  229. extracts: this.extracts.map(x => ({
  230. index: x.index,
  231. link: x.link,
  232. nextQuality: x.nextQuality,
  233. precQuality: x.precQuality,
  234. quality: x.quality,
  235. zone: x.zone
  236. })),
  237. qualities: this.qualities,
  238. referenceImage: this.referenceImage,
  239. comment: this.comment
  240. }
  241. this.sendMessage({ msgId: experimentMsgId.VALIDATED, msg: obj })
  242. this.setExperimentFinished()
  243. this.$router.push(`/experiments/${this.experimentName}/${this.sceneName}/validated`)
  244. }
  245. }
  246. }
  247. </script>
  248. <style>
  249. /* White border when hovering on extracts */
  250. .extract-hover-border:hover {
  251. z-index: 1;
  252. outline: 2px #f4f4f4 solid;
  253. }
  254. .img-extract-loader {
  255. height: 100%;
  256. width: 0px;
  257. display: flex;
  258. justify-content: center;
  259. align-items: center;
  260. }
  261. </style>