From 252c540829bfb5df61d56f5d32b45cc4637b80fd Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Tue, 14 May 2019 00:25:09 +0900 Subject: [PATCH 1/2] refactor: Move logics to store in new toot --- .../TimelineSpace/Modals/NewToot.vue | 64 ++++---------- src/renderer/errors/validations/index.js | 7 ++ .../store/TimelineSpace/Modals/NewToot.ts | 87 ++++++++++++++++++- 3 files changed, 105 insertions(+), 53 deletions(-) create mode 100644 src/renderer/errors/validations/index.js diff --git a/src/renderer/components/TimelineSpace/Modals/NewToot.vue b/src/renderer/components/TimelineSpace/Modals/NewToot.vue index a1d3bfee..c5b21e84 100644 --- a/src/renderer/components/TimelineSpace/Modals/NewToot.vue +++ b/src/renderer/components/TimelineSpace/Modals/NewToot.vue @@ -22,7 +22,8 @@ maxlength="420" class="image-description" :placeholder="$t('modals.new_toot.description')" - v-model="mediaDescriptions[media.id]" + :value="mediaDescriptions[media.id]" + @input="updateDescription(media.id, $event.target.value)" v-shortkey="{ left: ['arrowleft'], right: ['arrowright'] }" @shortkey="handleDescriptionKey" role="textbox" @@ -128,7 +129,6 @@ export default { data() { return { status: '', - mediaDescriptions: {}, spoiler: '', showContentWarning: false, visibilityList: Visibility @@ -145,6 +145,7 @@ export default { }, attachedMedias: state => state.attachedMedias, attachedMediaId: state => state.attachedMediaId, + mediaDescriptions: state => state.mediaDescriptions, blockSubmit: state => state.blockSubmit, visibility: state => state.visibility, sensitive: state => state.sensitive, @@ -210,55 +211,18 @@ export default { this.$store.dispatch('TimelineSpace/Modals/NewToot/closeModal') }, async toot() { - if (!this.newTootModal) { - return - } - if (this.status.length < 1 || this.status.length > this.tootMax) { - return this.$message({ - message: this.$t('validation.new_toot.toot_length', { min: 1, max: this.tootMax }), - type: 'error' - }) - } - const visibilityKey = Object.keys(Visibility).find(key => { - return Visibility[key].value === this.visibility - }) - let form = { + const form = { status: this.status, - visibility: Visibility[visibilityKey].key, - sensitive: this.sensitive, - spoiler_text: this.spoiler - } - if (this.replyToId !== null) { - form = Object.assign(form, { - in_reply_to_id: this.replyToId - }) - } - if (this.attachedMedias.length > 0) { - if (this.attachedMedias.length > 4) { - return this.$message({ - message: this.$t('validation.new_toot.attach_length', { max: 4 }), - type: 'error' - }) - } - form = Object.assign(form, { - media_ids: this.attachedMedias.map(m => { - return m.id - }) - }) + spoiler: this.spoiler } - const status = await this.$store - .dispatch('TimelineSpace/Modals/NewToot/updateMedia', this.mediaDescriptions) - .then(() => { - return this.$store.dispatch('TimelineSpace/Modals/NewToot/postToot', form) - }) - .catch(e => { - console.error(e) - this.$message({ - message: this.$t('message.toot_error'), - type: 'error' - }) + await this.$store.dispatch('TimelineSpace/Modals/NewToot/postToot', form).catch(err => { + console.error(err) + this.$message({ + message: this.$t('message.toot_error'), + type: 'error' }) + }) this.$store.dispatch('TimelineSpace/Modals/NewToot/updateHashtags', status.tags) this.close() }, @@ -305,8 +269,7 @@ export default { }) }, removeAttachment(media) { - this.$store.commit('TimelineSpace/Modals/NewToot/removeMedia', media) - delete this.mediaDescriptions[media.id] + this.$store.dispatch('TimelineSpace/Modals/NewToot/removeMedia', media) }, changeVisibility(level) { this.$store.commit('TimelineSpace/Modals/NewToot/changeVisibilityValue', level) @@ -340,6 +303,9 @@ export default { default: return true } + }, + updateDescription(id, value) { + this.$store.commit('TimelineSpace/Modals/NewToot/updateMediaDescription', { id: id, description: value }) } } } diff --git a/src/renderer/errors/validations/index.js b/src/renderer/errors/validations/index.js new file mode 100644 index 00000000..c45d8014 --- /dev/null +++ b/src/renderer/errors/validations/index.js @@ -0,0 +1,7 @@ +export class NewTootModalOpen extends Error {} + +export class NewTootTootLength extends Error {} + +export class NewTootAttachLength extends Error {} + +export class NewTootMediaDescription extends Error {} diff --git a/src/renderer/store/TimelineSpace/Modals/NewToot.ts b/src/renderer/store/TimelineSpace/Modals/NewToot.ts index 47aefcdc..a3c6a495 100644 --- a/src/renderer/store/TimelineSpace/Modals/NewToot.ts +++ b/src/renderer/store/TimelineSpace/Modals/NewToot.ts @@ -5,6 +5,12 @@ import TootStatus, { StatusState } from './NewToot/Status' import { Module, MutationTree, ActionTree, GetterTree } from 'vuex' import { RootState } from '@/store' import AxiosLoading from '@/utils/axiosLoading' +import { NewTootModalOpen, NewTootTootLength, NewTootAttachLength, NewTootMediaDescription } from '@/errors/validations' + +type MediaDescription = { + id: number + description: string +} export interface NewTootState { modalOpen: boolean @@ -13,6 +19,7 @@ export interface NewTootState { replyToMessage: Status | null blockSubmit: boolean attachedMedias: Array + mediaDescriptions: { [key: number]: string | null } visibility: number sensitive: boolean attachedMediaId: number @@ -32,6 +39,7 @@ const state = (): NewTootState => ({ replyToMessage: null, blockSubmit: false, attachedMedias: [], + mediaDescriptions: {}, visibility: Visibility.Public.value, sensitive: false, attachedMediaId: 0, @@ -49,6 +57,9 @@ export const MUTATION_TYPES = { APPEND_ATTACHED_MEDIAS: 'appendAttachedMedias', CLEAR_ATTACHED_MEDIAS: 'clearAttachedMedias', REMOVE_MEDIA: 'removeMedia', + UPDATE_MEDIA_DESCRIPTION: 'updateMediaDescription', + CLEAR_MEDIA_DESCRIPTIONS: 'clearMediaDescriptions', + REMOVE_MEDIA_DESCRIPTION: 'removeMediaDescription', CHANGE_VISIBILITY_VALUE: 'changeVisibilityValue', CHANGE_SENSITIVE: 'changeSensitive', UPDATE_MEDIA_ID: 'updateMediaId', @@ -82,6 +93,17 @@ const mutations: MutationTree = { [MUTATION_TYPES.REMOVE_MEDIA]: (state, media: Attachment) => { state.attachedMedias = state.attachedMedias.filter(m => m.id !== media.id) }, + [MUTATION_TYPES.UPDATE_MEDIA_DESCRIPTION]: (state, value: MediaDescription) => { + state.mediaDescriptions[value.id] = value.description + }, + [MUTATION_TYPES.CLEAR_MEDIA_DESCRIPTIONS]: state => { + state.mediaDescriptions = {} + }, + [MUTATION_TYPES.REMOVE_MEDIA_DESCRIPTION]: (state, id: number) => { + const descriptions = state.mediaDescriptions + delete descriptions[id] + state.mediaDescriptions = descriptions + }, /** * changeVisibilityValue * Update visibility using direct value @@ -118,20 +140,52 @@ const actions: ActionTree = { commit(MUTATION_TYPES.CHANGE_LOADING, false) }) }, - updateMedia: async ({ rootState }, media: Attachment) => { + updateMedia: async ({ rootState }, mediaDescription: MediaDescription) => { if (rootState.TimelineSpace.account.accessToken === undefined || rootState.TimelineSpace.account.accessToken === null) { throw new AuthenticationError() } const client = new Mastodon(rootState.TimelineSpace.account.accessToken, rootState.TimelineSpace.account.baseURL + '/api/v1') - const attachments = Object.keys(media).map(async id => { - return client.put(`/media/${id}`, { description: media[id] }) + const attachments = Object.keys(mediaDescription).map(async id => { + if (mediaDescription[id] !== null) { + return client.put(`/media/${id}`, { description: mediaDescription[id] }) + } else { + return Promise.resolve({}) + } }) return Promise.all(attachments).catch(err => { console.error(err) throw err }) }, - postToot: async ({ state, commit, rootState }, form) => { + postToot: async ({ state, commit, rootState, dispatch }, { status, spoiler }) => { + if (!state.modalOpen) { + throw new NewTootModalOpen() + } + + if (status.length < 1 || status.length > rootState.TimelineSpace.tootMax) { + throw new NewTootTootLength() + } + + const visibilityKey: string | undefined = Object.keys(Visibility).find(key => { + return Visibility[key].value === state.visibility + }) + let specifiedVisibility: string = Visibility.Public.key + if (visibilityKey !== undefined) { + specifiedVisibility = Visibility[visibilityKey].key + } + let form = { + status: status, + visibility: specifiedVisibility, + sensitive: state.sensitive, + spoiler_text: spoiler + } + + if (state.replyToMessage !== null) { + form = Object.assign(form, { + in_reply_to_id: state.replyToMessage.id + }) + } + if (rootState.TimelineSpace.account.accessToken === undefined || rootState.TimelineSpace.account.accessToken === null) { throw new AuthenticationError() } @@ -139,6 +193,22 @@ const actions: ActionTree = { return } commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, true) + + if (state.attachedMedias.length > 0) { + if (state.attachedMedias.length > 4) { + throw new NewTootAttachLength() + } + form = Object.assign(form, { + media_ids: state.attachedMedias.map(m => { + return m.id + }) + }) + // Update media descriptions + await dispatch('updateMedia', state.mediaDescriptions).catch(_ => { + throw new NewTootMediaDescription() + }) + } + const client = new Mastodon(rootState.TimelineSpace.account.accessToken, rootState.TimelineSpace.account.baseURL + '/api/v1') return client .post('/statuses', form) @@ -182,6 +252,7 @@ const actions: ActionTree = { commit(MUTATION_TYPES.SET_REPLY_TO, null) commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, false) commit(MUTATION_TYPES.CLEAR_ATTACHED_MEDIAS) + commit(MUTATION_TYPES.CLEAR_MEDIA_DESCRIPTIONS) commit(MUTATION_TYPES.CHANGE_SENSITIVE, false) commit(MUTATION_TYPES.CHANGE_VISIBILITY_VALUE, Visibility.Public.value) }, @@ -210,9 +281,17 @@ const actions: ActionTree = { incrementMediaId: ({ commit, state }) => { commit(MUTATION_TYPES.UPDATE_MEDIA_ID, state.attachedMediaId + 1) }, + decrementMediaId: ({ commit, state }) => { + commit(MUTATION_TYPES.UPDATE_MEDIA_ID, state.attachedMediaId - 1) + }, resetMediaId: ({ commit }) => { commit(MUTATION_TYPES.UPDATE_MEDIA_ID, 0) }, + removeMedia: ({ commit, dispatch }, media: Attachment) => { + commit(MUTATION_TYPES.REMOVE_MEDIA, media) + commit(MUTATION_TYPES.REMOVE_MEDIA_DESCRIPTION, media.id) + dispatch('decrementMediaId') + }, updateHashtags: ({ commit, state }, tags: Array) => { if (state.pinedHashtag && tags.length > 0) { commit(MUTATION_TYPES.UPDATE_HASHTAGS, tags) From 79bd5bef7f20238533b8a91030825039f1090525 Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Tue, 14 May 2019 23:33:28 +0900 Subject: [PATCH 2/2] refactor: Catch exception and show message when post toots --- .../TimelineSpace/Modals/NewToot.vue | 33 ++++++++++++++----- .../{validations/index.js => validations.js} | 6 ++++ .../store/TimelineSpace/Modals/NewToot.ts | 21 +++++++----- 3 files changed, 44 insertions(+), 16 deletions(-) rename src/renderer/errors/{validations/index.js => validations.js} (57%) diff --git a/src/renderer/components/TimelineSpace/Modals/NewToot.vue b/src/renderer/components/TimelineSpace/Modals/NewToot.vue index c5b21e84..8edc2b4c 100644 --- a/src/renderer/components/TimelineSpace/Modals/NewToot.vue +++ b/src/renderer/components/TimelineSpace/Modals/NewToot.vue @@ -120,6 +120,7 @@ import { mapState, mapGetters } from 'vuex' import { clipboard } from 'electron' import Visibility from '~/src/constants/visibility' import Status from './NewToot/Status' +import { NewTootTootLength, NewTootAttachLength, NewTootModalOpen, NewTootBlockSubmit } from '@/errors/validations' export default { name: 'new-toot', @@ -216,15 +217,31 @@ export default { spoiler: this.spoiler } - await this.$store.dispatch('TimelineSpace/Modals/NewToot/postToot', form).catch(err => { + try { + await this.$store.dispatch('TimelineSpace/Modals/NewToot/postToot', form) + this.$store.dispatch('TimelineSpace/Modals/NewToot/updateHashtags', status.tags) + this.close() + } catch (err) { console.error(err) - this.$message({ - message: this.$t('message.toot_error'), - type: 'error' - }) - }) - this.$store.dispatch('TimelineSpace/Modals/NewToot/updateHashtags', status.tags) - this.close() + if (err instanceof NewTootTootLength) { + this.$message({ + message: this.$t('validation.new_toot.toot_length', { min: 1, max: this.tootMax }), + type: 'error' + }) + } else if (err instanceof NewTootAttachLength) { + this.$message({ + message: this.$t('validation.new_toot.attach_length', { max: 4 }), + type: 'error' + }) + } else if (err instanceof NewTootModalOpen || err instanceof NewTootBlockSubmit) { + // Nothing + } else { + this.$message({ + message: this.$t('message.toot_error'), + type: 'error' + }) + } + } }, selectImage() { this.$refs.image.click() diff --git a/src/renderer/errors/validations/index.js b/src/renderer/errors/validations.js similarity index 57% rename from src/renderer/errors/validations/index.js rename to src/renderer/errors/validations.js index c45d8014..91f22f76 100644 --- a/src/renderer/errors/validations/index.js +++ b/src/renderer/errors/validations.js @@ -1,7 +1,13 @@ export class NewTootModalOpen extends Error {} +export class NewTootBlockSubmit extends Error {} + export class NewTootTootLength extends Error {} export class NewTootAttachLength extends Error {} export class NewTootMediaDescription extends Error {} + +export class NewTootUnknownType extends Error {} + +export class AuthenticationError extends Error {} diff --git a/src/renderer/store/TimelineSpace/Modals/NewToot.ts b/src/renderer/store/TimelineSpace/Modals/NewToot.ts index a3c6a495..9cc024ff 100644 --- a/src/renderer/store/TimelineSpace/Modals/NewToot.ts +++ b/src/renderer/store/TimelineSpace/Modals/NewToot.ts @@ -5,7 +5,15 @@ import TootStatus, { StatusState } from './NewToot/Status' import { Module, MutationTree, ActionTree, GetterTree } from 'vuex' import { RootState } from '@/store' import AxiosLoading from '@/utils/axiosLoading' -import { NewTootModalOpen, NewTootTootLength, NewTootAttachLength, NewTootMediaDescription } from '@/errors/validations' +import { + NewTootModalOpen, + NewTootBlockSubmit, + NewTootTootLength, + NewTootAttachLength, + NewTootMediaDescription, + NewTootUnknownType, + AuthenticationError +} from '@/errors/validations' type MediaDescription = { id: number @@ -157,7 +165,7 @@ const actions: ActionTree = { throw err }) }, - postToot: async ({ state, commit, rootState, dispatch }, { status, spoiler }) => { + postToot: async ({ state, commit, rootState, dispatch }, { status, spoiler }): Promise => { if (!state.modalOpen) { throw new NewTootModalOpen() } @@ -190,9 +198,8 @@ const actions: ActionTree = { throw new AuthenticationError() } if (state.blockSubmit) { - return + throw new NewTootBlockSubmit() } - commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, true) if (state.attachedMedias.length > 0) { if (state.attachedMedias.length > 4) { @@ -209,6 +216,7 @@ const actions: ActionTree = { }) } + commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, true) const client = new Mastodon(rootState.TimelineSpace.account.accessToken, rootState.TimelineSpace.account.baseURL + '/api/v1') return client .post('/statuses', form) @@ -268,7 +276,7 @@ const actions: ActionTree = { .post('/media', formData) .then(res => { commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, false) - if (res.data.type === 'unknown') throw new UnknownTypeError() + if (res.data.type === 'unknown') throw new NewTootUnknownType() commit(MUTATION_TYPES.APPEND_ATTACHED_MEDIAS, res.data) return res.data }) @@ -329,6 +337,3 @@ const NewToot: Module = { } export default NewToot - -class AuthenticationError {} -class UnknownTypeError {}