mirror of https://github.com/doccano/doccano.git
Hironsan
2 years ago
2 changed files with 0 additions and 859 deletions
Split View
Diff Options
-
358frontend/components/tasks/sequenceLabeling/EntityItem.vue
-
501frontend/components/tasks/sequenceLabeling/EntityItemBox.vue
@ -1,358 +0,0 @@ |
|||
<template> |
|||
<v-menu |
|||
v-if="label && !activeMenu" |
|||
v-model="showMenu" |
|||
offset-y |
|||
> |
|||
<template #activator="{ on }"> |
|||
<span :id="'spn-' + spanid" :style="{ borderColor: color }" class="highlight bottom" v-on="on"> |
|||
<span class="highlight__content">{{ content }}<v-icon class="delete" @click.stop="remove">mdi-close-circle</v-icon><span |
|||
v-if="!showMenu && sourceChunk.none" class="choose-link-type" @click.stop="showActiveLinks"></span><span |
|||
v-if="!showMenu && sourceChunk.id === spanid" class="active-link-source" @click.stop="abortNewLink"></span><span |
|||
v-if="sourceLinkType.id > -1 && sourceChunk.id && sourceChunk.id !== spanid" class="choose-target" |
|||
@click.stop="selectTarget"></span></span><span |
|||
:data-label="label" :style="{ backgroundColor: color, color: textColor }" class="highlight__label"/> |
|||
</span> |
|||
</template> |
|||
|
|||
<v-list |
|||
dense |
|||
min-width="150" |
|||
max-height="400" |
|||
class="overflow-y-auto" |
|||
> |
|||
<v-list-item |
|||
v-for="(item, i) in labels" |
|||
:key="i" |
|||
v-shortkey.once="[item.suffixKey]" |
|||
@shortkey="update(item)" |
|||
@click="update(item)" |
|||
> |
|||
<v-list-item-content> |
|||
<v-list-item-title v-text="item.text"/> |
|||
</v-list-item-content> |
|||
<v-list-item-action> |
|||
<v-list-item-action-text v-text="item.suffixKey"/> |
|||
</v-list-item-action> |
|||
</v-list-item> |
|||
</v-list> |
|||
</v-menu> |
|||
|
|||
<v-menu |
|||
v-else-if="label && activeMenu==='active-links'" |
|||
v-model="showActiveLinksMenu" |
|||
offset-y |
|||
> |
|||
<template #activator="{ on }"> |
|||
<span :id="'spn-' + spanid" :style="{ borderColor: color }" class="highlight bottom" v-on="on"> |
|||
<span class="highlight__content">{{ content }}<v-icon class="delete" @click.stop="remove">mdi-close-circle</v-icon><span |
|||
class="active-link-source" @click.stop="abortNewLink"></span></span><span |
|||
:data-label="label" :style="{ backgroundColor: color, color: textColor }" class="highlight__label"/> |
|||
</span> |
|||
</template> |
|||
|
|||
<v-list |
|||
dense |
|||
min-width="150" |
|||
max-height="400" |
|||
class="overflow-y-auto" |
|||
> |
|||
<v-list-item @click.stop="showNewLinkTypes"> |
|||
<v-list-item-content> |
|||
<v-list-item-title>new relation...</v-list-item-title> |
|||
</v-list-item-content> |
|||
</v-list-item> |
|||
|
|||
<v-list-item |
|||
v-for="(link, i) in sourceChunk.links" |
|||
:key="i" |
|||
> |
|||
<v-list-item-content> |
|||
<v-list-item-subtitle v-text="link.targetLabel"></v-list-item-subtitle> |
|||
</v-list-item-content> |
|||
|
|||
<v-list-item-action> |
|||
<v-btn icon @click.stop="deleteLink(link, i)"> |
|||
<v-icon color="grey lighten-1">mdi-delete</v-icon> |
|||
</v-btn> |
|||
</v-list-item-action> |
|||
</v-list-item> |
|||
</v-list> |
|||
</v-menu> |
|||
|
|||
<v-menu |
|||
v-else-if="label && activeMenu==='new-link'" |
|||
v-model="showNewLinkMenu" |
|||
offset-y |
|||
> |
|||
<template #activator="{ on }"> |
|||
<span :id="'spn-' + spanid" :style="{ borderColor: color }" class="highlight bottom" v-on="on"> |
|||
<span class="highlight__content">{{ content }}<v-icon class="delete" @click.stop="remove">mdi-close-circle</v-icon><span |
|||
class="active-link-source" @click.stop="abortNewLink"></span></span><span |
|||
:data-label="label" :style="{ backgroundColor: color, color: textColor }" class="highlight__label"/> |
|||
</span> |
|||
</template> |
|||
|
|||
<v-list |
|||
dense |
|||
min-width="150" |
|||
max-height="400" |
|||
class="overflow-y-auto" |
|||
> |
|||
<v-list-item> |
|||
<v-list-item-content> |
|||
<v-list-item-title>choose relation type:</v-list-item-title> |
|||
</v-list-item-content> |
|||
</v-list-item> |
|||
|
|||
<v-list-item |
|||
v-for="(type, i) in linkTypes" |
|||
:key="i" |
|||
@click="selectNewLinkType(type)" |
|||
> |
|||
<v-list-item-action> |
|||
<v-list-item-action-text v-text="type.name"/> |
|||
</v-list-item-action> |
|||
</v-list-item> |
|||
</v-list> |
|||
</v-menu> |
|||
|
|||
<span v-else :class="[newline ? 'newline' : '']">{{ content }}</span> |
|||
</template> |
|||
|
|||
<script> |
|||
import {idealColor} from '~/plugins/utils.js' |
|||
|
|||
export default { |
|||
props: { |
|||
spanid: { |
|||
type: Number, |
|||
default: 0, |
|||
required: true |
|||
}, |
|||
content: { |
|||
type: String, |
|||
default: '', |
|||
required: true |
|||
}, |
|||
label: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
color: { |
|||
type: String, |
|||
default: '#64FFDA' |
|||
}, |
|||
labels: { |
|||
type: Array, |
|||
default: () => [], |
|||
required: true |
|||
}, |
|||
linkTypes: { |
|||
type: Array, |
|||
default: () => [], |
|||
required: true |
|||
}, |
|||
newline: { |
|||
type: Boolean |
|||
}, |
|||
sourceChunk: { |
|||
type: Object, |
|||
default: () => { |
|||
} |
|||
}, |
|||
sourceLinkType: { |
|||
type: Object, |
|||
default: () => { |
|||
}, |
|||
required: true |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
showMenu: false, |
|||
showActiveLinksMenu: false, |
|||
showNewLinkMenu: false, |
|||
showChangeLinkMenu: false, |
|||
activeMenu: false |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
textColor() { |
|||
return idealColor(this.color) |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
update(label) { |
|||
this.$emit('update', label) |
|||
this.closeAllMenus(); |
|||
}, |
|||
|
|||
remove() { |
|||
this.$emit('remove') |
|||
}, |
|||
|
|||
closeAllMenus() { |
|||
this.showMenu = false; |
|||
this.showActiveLinksMenu = false; |
|||
this.showNewLinkMenu = false; |
|||
this.showChangeLinkMenu = false; |
|||
this.activeMenu = false; |
|||
}, |
|||
|
|||
showActiveLinks() { |
|||
this.closeAllMenus(); |
|||
this.showActiveLinksMenu = true; |
|||
this.activeMenu = 'active-links'; |
|||
this.$emit('selectSource'); |
|||
}, |
|||
|
|||
showNewLinkTypes() { |
|||
this.closeAllMenus(); |
|||
this.activeMenu = 'new-link'; |
|||
this.showNewLinkMenu = true; |
|||
}, |
|||
|
|||
deleteLink(link, i) { |
|||
this.$emit('deleteLink', {id: link.id, ndx: i}); |
|||
}, |
|||
|
|||
selectNewLinkType(type) { |
|||
this.closeAllMenus(); |
|||
this.$emit('selectNewLinkType', type); |
|||
}, |
|||
|
|||
changeLinkType(type) { |
|||
this.closeAllMenus(); |
|||
this.$emit('changeLinkType', type); |
|||
}, |
|||
|
|||
selectTarget() { |
|||
this.closeAllMenus(); |
|||
this.$emit('selectTarget'); |
|||
}, |
|||
|
|||
abortNewLink() { |
|||
this.closeAllMenus(); |
|||
this.$emit('hideAllLinkMenus'); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.highlight.blue { |
|||
background: #edf4fa !important; |
|||
} |
|||
|
|||
.highlight.bottom { |
|||
display: block; |
|||
white-space: normal; |
|||
} |
|||
|
|||
.highlight:first-child { |
|||
margin-left: 0; |
|||
} |
|||
|
|||
.highlight { |
|||
border: 2px solid; |
|||
margin: 4px 6px 4px 3px; |
|||
vertical-align: middle; |
|||
box-shadow: 2px 4px 20px rgba(0, 0, 0, .1); |
|||
position: relative; |
|||
cursor: default; |
|||
min-width: 26px; |
|||
line-height: 22px; |
|||
display: flex; |
|||
} |
|||
|
|||
.highlight .delete { |
|||
top: -15px; |
|||
left: -13px; |
|||
position: absolute; |
|||
display: none; |
|||
} |
|||
|
|||
.highlight:hover .delete { |
|||
display: block; |
|||
} |
|||
|
|||
.highlight .choose-link-type:before { |
|||
content: 'R'; |
|||
} |
|||
|
|||
.highlight .active-link-source:before { |
|||
content: 'R'; |
|||
} |
|||
|
|||
.highlight .choose-target:before { |
|||
content: '+'; |
|||
} |
|||
|
|||
.highlight .choose-link-type, |
|||
.highlight .active-link-source, |
|||
.highlight .choose-target { |
|||
display: none; |
|||
position: absolute; |
|||
top: -12px; |
|||
right: -11px; |
|||
width: 20px; |
|||
background: rgba(0, 0, 0, 0.54); |
|||
color: #ffffff; |
|||
border-radius: 30px; |
|||
cursor: pointer; |
|||
text-align: center; |
|||
} |
|||
|
|||
.highlight:hover .choose-link-type, |
|||
.highlight:hover .choose-target { |
|||
display: block; |
|||
} |
|||
|
|||
.highlight .active-link-source { |
|||
display: block; |
|||
background: #00a4cf; |
|||
color: #ffffff; |
|||
} |
|||
|
|||
.highlight__content { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
align-items: center; |
|||
padding: 2px 2px 0 6px; |
|||
} |
|||
|
|||
.highlight.bottom .highlight__content:after { |
|||
content: " "; |
|||
padding-right: 3px; |
|||
} |
|||
|
|||
.highlight__label { |
|||
line-height: 14px; |
|||
align-items: center; |
|||
justify-content: center; |
|||
display: flex; |
|||
padding: 0 8px; |
|||
text-align: center; |
|||
-webkit-user-select: none; |
|||
-moz-user-select: none; |
|||
-ms-user-select: none; |
|||
user-select: none; |
|||
color: white; |
|||
} |
|||
|
|||
.highlight__label::after { |
|||
content: attr(data-label); |
|||
display: block; |
|||
font-size: 14px; |
|||
-webkit-font-smoothing: subpixel-antialiased; |
|||
letter-spacing: .1em; |
|||
} |
|||
|
|||
.newline { |
|||
width: 100%; |
|||
} |
|||
</style> |
@ -1,501 +0,0 @@ |
|||
<template> |
|||
<div id="connections-wrapper"> |
|||
<div class="highlight-container highlight-container--bottom-labels" @click="open" @touchend="open"> |
|||
<entity-item |
|||
v-for="(chunk, i) in chunks" |
|||
:key="i" |
|||
:spanid="chunk.id" |
|||
:content="chunk.text" |
|||
:newline="chunk.newline" |
|||
:label="chunk.label" |
|||
:color="chunk.color" |
|||
:labels="labels" |
|||
:link-types="linkTypes" |
|||
:source-chunk="sourceChunk" |
|||
:source-link-type="sourceLinkType" |
|||
@remove="deleteAnnotation(chunk.id)" |
|||
@update="updateEntity($event.id, chunk.id)" |
|||
@selectSource="selectSource(chunk)" |
|||
@selectTarget="selectTarget(chunk)" |
|||
@deleteLink="deleteLink($event.id, $event.ndx)" |
|||
@selectNewLinkType="selectNewLinkType($event)" |
|||
@hideAllLinkMenus="hideAllLinkMenus()" |
|||
/> |
|||
<v-menu |
|||
v-model="showMenu" |
|||
:position-x="x" |
|||
:position-y="y" |
|||
absolute |
|||
offset-y |
|||
> |
|||
<v-list |
|||
dense |
|||
min-width="150" |
|||
max-height="400" |
|||
class="overflow-y-auto" |
|||
> |
|||
<v-list-item |
|||
v-for="(label, i) in labels" |
|||
:key="i" |
|||
v-shortkey="[label.suffixKey]" |
|||
@shortkey="assignLabel(label.id)" |
|||
@click="assignLabel(label.id)" |
|||
> |
|||
<v-list-item-content> |
|||
<v-list-item-title v-text="label.text"/> |
|||
</v-list-item-content> |
|||
<v-list-item-action> |
|||
<v-list-item-action-text v-text="label.suffixKey"/> |
|||
</v-list-item-action> |
|||
</v-list-item> |
|||
</v-list> |
|||
</v-menu> |
|||
</div> |
|||
|
|||
<canvas id="connections"> |
|||
</canvas> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import EntityItem from './EntityItem' |
|||
|
|||
export default { |
|||
components: { |
|||
EntityItem |
|||
}, |
|||
|
|||
props: { |
|||
text: { |
|||
type: String, |
|||
default: '', |
|||
required: true |
|||
}, |
|||
labels: { |
|||
type: Array, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
linkTypes: { |
|||
type: Array, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
entities: { |
|||
type: Array, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
deleteAnnotation: { |
|||
type: Function, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
updateEntity: { |
|||
type: Function, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
addEntity: { |
|||
type: Function, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
sourceChunk: { |
|||
type: Object, |
|||
default: () => { |
|||
}, |
|||
required: true |
|||
}, |
|||
sourceLinkType: { |
|||
type: Object, |
|||
default: () => { |
|||
}, |
|||
required: true |
|||
}, |
|||
selectSource: { |
|||
type: Function, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
selectTarget: { |
|||
type: Function, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
deleteLink: { |
|||
type: Function, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
selectNewLinkType: { |
|||
type: Function, |
|||
default: () => ([]), |
|||
required: true |
|||
}, |
|||
hideAllLinkMenus: { |
|||
type: Function, |
|||
default: () => ([]), |
|||
required: true |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
showMenu: false, |
|||
x: 0, |
|||
y: 0, |
|||
start: 0, |
|||
end: 0 |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
sortedEntities() { |
|||
return this.entities.slice().sort((a, b) => a.startOffset - b.startOffset) |
|||
}, |
|||
|
|||
chunks() { |
|||
let chunks = [] |
|||
let startOffset = 0 |
|||
// to count the number of characters correctly. |
|||
const characters = [...this.text] |
|||
for (const entity of this.sortedEntities) { |
|||
// add non-entities to chunks. |
|||
let piece = characters.slice(startOffset, entity.startOffset).join('') |
|||
chunks = chunks.concat(this.makeChunks(piece)) |
|||
startOffset = entity.endOffset |
|||
// add entities to chunks. |
|||
const label = this.labelObject[entity.label] |
|||
piece = characters.slice(entity.startOffset, entity.endOffset).join('') |
|||
chunks.push({ |
|||
id: entity.id, |
|||
label: label.text, |
|||
color: label.backgroundColor, |
|||
text: piece, |
|||
selectedAsLinkSource: false, |
|||
links: entity.links ? entity.links.map(link => { |
|||
return { |
|||
id: link.id, |
|||
type: link.type, |
|||
color: this.getColor(link.type), |
|||
targetId: link.annotation_id_2, |
|||
targetLabel: null // target label can be computed only after all chunks are made, see line 204 |
|||
} |
|||
}) : null |
|||
}) |
|||
} |
|||
// add the rest of text. |
|||
chunks = chunks.concat(this.makeChunks(characters.slice(startOffset, characters.length).join(''))); |
|||
|
|||
// populate the links. Must be done after chunk creation |
|||
chunks.forEach(chunk => { |
|||
if (chunk.links) { |
|||
chunk.links.forEach(link => { |
|||
link.targetLabel = chunks.find(target => target.id === link.targetId).text; |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return chunks; |
|||
}, |
|||
|
|||
labelObject() { |
|||
const obj = {} |
|||
for (const label of this.labels) { |
|||
obj[label.id] = label |
|||
} |
|||
return obj |
|||
} |
|||
}, |
|||
|
|||
updated() { |
|||
this.$nextTick(() => { |
|||
const parentPos = document.getElementById('connections-wrapper').getBoundingClientRect(); |
|||
const canvas = document.getElementById('connections'); |
|||
canvas.width = parentPos.width; |
|||
canvas.height = parentPos.height; |
|||
|
|||
const ctx = canvas.getContext('2d'); |
|||
ctx.clearRect(0, 0, parentPos.width, parentPos.height); |
|||
|
|||
const topPoints = this.drawnCountPoints(this.chunks.length); |
|||
const bottomPoints = this.drawnCountPoints(this.chunks.length); |
|||
|
|||
const chunks = this.chunks; |
|||
|
|||
chunks.forEach(function(sourceChunk, sourceNdx) { |
|||
if (sourceChunk.links) { |
|||
sourceChunk.links.forEach(function(link) { |
|||
let childPos = document.getElementById('spn-' + sourceChunk.id).getBoundingClientRect(); |
|||
const y1 = childPos.y - parentPos.y; |
|||
|
|||
childPos = document.getElementById('spn-' + link.targetId).getBoundingClientRect(); |
|||
const y2 = childPos.y - parentPos.y; |
|||
|
|||
const targetNdx = chunks.findIndex(ch => ch.id === link.targetId); |
|||
|
|||
if (y1 < y2) { |
|||
bottomPoints[sourceNdx].count++; |
|||
topPoints[targetNdx].count++; |
|||
|
|||
} else if (y1 > y2) { |
|||
topPoints[sourceNdx].count++; |
|||
bottomPoints[targetNdx].count++; |
|||
|
|||
} else { |
|||
bottomPoints[sourceNdx].count++; |
|||
bottomPoints[targetNdx].count++; |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
chunks.forEach(function(sourceChunk, sourceNdx) { |
|||
if (sourceChunk.links) { |
|||
sourceChunk.links.forEach(function(link) { |
|||
const sourcePos = document.getElementById('spn-' + sourceChunk.id).getBoundingClientRect(); |
|||
let x1 = sourcePos.x - parentPos.x; |
|||
let y1 = sourcePos.y - parentPos.y; |
|||
|
|||
const targetPos = document.getElementById('spn-' + link.targetId).getBoundingClientRect(); |
|||
let x2 = targetPos.x - parentPos.x; |
|||
let y2 = targetPos.y - parentPos.y; |
|||
|
|||
const targetNdx = chunks.findIndex(ch => ch.id === link.targetId); |
|||
|
|||
ctx.beginPath(); |
|||
ctx.lineWidth = 3; |
|||
ctx.strokeStyle = link.color; |
|||
|
|||
if (y1 < y2) { |
|||
bottomPoints[sourceNdx].drawn++; |
|||
topPoints[targetNdx].drawn++; |
|||
|
|||
x1 += bottomPoints[sourceNdx].drawn * sourcePos.width / (bottomPoints[sourceNdx].count + 1); |
|||
y1 += sourcePos.height; |
|||
|
|||
x2 += topPoints[targetNdx].drawn * targetPos.width / (topPoints[targetNdx].count + 1); |
|||
|
|||
ctx.moveTo(x1, y1); |
|||
ctx.lineTo(x1, y1 + 12); |
|||
ctx.lineTo(x2, y2 - 12); |
|||
ctx.lineTo(x2, y2); |
|||
ctx.stroke(); |
|||
|
|||
ctx.fillStyle = link.color; |
|||
ctx.beginPath(); |
|||
ctx.moveTo(x2, y2); |
|||
ctx.lineTo(x2 - 3, y2 - 5); |
|||
ctx.lineTo(x2 + 3, y2 - 5); |
|||
ctx.lineTo(x2, y2); |
|||
ctx.closePath(); |
|||
ctx.stroke(); |
|||
|
|||
} else if (y1 > y2) { |
|||
topPoints[sourceNdx].drawn++; |
|||
bottomPoints[targetNdx].drawn++; |
|||
|
|||
x1 += topPoints[sourceNdx].drawn * sourcePos.width / (topPoints[sourceNdx].count + 1); |
|||
|
|||
x2 += bottomPoints[targetNdx].drawn * targetPos.width / (bottomPoints[targetNdx].count + 1); |
|||
y2 += targetPos.height; |
|||
|
|||
ctx.moveTo(x1, y1); |
|||
ctx.lineTo(x1, y1 - 12); |
|||
ctx.lineTo(x2, y2 + 12); |
|||
ctx.lineTo(x2, y2); |
|||
ctx.stroke(); |
|||
|
|||
ctx.fillStyle = link.color; |
|||
ctx.beginPath(); |
|||
ctx.moveTo(x2, y2); |
|||
ctx.lineTo(x2 - 3, y2 + 5); |
|||
ctx.lineTo(x2 + 3, y2 + 5); |
|||
ctx.lineTo(x2, y2); |
|||
ctx.closePath(); |
|||
ctx.stroke(); |
|||
|
|||
} else { |
|||
bottomPoints[sourceNdx].drawn++; |
|||
bottomPoints[targetNdx].drawn++; |
|||
|
|||
x1 += bottomPoints[sourceNdx].drawn * sourcePos.width / (bottomPoints[sourceNdx].count + 1); |
|||
y1 += sourcePos.height; |
|||
|
|||
x2 += bottomPoints[targetNdx].drawn * targetPos.width / (bottomPoints[targetNdx].count + 1); |
|||
y2 += targetPos.height; |
|||
|
|||
ctx.moveTo(x1, y1); |
|||
ctx.lineTo(x1, y1 + 12); |
|||
ctx.lineTo(x2, y2 + 12); |
|||
ctx.lineTo(x2, y2); |
|||
ctx.stroke(); |
|||
|
|||
ctx.fillStyle = link.color; |
|||
ctx.beginPath(); |
|||
ctx.moveTo(x2, y2); |
|||
ctx.lineTo(x2 - 3, y2 + 5); |
|||
ctx.lineTo(x2 + 3, y2 + 5); |
|||
ctx.lineTo(x2, y2); |
|||
ctx.closePath(); |
|||
ctx.stroke(); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
}, |
|||
|
|||
methods: { |
|||
makeChunks(text) { |
|||
const chunks = [] |
|||
const snippets = text.split('\n') |
|||
for (const snippet of snippets.slice(0, -1)) { |
|||
chunks.push({ |
|||
label: null, |
|||
color: null, |
|||
text: snippet + '\n', |
|||
newline: false |
|||
}) |
|||
chunks.push({ |
|||
label: null, |
|||
color: null, |
|||
text: '', |
|||
newline: true |
|||
}) |
|||
} |
|||
chunks.push({ |
|||
label: null, |
|||
color: null, |
|||
text: snippets.slice(-1)[0], |
|||
newline: false |
|||
}) |
|||
return chunks |
|||
}, |
|||
|
|||
show(e) { |
|||
e.preventDefault() |
|||
this.showMenu = false |
|||
this.x = e.clientX || e.changedTouches[0].clientX |
|||
this.y = e.clientY || e.changedTouches[0].clientY |
|||
this.$nextTick(() => { |
|||
this.showMenu = true |
|||
}) |
|||
}, |
|||
|
|||
setSpanInfo() { |
|||
let selection |
|||
// Modern browsers. |
|||
if (window.getSelection) { |
|||
selection = window.getSelection() |
|||
} else if (document.selection) { |
|||
selection = document.selection |
|||
} |
|||
// If nothing is selected. |
|||
if (selection.rangeCount <= 0) { |
|||
return |
|||
} |
|||
const range = selection.getRangeAt(0) |
|||
const preSelectionRange = range.cloneRange() |
|||
preSelectionRange.selectNodeContents(this.$el) |
|||
preSelectionRange.setEnd(range.startContainer, range.startOffset) |
|||
this.start = [...preSelectionRange.toString()].length |
|||
this.end = this.start + [...range.toString()].length |
|||
}, |
|||
|
|||
validateSpan() { |
|||
if ((typeof this.start === 'undefined') || (typeof this.end === 'undefined')) { |
|||
return false |
|||
} |
|||
if (this.start === this.end) { |
|||
return false |
|||
} |
|||
for (const entity of this.entities) { |
|||
if ((entity.startOffset <= this.start) && (this.start < entity.endOffset)) { |
|||
return false |
|||
} |
|||
if ((entity.startOffset < this.end) && (this.end <= entity.endOffset)) { |
|||
return false |
|||
} |
|||
if ((this.start < entity.startOffset) && (entity.endOffset < this.end)) { |
|||
return false |
|||
} |
|||
} |
|||
return true |
|||
}, |
|||
|
|||
open(e) { |
|||
this.$emit('hideAllLinkMenus'); |
|||
|
|||
this.setSpanInfo() |
|||
if (this.validateSpan()) { |
|||
this.show(e) |
|||
} |
|||
}, |
|||
|
|||
assignLabel(labelId) { |
|||
if (this.validateSpan()) { |
|||
this.addEntity(this.start, this.end, labelId) |
|||
this.showMenu = false |
|||
this.start = 0 |
|||
this.end = 0 |
|||
} |
|||
}, |
|||
|
|||
getColor(typeId) { |
|||
const type = this.linkTypes.find(type => type.id === typeId); |
|||
if (type) { |
|||
return type.color; |
|||
} |
|||
return "#787878"; |
|||
}, |
|||
|
|||
drawnCountPoints(size) { |
|||
const points = Array(size); |
|||
|
|||
for (let i = 0; i < points.length; i++) { |
|||
points[i] = { |
|||
drawn: 0, |
|||
count: 0 |
|||
} |
|||
} |
|||
|
|||
return points; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.highlight-container.highlight-container--bottom-labels { |
|||
align-items: flex-start; |
|||
} |
|||
|
|||
.highlight-container { |
|||
line-height: 70px !important; |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
white-space: pre-wrap; |
|||
cursor: default; |
|||
position: relative; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.highlight-container.highlight-container--bottom-labels .highlight.bottom { |
|||
margin-top: 6px; |
|||
} |
|||
|
|||
#connections-wrapper { |
|||
position: relative; |
|||
} |
|||
|
|||
#connections-wrapper canvas { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 0; |
|||
} |
|||
</style> |
Write
Preview
Loading…
Cancel
Save