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.

235 lines
5.3 KiB

  1. <template>
  2. <div>
  3. <v-annotator
  4. :dark="$vuetify.theme.dark"
  5. :rtl="rtl"
  6. :text="text"
  7. :entities="JSON.stringify(entities)"
  8. :entity-labels="entityLabels"
  9. :relations="relations"
  10. :relation-labels="relationLabels"
  11. :allow-overlapping="allowOverlapping"
  12. :grapheme-mode="graphemeMode"
  13. @add:entity="handleAddEvent"
  14. @click:entity="handleEntityClickEvent"
  15. @click:relation="updateRelation"
  16. @contextmenu:entity="deleteEntity"
  17. @contextmenu:relation="deleteRelation"
  18. />
  19. <v-menu
  20. v-model="showMenu"
  21. :position-x="x"
  22. :position-y="y"
  23. absolute
  24. offset-y
  25. @input="cleanUp"
  26. >
  27. <v-list
  28. dense
  29. min-width="150"
  30. max-height="400"
  31. class="overflow-y-auto"
  32. >
  33. <v-list-item>
  34. <v-autocomplete
  35. ref="autocomplete"
  36. :value="currentLabel"
  37. :items="entityLabels"
  38. autofocus
  39. dense
  40. deletable-chips
  41. hide-details
  42. item-text="text"
  43. item-value="id"
  44. label="Label List"
  45. small-chips
  46. @input="addOrUpdateEntity"
  47. />
  48. </v-list-item>
  49. <v-list-item
  50. v-for="(label, i) in entityLabels"
  51. :key="i"
  52. v-shortkey="[label.suffixKey]"
  53. @shortkey="addOrUpdateEntity(label.id)"
  54. @click="addOrUpdateEntity(label.id)"
  55. >
  56. <v-list-item-action
  57. v-if="hasAnySuffixKey"
  58. >
  59. <v-chip
  60. v-if="label.suffixKey"
  61. :color="label.backgroundColor"
  62. outlined
  63. small
  64. v-text="label.suffixKey"
  65. />
  66. <span v-else class="mr-8" />
  67. </v-list-item-action>
  68. <v-list-item-content>
  69. <v-list-item-title v-text="label.text"/>
  70. </v-list-item-content>
  71. </v-list-item>
  72. </v-list>
  73. </v-menu>
  74. </div>
  75. </template>
  76. <script lang="ts">
  77. import Vue from 'vue'
  78. import VAnnotator from 'v-annotator'
  79. import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
  80. export default Vue.extend({
  81. components: {
  82. VAnnotator,
  83. },
  84. props: {
  85. dark: {
  86. type: Boolean,
  87. default: false,
  88. },
  89. rtl: {
  90. type: Boolean,
  91. default: false,
  92. },
  93. text: {
  94. type: String,
  95. default: "",
  96. required: true,
  97. },
  98. entities: {
  99. type: Array,
  100. default: () => [],
  101. required: true,
  102. },
  103. entityLabels: {
  104. type: Array,
  105. default: () => [],
  106. required: true,
  107. },
  108. relations: {
  109. type: Array,
  110. default: () => [],
  111. },
  112. relationLabels: {
  113. type: Array,
  114. default: () => [],
  115. },
  116. allowOverlapping: {
  117. type: Boolean,
  118. default: false,
  119. required: false,
  120. },
  121. graphemeMode: {
  122. type: Boolean,
  123. default: false,
  124. },
  125. },
  126. data() {
  127. return {
  128. showMenu: false,
  129. x: 0,
  130. y: 0,
  131. startOffset: 0,
  132. endOffset: 0,
  133. entity: null as any,
  134. };
  135. },
  136. computed: {
  137. hasAnySuffixKey(): boolean {
  138. return this.entityLabels.some((label: any) => label.suffixKey !== null)
  139. },
  140. currentLabel(): any {
  141. if (this.entity) {
  142. const label = this.entityLabels.find((label: any) => label.id === this.entity!.label)
  143. return label
  144. } else {
  145. return null
  146. }
  147. }
  148. },
  149. methods: {
  150. setOffset(startOffset: number, endOffset: number) {
  151. this.startOffset = startOffset
  152. this.endOffset = endOffset
  153. },
  154. setEntity(entityId: number) {
  155. this.entity = this.entities.find((entity: any) => entity.id === entityId)
  156. },
  157. showEntityLabelMenu(e: any) {
  158. e.preventDefault()
  159. this.showMenu = false
  160. this.x = e.clientX || e.changedTouches[0].clientX
  161. this.y = e.clientY || e.changedTouches[0].clientY
  162. this.$nextTick(() => {
  163. this.showMenu = true
  164. })
  165. },
  166. handleAddEvent(e: any, startOffset: number, endOffset: number) {
  167. this.setOffset(startOffset, endOffset)
  168. this.showEntityLabelMenu(e)
  169. },
  170. handleEntityClickEvent(e: any, entityId: number) {
  171. this.setEntity(entityId)
  172. this.showEntityLabelMenu(e)
  173. },
  174. addOrUpdateEntity(labelId: number) {
  175. if (labelId) {
  176. if (this.entity) {
  177. this.updateEntity(labelId)
  178. } else {
  179. this.addEntity(labelId)
  180. }
  181. } else {
  182. this.deleteEntity(this.entity)
  183. }
  184. this.cleanUp()
  185. },
  186. addEntity(labelId: number) {
  187. this.$emit('addEntity', this.startOffset, this.endOffset, labelId)
  188. },
  189. updateEntity(labelId: number) {
  190. this.$emit('click:entity', this.entity!.id, labelId)
  191. },
  192. deleteEntity(entity: any) {
  193. this.$emit('contextmenu:entity', entity.id)
  194. this.cleanUp()
  195. },
  196. cleanUp() {
  197. this.showMenu = false
  198. this.entity = null
  199. this.startOffset = 0
  200. this.endOffset = 0
  201. // Todo: a bit hacky. I want to fix this problem.
  202. // https://github.com/vuetifyjs/vuetify/issues/10765
  203. this.$nextTick(() => {
  204. if (this.$refs.autocomplete) {
  205. (this.$refs.autocomplete as any).selectedItems = []
  206. }
  207. })
  208. },
  209. updateRelation() {
  210. console.log("updateRelation")
  211. },
  212. deleteRelation(relation: any) {
  213. this.$emit('contextmenu:relation', relation.id)
  214. }
  215. },
  216. });
  217. </script>