Browse Source

Simplify FormUpdate component

pull/2093/head
Hironsan 2 years ago
parent
commit
f7a12a949a
7 changed files with 75 additions and 197 deletions
  1. 240
      frontend/components/project/FormUpdate.vue
  2. 3
      frontend/components/project/TagList.vue
  3. 6
      frontend/domain/models/tag/tagRepository.ts
  4. 2
      frontend/pages/projects/create.vue
  5. 10
      frontend/repositories/tag/apiTagRepository.ts
  6. 2
      frontend/services/application/project/projectApplicationService.ts
  7. 9
      frontend/services/application/tag/tagApplicationService.ts

240
frontend/components/project/FormUpdate.vue

@ -1,222 +1,92 @@
<template>
<v-card>
<v-card-text v-if="isReady">
<v-form ref="form" v-model="valid">
<v-card-text v-if="!!project">
<v-form ref="form" v-model="valid" :disabled="!isEditing">
<v-row>
<v-col cols="12" sm="6">
<h3>Name</h3>
<project-name-field v-model="project.name" :disabled="!edit.name" single-line />
</v-col>
<v-col cols="12" sm="6">
<v-btn
v-if="!edit.name"
outlined
color="grey"
class="text-capitalize"
@click="editProject('name')"
>
Edit
</v-btn>
<v-btn
v-if="edit.name"
outlined
color="primary"
class="text-capitalize"
@click="doneEdit()"
>
Save
</v-btn>
<v-btn
v-if="edit.name"
outlined
color="grey"
class="text-capitalize"
@click="cancelEdit()"
>
Cancel
</v-btn>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6">
<h3>Description</h3>
<project-description-field
v-model="project.description"
:disabled="!edit.desc"
single-line
/>
</v-col>
<v-col cols="12" sm="6">
<v-btn
v-if="!edit.desc"
outlined
color="grey"
class="text-capitalize"
@click="editProject('desc')"
>
Edit
</v-btn>
<v-btn
v-if="edit.desc"
outlined
color="primary"
class="text-capitalize"
@click="doneEdit()"
>
Save
</v-btn>
<v-btn
v-if="edit.desc"
outlined
color="grey"
class="text-capitalize"
@click="cancelEdit()"
>
Cancel
</v-btn>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6">
<h3>Tags</h3>
<v-chip
v-for="tag in tags"
:key="tag.id"
close
outlined
@click:close="removeTag(tag.id)"
>{{ tag.text }}
</v-chip>
<v-text-field
v-model="tagInput"
clearable
:prepend-icon="mdiPlusCircle"
@keyup.enter="addTag"
@click:prepend="addTag"
>
</v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6">
<h3>Shuffle</h3>
<v-col cols="12" sm="12" md="6" lg="6" xl="6">
<project-name-field v-model="project.name" />
<project-description-field v-model="project.description" />
<tag-list v-model="tags" />
<random-order-field v-model="project.enableRandomOrder" />
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6">
<h3>Collaboration</h3>
<sharing-mode-field v-model="project.enableSharingMode" />
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions class="ps-4 pt-0">
<v-btn
v-if="!isEditing"
color="primary"
class="text-capitalize"
@click="isEditing = true"
v-text="`Edit`"
/>
<v-btn
v-show="isEditing"
color="primary"
:disabled="!valid || isUpdating"
class="mr-4 text-capitalize"
@click="save"
v-text="$t('generic.save')"
/>
<v-btn
v-show="isEditing"
:disabled="isUpdating"
class="text-capitalize"
@click="cancel"
v-text="$t('generic.cancel')"
/>
</v-card-actions>
</v-card>
</template>
<script>
import { mdiPlusCircle } from '@mdi/js'
<script lang="ts">
import Vue from 'vue'
import ProjectDescriptionField from './ProjectDescriptionField.vue'
import ProjectNameField from './ProjectNameField.vue'
import RandomOrderField from './RandomOrderField.vue'
import SharingModeField from './SharingModeField.vue'
import TagList from './TagList.vue'
import { Project } from '~/domain/models/project/project'
export default {
export default Vue.extend({
components: {
ProjectNameField,
ProjectDescriptionField,
RandomOrderField,
SharingModeField
SharingModeField,
TagList
},
data() {
return {
project: {},
tags: {},
beforeEditCache: {},
tagInput: '',
edit: {
name: false,
desc: false
},
project: {} as Project,
tags: [] as string[],
valid: false,
mdiPlusCircle
isEditing: false,
isUpdating: false
}
},
async fetch() {
this.project = await this.$services.project.findById(this.projectId)
console.log(this.project)
this.getTags()
},
computed: {
isReady() {
return !!this.project
},
projectId() {
return this.$route.params.id
}
},
watch: {
'project.enableRandomOrder'() {
this.doneEdit()
},
'project.enableSharingMode'() {
this.doneEdit()
}
async fetch() {
const projectId = this.$route.params.id
this.project = await this.$services.project.findById(projectId)
this.tags = this.project.tags.map((item: any) => item.text)
this.isEditing = false
},
methods: {
initEdit() {
Object.keys(this.edit).forEach((v) => {
this.edit[v] = false
})
},
editProject(name) {
this.cancelEdit()
this.edit[name] = true
Object.assign(this.beforeEditCache, this.project)
},
cancelEdit() {
this.initEdit()
Object.assign(this.project, this.beforeEditCache)
},
async doneEdit() {
if (!this.validate()) {
this.cancelEdit()
return
}
try {
await this.$services.project.update(this.projectId, this.project)
this.beforeEditCache = {}
this.$fetch()
} finally {
this.initEdit()
}
},
validate() {
return this.$refs.form.validate()
},
async getTags() {
this.tags = await this.$services.tag.list(this.projectId)
},
async addTag() {
await this.$services.tag.create(this.projectId, this.tagInput)
this.tagInput = ''
this.getTags()
cancel() {
this.$fetch()
},
removeTag(id) {
this.$services.tag.delete(this.projectId, id)
this.tags = this.tags.filter((tag) => tag.id !== id)
async save() {
this.isUpdating = true
await this.$services.project.update(this.project.id, this.project)
await this.$services.tag.bulkUpdate(this.project.id, this.tags)
this.$fetch()
this.isUpdating = false
}
}
}
})
</script>

3
frontend/components/project/TagList.vue

@ -6,10 +6,9 @@
label="Tags"
multiple
chips
outlined
dense
deletable-chips
hide-selected
hide-details
@change="$emit('input', $event)"
/>
</template>

6
frontend/domain/models/tag/tagRepository.ts

@ -1,9 +1,9 @@
import { TagItem } from '~/domain/models/tag/tag'
export interface TagRepository {
list(projectId: string): Promise<TagItem[]>
list(projectId: string | number): Promise<TagItem[]>
create(projectId: string, item: string): Promise<TagItem>
create(projectId: string | number, item: string): Promise<TagItem>
delete(projectId: string, tagId: number): Promise<void>
delete(projectId: string | number, tagId: number): Promise<void>
}

2
frontend/pages/projects/create.vue

@ -6,7 +6,7 @@
<project-type-field v-model="editedItem.projectType" />
<project-name-field v-model="editedItem.name" outlined autofocus />
<project-description-field v-model="editedItem.description" outlined />
<tag-list v-model="editedItem.tags" />
<tag-list v-model="editedItem.tags" outlined />
<v-checkbox
v-if="showExclusiveCategories"
v-model="editedItem.exclusiveCategories"

10
frontend/repositories/tag/apiTagRepository.ts

@ -1,6 +1,6 @@
import ApiService from '@/services/api.service'
import { TagRepository } from '@/domain/models/tag/tagRepository'
import { TagItem } from '@/domain/models/tag/tag'
import { TagRepository } from '@/domain/models/tag/tagRepository'
import ApiService from '@/services/api.service'
function toModel(item: { [key: string]: any }): TagItem {
return new TagItem(item.id, item.text, item.project)
@ -9,19 +9,19 @@ function toModel(item: { [key: string]: any }): TagItem {
export class APITagRepository implements TagRepository {
constructor(private readonly request = ApiService) {}
async list(projectId: string): Promise<TagItem[]> {
async list(projectId: string | number): Promise<TagItem[]> {
const url = `/projects/${projectId}/tags`
const response = await this.request.get(url)
return response.data.map((item: { [key: string]: any }) => toModel(item))
}
async create(projectId: string, text: string): Promise<TagItem> {
async create(projectId: string | number, text: string): Promise<TagItem> {
const url = `/projects/${projectId}/tags`
const response = await this.request.post(url, { text })
return toModel(response.data)
}
async delete(projectId: string, tagId: number): Promise<void> {
async delete(projectId: string | number, tagId: number): Promise<void> {
const url = `/projects/${projectId}/tags/${tagId}`
await this.request.delete(url)
}

2
frontend/services/application/project/projectApplicationService.ts

@ -87,7 +87,7 @@ export class ProjectApplicationService {
enableGraphemeMode,
useRelation,
guideline = ''
}: Options
}: Omit<Options, 'tags'>
): Promise<void> {
const project = Project.create(
projectId,

9
frontend/services/application/tag/tagApplicationService.ts

@ -16,4 +16,13 @@ export class TagApplicationService {
public async delete(projectId: string, id: number): Promise<void> {
return await this.repository.delete(projectId, id)
}
public async bulkUpdate(projectId: string | number, tags: string[]): Promise<void> {
const currentTags = await this.repository.list(projectId)
const currentTagNames = currentTags.map((tag) => tag.text)
const addedTagNames = tags.filter((tag) => !currentTagNames.includes(tag))
const deletedTagIds = currentTags.filter((tag) => !tags.includes(tag.text)).map((tag) => tag.id)
await Promise.all(addedTagNames.map((tag) => this.repository.create(projectId, tag)))
await Promise.all(deletedTagIds.map((id) => this.repository.delete(projectId, id)))
}
}
Loading…
Cancel
Save