Edition.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. <template>
  2. <MainContentLayout id="edition" :reftoscrollto="reftoscrollto" @onCenterScrolled="onCenterScrolled">
  3. <template v-if="!content" #header>
  4. <span>Loading ...</span>
  5. </template>
  6. <template #header>
  7. <h1>{{ title }}</h1>
  8. <p v-if="meta">{{ meta.author }}</p>
  9. <aside
  10. v-if="indexitem"
  11. class="index-tooltip"
  12. :style="{ top:tooltip_top + 'px' }"
  13. >
  14. <span v-if="indexitem == 'loading'">Loading ...</span>
  15. <template v-if="indexitem !== 'loading'">
  16. <h1 v-html="indexitem.title" />
  17. <p v-if="indexitem.birthDate" class="birthdeath">
  18. <time>{{ indexitem.birthDate }}</time>, <span class="place">{{ indexitem.birthPlace }}</span><br>
  19. <time>{{ indexitem.deathDate }}</time>, <span class="place">{{ indexitem.deathPlace }}</span>
  20. </p>
  21. <p v-if="indexitem.occupation" class="occupation">
  22. {{ indexitem.occupation }}
  23. </p>
  24. <p v-if="indexitem.type" class="type">
  25. {{ indexitem.type }}
  26. </p>
  27. </template>
  28. </aside>
  29. </template>
  30. <!-- default slot -->
  31. <div id="text">
  32. <template v-if="texts.length">
  33. <infinite-loading
  34. v-if="center_scrolled"
  35. :identifier="textid"
  36. direction="top"
  37. :distance="inifinite_load_distance"
  38. @infinite="prevText"
  39. />
  40. <EdText
  41. v-for="text in texts"
  42. :ref="text.content.uuid"
  43. :key="text.content.uuid"
  44. :tei="text.content.tei"
  45. :uuid="text.content.uuid"
  46. :textid="textid"
  47. @onHoverLink="onHoverLink"
  48. @onLeaveLink="onLeaveLink"
  49. @onNewPageBreaks="onNewPageBreaks"
  50. />
  51. <infinite-loading
  52. :identifier="textid"
  53. @infinite="nextText"
  54. />
  55. </template>
  56. </div>
  57. <template #nav>
  58. <EdToc id="toc" :toc="toc" :loadedtextsuuids="textsuuids" @onClickTocItem="onClickTocItem" />
  59. <div v-if="pages.length" id="page-nav">
  60. <!-- <select class="" name="page-nav" v-model="page_selected" placeholder="Pages">
  61. <option value="" disabled selected>Pages</option>
  62. <option v-for="(page, index) in pages" :key="index" :value="page">{{ page }}</option>
  63. </select> -->
  64. <v-select
  65. id="page-nav-select"
  66. type="select"
  67. placeholder="Aller à la page ..."
  68. append-to-body
  69. :calculate-position="dropDownMenuPos"
  70. :options="pages"
  71. :clearable="false"
  72. :value="page_selected"
  73. @input="onPageSelected"
  74. />
  75. </div>
  76. </template>
  77. </MainContentLayout>
  78. </template>
  79. <script>
  80. import { createPopper } from '@popperjs/core'
  81. import { REST } from 'api/rest-axios'
  82. import { mapState, mapActions } from 'vuex'
  83. import MainContentLayout from '../components/Layouts/MainContentLayout'
  84. import EdText from '../components/Content/EdText'
  85. import EdToc from '../components/Content/EdToc'
  86. export default {
  87. name: 'Edition',
  88. metaInfo () {
  89. // console.log('metainfo', this.meta)
  90. return {
  91. title: this.metainfotitle
  92. }
  93. },
  94. components: {
  95. MainContentLayout,
  96. EdText,
  97. EdToc
  98. },
  99. data: () => ({
  100. toc: null,
  101. meta: null,
  102. editionid: null,
  103. textid: null,
  104. texts: [],
  105. textsuuids: [],
  106. metainfotitle: undefined,
  107. title: undefined,
  108. author: undefined,
  109. texttitle: undefined,
  110. //
  111. indexitem: null,
  112. tooltip_top: null,
  113. //
  114. next_loaded: false,
  115. center_scrolled: false,
  116. inifinite_load_distance: 10,
  117. reftoscrollto: null,
  118. //
  119. pages: [],
  120. pagesOptions: [],
  121. page_selected: ''
  122. }),
  123. computed: {
  124. ...mapState({
  125. editionslist: state => state.Corpus.editionslist,
  126. editionsbyuuid: state => state.Corpus.editionsbyuuid
  127. })
  128. },
  129. watch: {
  130. textid: function (newid, oldid) {
  131. console.log('textid watcher', this, oldid, newid)
  132. this.texts = []
  133. this.textsuuids = []
  134. this.pages = []
  135. this.pagesOtpions = []
  136. this.getTextContent(newid)
  137. },
  138. textdata: function (newtxtdata, oldtxtdata) {
  139. console.log('textdata watcher', oldtxtdata, newtxtdata)
  140. this.metainfotitle = `${this.title} ${newtxtdata.meta.title}`
  141. },
  142. page_selected: function (newp, oldp) {
  143. console.log('page_selected watcher', oldp, newp)
  144. this.scrollToPage(newp)
  145. }
  146. },
  147. beforeCreate () {
  148. console.log('texts this.$route', this.$route)
  149. // http://localhost:8984/texts/gdpSauval1724/toc
  150. // get the edition's toc
  151. REST.get(`${window.apipath}/texts/` + this.$route.params.id + `/toc`, {})
  152. .then(({ data }) => {
  153. console.log('texts/toc REST: data', data)
  154. this.meta = data.meta
  155. this.author = data.meta.author
  156. if (data.content && data.content !== 'vide') {
  157. if (Array.isArray(data.content)) {
  158. this.toc = data.content
  159. } else {
  160. this.toc = [data.content]
  161. }
  162. // if front page get the first item in toc
  163. if (!this.$route.params.textid) {
  164. // console.log('toc', this.toc)
  165. this.textid = this.toc[0].children[1].uuid
  166. }
  167. } else {
  168. console.warn('Bad edition uuid', this.$route.params.id, this.$route)
  169. this.$router.replace({
  170. name: 'notfound',
  171. query: { fullpath: this.$route.path }
  172. })
  173. }
  174. })
  175. .catch((error) => {
  176. console.warn('Issue with text toc', error)
  177. Promise.reject(error)
  178. })
  179. },
  180. created () {
  181. // console.log('Edition this.$route.params.id', this.$route.params.id)
  182. this.editionid = this.$route.params.id
  183. // load editions list from Corpus Store if not exist
  184. if (!this.editionslist.length) {
  185. this.getCorpuses()
  186. // subsribe to store to get the editionbyuuid list
  187. // https://dev.to/viniciuskneves/watch-for-vuex-state-changes-2mgj
  188. this.unsubscribe = this.$store.subscribe((mutation, state) => {
  189. // console.log('Edition store subscribe', mutation.type)
  190. if (mutation.type === 'Corpus/setEditionsByUUID') {
  191. console.log('Edition state.Coprus.editionsbyuuid', this.editionid, state.Corpus.editionsbyuuid)
  192. // this.title = 'HoHoHo'
  193. this.title = this.metainfotitle = state.Corpus.editionsbyuuid[this.editionid].title
  194. }
  195. })
  196. } else {
  197. this.title = this.metainfotitle = this.editionsbyuuid[this.editionid].title
  198. }
  199. // get the text if textid available
  200. if (this.$route.params.textid) {
  201. this.textid = this.$route.params.textid
  202. }
  203. },
  204. beforeRouteUpdate (to, from, next) {
  205. // called when the route that renders this component has changed,
  206. // but this component is reused in the new route.
  207. // For example, for a route with dynamic params `/foo/:id`, when we
  208. // navigate between `/foo/1` and `/foo/2`, the same `Foo` component instance
  209. // will be reused, and this hook will be called when that happens.
  210. // has access to `this` component instance.
  211. // console.log('beforeRouteUpdate to', to)
  212. if (to.params.textid) {
  213. this.textid = to.params.textid
  214. }
  215. next()
  216. },
  217. methods: {
  218. ...mapActions({
  219. getCorpuses: 'Corpus/getCorpuses'
  220. }),
  221. getTextContent (textid, $state = null, direction = 'next') {
  222. console.log('getTextContent', textid)
  223. REST.get(`${window.apipath}/items/${textid}`, {})
  224. .then(({ data }) => {
  225. console.log('text REST: data', data)
  226. if (direction === 'next') {
  227. this.texts.push(data)
  228. this.textsuuids.push(data.content.uuid)
  229. } else {
  230. this.texts.unshift(data)
  231. this.textsuuids.unshift(data.content.uuid)
  232. }
  233. if ($state) {
  234. $state.loaded()
  235. this.next_loaded = true
  236. }
  237. })
  238. .catch((error) => {
  239. console.warn('Issue with getTextContent', error)
  240. console.warn('Bad edition uuid', this.$route.params.id, this.$route)
  241. Promise.reject(error)
  242. this.$router.replace({
  243. name: 'notfound',
  244. query: { fullpath: this.$route.path }
  245. })
  246. })
  247. },
  248. onCenterScrolled (e) {
  249. // console.log('Edition centerScrolled(e)', e.target.scrollTop)
  250. if (!this.center_scrolled && e.target.scrollTop > this.inifinite_load_distance * 1.5) {
  251. this.center_scrolled = true
  252. }
  253. this.indexitem = null
  254. },
  255. nextText ($state) {
  256. console.log('infinite loading nextText()', this.texts[this.texts.length - 1].content.itemAfterUuid, $state)
  257. if (this.texts[this.texts.length - 1].content.itemAfterUuid) {
  258. this.getTextContent(this.texts[this.texts.length - 1].content.itemAfterUuid, $state, 'next')
  259. } else {
  260. $state.complete()
  261. }
  262. },
  263. prevText ($state) {
  264. console.log('infinite loading prevText()', this.texts[0].content.itemBeforeUuid, $state)
  265. if (this.texts[0].content.itemBeforeUuid) {
  266. this.getTextContent(this.texts[0].content.itemBeforeUuid, $state, 'prev')
  267. } else {
  268. $state.complete()
  269. }
  270. },
  271. onHoverLink (elmt) {
  272. console.log('Edition onHoverLink(elmt)', elmt)
  273. this.tooltip_top = elmt.rect.top
  274. this.getIndexItem(elmt)
  275. },
  276. onLeaveLink () {
  277. console.log('Edition onLeaveLink()')
  278. this.indexitem = null
  279. },
  280. getIndexItem (item) {
  281. this.indexitem = 'loading'
  282. REST.get(`${window.apipath}/index${item.index.charAt(0).toUpperCase()}${item.index.slice(1)}/${item.uuid}`, {})
  283. .then(({ data }) => {
  284. console.log('index tooltip REST: data', data)
  285. if (this.indexitem === 'loading') {
  286. this.indexitem = data.content
  287. }
  288. })
  289. .catch((error) => {
  290. console.warn('Issue with index tooltip rest', error)
  291. Promise.reject(error)
  292. this.indexitem = null
  293. })
  294. },
  295. onClickTocItem (uuid) {
  296. console.log('Edition onClickTocItem', uuid, this.$refs)
  297. if (this.textsuuids.indexOf(uuid) !== -1) {
  298. // if already loaded, scroll to uuid
  299. this.reftoscrollto = `.tei[data-uuid="${uuid}"]`
  300. } else {
  301. // if not already loaded, change route
  302. this.$router.push({
  303. name: `editiontext`,
  304. params: {
  305. id: this.editionid,
  306. textid: uuid
  307. }
  308. })
  309. }
  310. },
  311. onNewPageBreaks (p) {
  312. // console.log('onNewPageBreaks', p)
  313. for (var i = 0; i < p.length; i++) {
  314. if (this.pages.indexOf(p[i]) === -1) {
  315. this.pages.push(p[i])
  316. }
  317. }
  318. // reorder array
  319. this.pages.sort((a, b) => a - b)
  320. // this.pagesOptions = []
  321. // for (var j = 0; j < this.pages.length; j++) {
  322. // this.pagesOptions.push({ code: this.pages[j], label: `page ${this.pages[j]}` })
  323. // }
  324. },
  325. dropDownMenuPos (dropdownList, component, { width }) {
  326. /**
  327. * We need to explicitly define the dropdown width since
  328. * it is usually inherited from the parent with CSS.
  329. */
  330. dropdownList.style.width = width
  331. /**
  332. * Here we position the dropdownList relative to the $refs.toggle Element.
  333. *
  334. * The 'offset' modifier aligns the dropdown so that the $refs.toggle and
  335. * the dropdownList overlap by 1 pixel.
  336. *
  337. * The 'toggleClass' modifier adds a 'drop-up' class to the Vue Select
  338. * wrapper so that we can set some styles for when the dropdown is placed
  339. * above.
  340. */
  341. const popper = createPopper(component.$refs.toggle, dropdownList, {
  342. placement: 'top',
  343. modifiers: [
  344. {
  345. name: 'offset',
  346. options: {
  347. offset: [0, -1]
  348. }
  349. },
  350. {
  351. name: 'toggleClass',
  352. enabled: true,
  353. phase: 'write',
  354. fn ({ state }) {
  355. component.$el.classList.toggle('drop-up', state.placement === 'top')
  356. }
  357. }]
  358. })
  359. /**
  360. * To prevent memory leaks Popper needs to be destroyed.
  361. * If you return function, it will be called just before dropdown is removed from DOM.
  362. */
  363. return () => popper.destroy()
  364. },
  365. onPageSelected (e) {
  366. console.log('onPageSelected', e)
  367. this.page_selected = e
  368. this.scrollToPage(e)
  369. // this.scrollToPage(e.code)
  370. },
  371. scrollToPage (p) {
  372. // console.log('scrollToPage', p)
  373. this.reftoscrollto = `span[role="pageBreak"][data-num="${p}"]`
  374. }
  375. }
  376. }
  377. </script>
  378. <style lang="scss" scoped>
  379. </style>