From b6eea920953e7f2bcd600612c9be98448a73a0ff Mon Sep 17 00:00:00 2001 From: mauro Date: Wed, 5 May 2021 17:59:22 +0200 Subject: [PATCH] - avanzamento creazione grafica relazioni - tentativo di creazione pagine di gestione tipi di relazioni --- frontend/components/layout/TheSideBar.vue | 1 + frontend/components/links/ActionMenu.vue | 32 +++ frontend/components/links/FormCreate.vue | 106 +++++++++ frontend/components/links/FormDelete.vue | 28 +++ frontend/components/links/LinksList.vue | 89 ++++++++ .../tasks/sequenceLabeling/EntityItem.vue | 111 ++++++++-- .../tasks/sequenceLabeling/EntityItemBox.vue | 209 ++++++++++-------- frontend/domain/models/links/link.ts | 81 +++++++ .../domain/models/links/linksRepository.ts | 11 + frontend/pages/projects/_id/links/index.vue | 169 ++++++++++++++ .../projects/_id/sequence-labeling/index.vue | 94 +++++--- frontend/plugins/services.ts | 202 +++++++++-------- .../repositories/links/apiLinksRepository.ts | 45 ++++ .../services/application/links/linkData.ts | 19 ++ .../links/linksApplicationService.ts | 30 +++ 15 files changed, 984 insertions(+), 243 deletions(-) create mode 100644 frontend/components/links/ActionMenu.vue create mode 100644 frontend/components/links/FormCreate.vue create mode 100644 frontend/components/links/FormDelete.vue create mode 100644 frontend/components/links/LinksList.vue create mode 100644 frontend/domain/models/links/link.ts create mode 100644 frontend/domain/models/links/linksRepository.ts create mode 100644 frontend/pages/projects/_id/links/index.vue create mode 100644 frontend/repositories/links/apiLinksRepository.ts create mode 100644 frontend/services/application/links/linkData.ts create mode 100644 frontend/services/application/links/linksApplicationService.ts diff --git a/frontend/components/layout/TheSideBar.vue b/frontend/components/layout/TheSideBar.vue index ea034fda..798b6387 100644 --- a/frontend/components/layout/TheSideBar.vue +++ b/frontend/components/layout/TheSideBar.vue @@ -61,6 +61,7 @@ export default { { icon: 'mdi-home', text: this.$t('projectHome.home'), link: '', adminOnly: false }, { icon: 'mdi-database', text: this.$t('dataset.dataset'), link: 'dataset', adminOnly: true }, { icon: 'label', text: this.$t('labels.labels'), link: 'labels', adminOnly: true }, + { icon: 'label', text: 'Relations', link: 'links', adminOnly: true }, { icon: 'person', text: this.$t('members.members'), link: 'members', adminOnly: true }, { icon: 'mdi-comment-account-outline', text: 'Comments', link: 'comments', adminOnly: true }, { icon: 'mdi-book-open-outline', text: this.$t('guideline.guideline'), link: 'guideline', adminOnly: true }, diff --git a/frontend/components/links/ActionMenu.vue b/frontend/components/links/ActionMenu.vue new file mode 100644 index 00000000..f40b9792 --- /dev/null +++ b/frontend/components/links/ActionMenu.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/components/links/FormCreate.vue b/frontend/components/links/FormCreate.vue new file mode 100644 index 00000000..7fc0d82a --- /dev/null +++ b/frontend/components/links/FormCreate.vue @@ -0,0 +1,106 @@ + + + diff --git a/frontend/components/links/FormDelete.vue b/frontend/components/links/FormDelete.vue new file mode 100644 index 00000000..03cbeb12 --- /dev/null +++ b/frontend/components/links/FormDelete.vue @@ -0,0 +1,28 @@ + + + diff --git a/frontend/components/links/LinksList.vue b/frontend/components/links/LinksList.vue new file mode 100644 index 00000000..cd9887a4 --- /dev/null +++ b/frontend/components/links/LinksList.vue @@ -0,0 +1,89 @@ + + + diff --git a/frontend/components/tasks/sequenceLabeling/EntityItem.vue b/frontend/components/tasks/sequenceLabeling/EntityItem.vue index df3fcc83..f0bb26c0 100644 --- a/frontend/components/tasks/sequenceLabeling/EntityItem.vue +++ b/frontend/components/tasks/sequenceLabeling/EntityItem.vue @@ -1,15 +1,17 @@ @@ -69,32 +105,63 @@ export default { newline: { type: Boolean }, - selectedChunkId: { + sourceChunk: { + type: Object, + default: () => { + } + }, + selectedLinkType: { type: Number, - default: -1 + default: -1, + required: true } }, + data() { return { - showMenu: false + showMenu: false, + showLinksMenu: false } }, + computed: { textColor() { return idealColor(this.color) } }, + methods: { update(label) { this.$emit('update', label) this.showMenu = false }, + remove() { this.$emit('remove') }, - onLinkClick() { - this.$emit('selectLinkSource'); + + selectSourceAndShowLinkTypes() { + this.showMenu = true; + this.showLinksMenu = true; + this.$emit('selectSource'); + }, + + selectLinkType(type) { + this.showMenu = false; + this.showLinksMenu = false; + this.$emit('selectLinkType', type); + }, + + selectTarget() { this.showMenu = false; + this.showLinksMenu = false; + this.$emit('selectTarget'); + }, + + abortNewLink() { + this.showMenu = false; + this.showLinksMenu = false; + this.$emit('abortNewLink'); } } } @@ -137,11 +204,22 @@ export default { display: block; } -.highlight .target-selector:before { - content: 'L'; +.highlight .choose-link-type:before { + content: 'R'; +} + +.highlight .active-link-source:before { + content: 'R'; } -.highlight .target-selector { +.highlight .choose-target:before { + content: '+'; +} + +.highlight .choose-link-type, +.highlight .active-link-source, +.highlight .choose-target { + display: none; position: absolute; top: -12px; right: -11px; @@ -153,9 +231,14 @@ export default { text-align: center; } -.highlight .target-selector.active { +.highlight:hover .choose-link-type, +.highlight:hover .choose-target { + display: block; +} + +.highlight .active-link-source { display: block; - background: #008ad6; + background: #00a4cf; color: #ffffff; } diff --git a/frontend/components/tasks/sequenceLabeling/EntityItemBox.vue b/frontend/components/tasks/sequenceLabeling/EntityItemBox.vue index b67c8f37..dbff2cf9 100644 --- a/frontend/components/tasks/sequenceLabeling/EntityItemBox.vue +++ b/frontend/components/tasks/sequenceLabeling/EntityItemBox.vue @@ -1,54 +1,58 @@ + + diff --git a/frontend/pages/projects/_id/sequence-labeling/index.vue b/frontend/pages/projects/_id/sequence-labeling/index.vue index 7332c1b9..28cf096b 100644 --- a/frontend/pages/projects/_id/sequence-labeling/index.vue +++ b/frontend/pages/projects/_id/sequence-labeling/index.vue @@ -2,39 +2,43 @@ - @@ -60,11 +64,11 @@ export default { async fetch() { this.docs = await this.$services.document.fetchOne( - this.projectId, - this.$route.query.page, - this.$route.query.q, - this.$route.query.isChecked, - this.project.filterOption + this.projectId, + this.$route.query.page, + this.$route.query.q, + this.$route.query.isChecked, + this.project.filterOption ) const doc = this.docs.items[0] if (this.enableAutoLabeling) { @@ -80,7 +84,10 @@ export default { labels: [], project: {}, enableAutoLabeling: false, - selectedChunkId: -1 + sourceChunk: { + none: true + }, + selectedLinkType: -1 } }, @@ -116,20 +123,24 @@ export default { methods: { async list(docId) { + this.abortNewLink(); this.annotations = await this.$services.sequenceLabeling.list(this.projectId, docId) }, async remove(id) { + this.abortNewLink(); await this.$services.sequenceLabeling.delete(this.projectId, this.doc.id, id) await this.list(this.doc.id) }, async add(startOffset, endOffset, labelId) { + this.abortNewLink(); await this.$services.sequenceLabeling.create(this.projectId, this.doc.id, labelId, startOffset, endOffset) await this.list(this.doc.id) }, async update(labelId, annotationId) { + this.abortNewLink(); await this.$services.sequenceLabeling.changeLabel(this.projectId, this.doc.id, annotationId, labelId) await this.list(this.doc.id) }, @@ -153,19 +164,34 @@ export default { await this.$fetch() }, - selectLinkSource(chunk) { - console.log(chunk.id); - console.log(this.selectedChunkId); + selectSource(chunk) { + this.sourceChunk = chunk; + }, - if (this.selectedChunkId !== -1) { - console.log('aggiungi link [' + this.selectedChunkId + ', ' + chunk.id + ']'); + selectTarget(chunk) { + // skips links duplicates + if (!chunk.links.find(ch => ch.id === this.sourceChunk.id)) { + this.sourceChunk.links.push({ + id: chunk.id, + type: this.selectedLinkType + }); } + this.abortNewLink(); + }, + + selectLinkType(type) { + this.selectedLinkType = type; + }, - this.selectedChunkId = (this.selectedChunkId === chunk.id) ? -1 : chunk.id; + abortNewLink() { + this.sourceChunk = { + none: true + }; + this.selectedLinkType = -1; } }, - validate({ params, query }) { + validate({params, query}) { return /^\d+$/.test(params.id) && /^\d+$/.test(query.page) } } diff --git a/frontend/plugins/services.ts b/frontend/plugins/services.ts index 411b53e2..cd61745c 100644 --- a/frontend/plugins/services.ts +++ b/frontend/plugins/services.ts @@ -1,110 +1,116 @@ -import { Plugin } from '@nuxt/types' -import { APISequenceLabelingRepository } from '~/repositories/tasks/sequenceLabeling/apiSequenceLabeling' -import { APISeq2seqRepository } from '~/repositories/tasks/seq2seq/apiSeq2seq' -import { APIConfigRepository } from '~/repositories/autoLabeling/config/apiConfigRepository' -import { APITemplateRepository } from '~/repositories/autoLabeling/template/apiTemplateRepository' -import { APIUserRepository } from '~/repositories/user/apiUserRepository' -import { APIStatisticsRepository } from '~/repositories/statistics/apiStatisticsRepository' -import { APIRoleRepository } from '~/repositories/role/apiRoleRepository' -import { APIProjectRepository } from '~/repositories/project/apiProjectRepository' -import { LocalStorageOptionRepository} from '~/repositories/option/apiOptionRepository' -import { APIMemberRepository } from '~/repositories/member/apiMemberRepository' -import { APILabelRepository } from '~/repositories/label/apiLabelRepository' -import { APIDocumentRepository } from '~/repositories/document/apiDocumentRepository' -import { APICommentRepository } from '~/repositories/comment/apiCommentRepository' -import { APIAuthRepository } from '~/repositories/auth/apiAuthRepository' -import { LabelApplicationService } from '~/services/application/label/labelApplicationService' -import { MemberApplicationService } from '~/services/application/member/memberApplicationService' -import { UserApplicationService } from '~/services/application/user/userApplicationService' -import { RoleApplicationService } from '~/services/application/role/roleApplicationService' -import { ProjectApplicationService } from '~/services/application/project/projectApplicationService' -import { CommentApplicationService } from '~/services/application/comment/commentApplicationService' -import { StatisticsApplicationService } from '~/services/application/statistics/statisticsApplicationService' -import { DocumentApplicationService } from '~/services/application/document/documentApplicationService' -import { OptionApplicationService } from '~/services/application/option/optionApplicationService' -import { SequenceLabelingApplicationService } from '~/services/application/tasks/sequenceLabeling/sequenceLabelingApplicationService' -import { Seq2seqApplicationService } from '~/services/application/tasks/seq2seq/seq2seqApplicationService' -import { ConfigApplicationService } from '~/services/application/autoLabeling/configApplicationService' -import { TemplateApplicationService } from '~/services/application/autoLabeling/templateApplicationService' -import { APITextClassificationRepository } from '~/repositories/tasks/textClassification/apiTextClassification' -import { TextClassificationApplicationService } from '~/services/application/tasks/textClassification/textClassificationApplicationService' -import { AuthApplicationService } from '~/services/application/auth/authApplicationService' +import {Plugin} from '@nuxt/types' +import {APISequenceLabelingRepository} from '~/repositories/tasks/sequenceLabeling/apiSequenceLabeling' +import {APISeq2seqRepository} from '~/repositories/tasks/seq2seq/apiSeq2seq' +import {APIConfigRepository} from '~/repositories/autoLabeling/config/apiConfigRepository' +import {APITemplateRepository} from '~/repositories/autoLabeling/template/apiTemplateRepository' +import {APIUserRepository} from '~/repositories/user/apiUserRepository' +import {APIStatisticsRepository} from '~/repositories/statistics/apiStatisticsRepository' +import {APIRoleRepository} from '~/repositories/role/apiRoleRepository' +import {APIProjectRepository} from '~/repositories/project/apiProjectRepository' +import {LocalStorageOptionRepository} from '~/repositories/option/apiOptionRepository' +import {APIMemberRepository} from '~/repositories/member/apiMemberRepository' +import {APILabelRepository} from '~/repositories/label/apiLabelRepository' +import {ApiLinksRepository} from "~/repositories/links/apiLinksRepository"; +import {APIDocumentRepository} from '~/repositories/document/apiDocumentRepository' +import {APICommentRepository} from '~/repositories/comment/apiCommentRepository' +import {APIAuthRepository} from '~/repositories/auth/apiAuthRepository' +import {LabelApplicationService} from '~/services/application/label/labelApplicationService' +import {LinksApplicationService} from "~/services/application/links/linksApplicationService"; +import {MemberApplicationService} from '~/services/application/member/memberApplicationService' +import {UserApplicationService} from '~/services/application/user/userApplicationService' +import {RoleApplicationService} from '~/services/application/role/roleApplicationService' +import {ProjectApplicationService} from '~/services/application/project/projectApplicationService' +import {CommentApplicationService} from '~/services/application/comment/commentApplicationService' +import {StatisticsApplicationService} from '~/services/application/statistics/statisticsApplicationService' +import {DocumentApplicationService} from '~/services/application/document/documentApplicationService' +import {OptionApplicationService} from '~/services/application/option/optionApplicationService' +import {SequenceLabelingApplicationService} from '~/services/application/tasks/sequenceLabeling/sequenceLabelingApplicationService' +import {Seq2seqApplicationService} from '~/services/application/tasks/seq2seq/seq2seqApplicationService' +import {ConfigApplicationService} from '~/services/application/autoLabeling/configApplicationService' +import {TemplateApplicationService} from '~/services/application/autoLabeling/templateApplicationService' +import {APITextClassificationRepository} from '~/repositories/tasks/textClassification/apiTextClassification' +import {TextClassificationApplicationService} from '~/services/application/tasks/textClassification/textClassificationApplicationService' +import {AuthApplicationService} from '~/services/application/auth/authApplicationService' export interface Services { - label: LabelApplicationService, - member: MemberApplicationService, - user: UserApplicationService, - role: RoleApplicationService, - project: ProjectApplicationService, - comment: CommentApplicationService, - statistics: StatisticsApplicationService, - document: DocumentApplicationService, - textClassification: TextClassificationApplicationService, - sequenceLabeling: SequenceLabelingApplicationService, - seq2seq: Seq2seqApplicationService, - option: OptionApplicationService, - config: ConfigApplicationService, - template: TemplateApplicationService, - auth: AuthApplicationService + label: LabelApplicationService, + links: LinksApplicationService, + member: MemberApplicationService, + user: UserApplicationService, + role: RoleApplicationService, + project: ProjectApplicationService, + comment: CommentApplicationService, + statistics: StatisticsApplicationService, + document: DocumentApplicationService, + textClassification: TextClassificationApplicationService, + sequenceLabeling: SequenceLabelingApplicationService, + seq2seq: Seq2seqApplicationService, + option: OptionApplicationService, + config: ConfigApplicationService, + template: TemplateApplicationService, + auth: AuthApplicationService } declare module 'vue/types/vue' { - interface Vue { - readonly $services: Services - } + interface Vue { + readonly $services: Services + } } const plugin: Plugin = (context, inject) => { - const labelRepository = new APILabelRepository() - const memberRepository = new APIMemberRepository() - const userRepository = new APIUserRepository() - const roleRepository = new APIRoleRepository() - const projectRepository = new APIProjectRepository() - const commentRepository = new APICommentRepository() - const statisticsRepository = new APIStatisticsRepository() - const documentRepository = new APIDocumentRepository() - const textClassificationRepository = new APITextClassificationRepository() - const sequenceLabelingRepository = new APISequenceLabelingRepository() - const seq2seqRepository = new APISeq2seqRepository() - const optionRepository = new LocalStorageOptionRepository() - const configRepository = new APIConfigRepository() - const templateRepository = new APITemplateRepository() - const authRepository = new APIAuthRepository() + const labelRepository = new APILabelRepository() + const linksRepository = new ApiLinksRepository() + const memberRepository = new APIMemberRepository() + const userRepository = new APIUserRepository() + const roleRepository = new APIRoleRepository() + const projectRepository = new APIProjectRepository() + const commentRepository = new APICommentRepository() + const statisticsRepository = new APIStatisticsRepository() + const documentRepository = new APIDocumentRepository() + const textClassificationRepository = new APITextClassificationRepository() + const sequenceLabelingRepository = new APISequenceLabelingRepository() + const seq2seqRepository = new APISeq2seqRepository() + const optionRepository = new LocalStorageOptionRepository() + const configRepository = new APIConfigRepository() + const templateRepository = new APITemplateRepository() + const authRepository = new APIAuthRepository() - const label = new LabelApplicationService(labelRepository) - const member = new MemberApplicationService(memberRepository) - const user = new UserApplicationService(userRepository) - const role = new RoleApplicationService(roleRepository) - const project = new ProjectApplicationService(projectRepository) - const comment = new CommentApplicationService(commentRepository) - const statistics = new StatisticsApplicationService(statisticsRepository) - const document = new DocumentApplicationService(documentRepository) - const textClassification = new TextClassificationApplicationService(textClassificationRepository) - const sequenceLabeling = new SequenceLabelingApplicationService(sequenceLabelingRepository) - const seq2seq = new Seq2seqApplicationService(seq2seqRepository) - const option = new OptionApplicationService(optionRepository) - const config = new ConfigApplicationService(configRepository) - const template = new TemplateApplicationService(templateRepository) - const auth = new AuthApplicationService(authRepository) - - const services: Services = { - label, - member, - user, - role, - project, - comment, - statistics, - document, - textClassification, - sequenceLabeling, - seq2seq, - option, - config, - template, - auth - } - inject('services', services) + const label = new LabelApplicationService(labelRepository) + const links = new LinksApplicationService(linksRepository) + const member = new MemberApplicationService(memberRepository) + const user = new UserApplicationService(userRepository) + const role = new RoleApplicationService(roleRepository) + const project = new ProjectApplicationService(projectRepository) + const comment = new CommentApplicationService(commentRepository) + const statistics = new StatisticsApplicationService(statisticsRepository) + const document = new DocumentApplicationService(documentRepository) + const textClassification = new TextClassificationApplicationService(textClassificationRepository) + const sequenceLabeling = new SequenceLabelingApplicationService(sequenceLabelingRepository) + const seq2seq = new Seq2seqApplicationService(seq2seqRepository) + const option = new OptionApplicationService(optionRepository) + const config = new ConfigApplicationService(configRepository) + const template = new TemplateApplicationService(templateRepository) + const auth = new AuthApplicationService(authRepository) + + const services: Services = { + label, + links, + member, + user, + role, + project, + comment, + statistics, + document, + textClassification, + sequenceLabeling, + seq2seq, + option, + config, + template, + auth + } + inject('services', services) } export default plugin diff --git a/frontend/repositories/links/apiLinksRepository.ts b/frontend/repositories/links/apiLinksRepository.ts new file mode 100644 index 00000000..86d9487e --- /dev/null +++ b/frontend/repositories/links/apiLinksRepository.ts @@ -0,0 +1,45 @@ +import ApiService from '@/services/api.service' +import { LinksRepository } from "~/domain/models/links/linksRepository"; +import { LinkItem } from "~/domain/models/links/link"; + +export interface LinkItemResponse { + id: number, + text: string, + prefix_key: string, + suffix_key: string, + background_color: string, + text_color: string +} + +export class ApiLinksRepository implements LinksRepository { + constructor( + private readonly request = ApiService + ) {} + + async list(projectId: string): Promise { + const url = `/projects/${projectId}/relation_types` + const response = await this.request.get(url) + console.log(response); + const responseItems: LinkItemResponse[] = response.data + return responseItems.map(item => LinkItem.valueOf(item)) + } + + async create(projectId: string, item: LinkItem): Promise { + const url = `/projects/${projectId}/relation_types` + const response = await this.request.post(url, item.toObject()) + const responseItem: LinkItemResponse = response.data + return LinkItem.valueOf(responseItem) + } + + async update(projectId: string, item: LinkItem): Promise { + const url = `/projects/${projectId}/relation_types/${item.id}` + const response = await this.request.patch(url, item.toObject()) + const responseItem: LinkItemResponse = response.data + return LinkItem.valueOf(responseItem) + } + + async bulkDelete(projectId: string, linkIds: number[]): Promise { + const url = `/projects/${projectId}/relation_types` + await this.request.delete(url, { ids: linkIds }) + } +} diff --git a/frontend/services/application/links/linkData.ts b/frontend/services/application/links/linkData.ts new file mode 100644 index 00000000..f705c22f --- /dev/null +++ b/frontend/services/application/links/linkData.ts @@ -0,0 +1,19 @@ +import { LinkItem } from '~/domain/models/links/link' + +export class LinkDTO { + id: number + text: string + prefixKey: string | null + suffixKey: string | null + backgroundColor: string + textColor: string + + constructor(item: LinkItem) { + this.id = item.id + this.text = item.text + this.prefixKey = item.prefixKey + this.suffixKey = item.suffixKey + this.backgroundColor = item.backgroundColor + this.textColor = '#ffffff' + } +} diff --git a/frontend/services/application/links/linksApplicationService.ts b/frontend/services/application/links/linksApplicationService.ts new file mode 100644 index 00000000..397227a5 --- /dev/null +++ b/frontend/services/application/links/linksApplicationService.ts @@ -0,0 +1,30 @@ +import { LinkDTO } from './linkData'; +import { LinksRepository } from '~/domain/models/links/linksRepository'; +import { LinkItem } from '~/domain/models/links/link'; + + +export class LinksApplicationService { + constructor( + private readonly repository: LinksRepository + ) {} + + public async list(id: string): Promise { + const items = await this.repository.list(id) + return items.map(item => new LinkDTO(item)) + } + + public create(projectId: string, item: LinkDTO): void { + const label = new LinkItem(0, item.text, item.prefixKey, item.suffixKey, item.backgroundColor, item.textColor) + this.repository.create(projectId, label) + } + + public update(projectId: string, item: LinkDTO): void { + const label = new LinkItem(item.id, item.text, item.prefixKey, item.suffixKey, item.backgroundColor, item.textColor) + this.repository.update(projectId, label) + } + + public bulkDelete(projectId: string, items: LinkDTO[]): Promise { + const ids = items.map(item => item.id) + return this.repository.bulkDelete(projectId, ids) + } +}