Skip to content

Commit

Permalink
一旦スタンプの作成ができるように
Browse files Browse the repository at this point in the history
  • Loading branch information
mehm8128 committed Nov 5, 2023
1 parent fc57879 commit 4753f8d
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 133 deletions.
2 changes: 1 addition & 1 deletion src/components/Modal/Common/ModalFrame.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const { clearModal } = useModalStore()
display: flex;
flex-flow: column nowrap;
max-height: 480px;
max-width: 440px;
max-width: 600px;
width: #{calc(100% - 32px)};
border-radius: 4px;
overflow: hidden;
Expand Down
8 changes: 8 additions & 0 deletions src/components/Modal/ModalContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
? currentState.userId
: undefined
"
:file="
currentState.type === 'settings-stamp-create'
? currentState.file
: undefined
"
/>
</div>
</transition>
Expand All @@ -63,6 +68,7 @@ import GroupCreateModal from './GroupCreateModal/GroupCreateModal.vue'
import GroupMemberEditModal from './GroupMemberEditModal/GroupMemberEditModal.vue'
import GroupAdminAddModal from './GroupAdminAddModal/GroupAdminAddModal.vue'
import GroupMemberAddModal from './GroupMemberAddModal/GroupMemberAddModal.vue'
import StampCreateModal from './StampCreateModal/StampCreateModal.vue'
const { shouldShowModal, currentState } = useModalStore()
Expand Down Expand Up @@ -96,6 +102,8 @@ const component = computed(() => {
return GroupAdminAddModal
case 'group-member-add':
return GroupMemberAddModal
case 'settings-stamp-create':
return StampCreateModal
}
// eslint-disable-next-line no-console
console.error('Unexpected modal type:', currentState.value)
Expand Down
170 changes: 170 additions & 0 deletions src/components/Modal/StampCreateModal/StampCreateModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<template>
<modal-frame title="新規スタンプ登録" icon-name="">
<div :class="$style.container">
<image-upload v-model="stampImage" :class="$style.form" />
<div :class="$style.rightContainer">
<form-input
v-model="newStampName"
label="スタンプ名"
prefix=":"
suffix=":"
:max-length="32"
:class="$style.form"
/>
<label :class="$style.label">所有者</label>
<p :class="$style.creator">@ {{ detail?.name }}</p>
</div>
</div>
<p :class="$style.note">{{ cropperNote }}</p>
<div :class="$style.buttonContainer">
<form-button label="キャンセル" color="secondary" @click="cancel" />
<form-button
label="登録する"
:disabled="!isCreateEnabled"
:loading="isCreating"
:class="$style.form"
@click="createStamp"
/>
</div>
</modal-frame>
</template>

<script lang="ts" setup>
import { ref, computed, watch, type Ref } from 'vue'
import apis, { formatResizeError } from '/@/lib/apis'
import { isValidStampName } from '/@/lib/validate'
import { useToastStore } from '/@/store/ui/toast'
import { imageSize } from '/@/components/Settings/StampTab/imageSize'
import ImageUpload from '/@/components/Settings/ImageUpload.vue'
import FormInput from '/@/components/UI/FormInput.vue'
import FormButton from '/@/components/UI/FormButton.vue'
import ModalFrame from '../Common/ModalFrame.vue'
import { useMeStore } from '/@/store/domain/me'
import { useModalStore } from '/@/store/ui/modal'
const props = defineProps<{
file: File
}>()
const { detail } = useMeStore()
const { clearModal } = useModalStore()
const stampImage = ref(props.file)
const newStampName = ref('')
const isCreateEnabled = computed(() => isValidStampName(newStampName.value))
watch(
() => stampImage.value,
newImageData => {
if (!newStampName.value && newImageData && newImageData.name) {
newStampName.value = trimExt(newImageData.name)
}
}
)
const isGif = stampImage.value.type === 'image/gif'
const cropperNote = computed(() =>
isGif ? 'GIFは切り抜きできません' : '画像の位置・サイズを編集できます'
)
/**
* 拡張子を削る
*/
const trimExt = (filename: string) => filename.replace(/\.[^.]+$/, '')
const useStampCreate = (
newStampName: Ref<string>,
stampImage: Ref<File | undefined>
) => {
const { addSuccessToast, addErrorToast } = useToastStore()
const isCreating = ref(false)
const createStamp = async () => {
if (!stampImage.value) return
try {
const size = await imageSize(stampImage.value)
if (size.height !== size.width) {
addErrorToast('画像が正方形ではありません。編集してください')
return
}
} catch (e) {
// eslint-disable-next-line no-console
console.error('スタンプの作成に失敗しました', e)
addErrorToast(formatResizeError(e, 'スタンプの作成に失敗しました'))
}
try {
isCreating.value = true
await apis.createStamp(newStampName.value, stampImage.value)
newStampName.value = ''
stampImage.value = undefined
addSuccessToast('スタンプを登録しました')
} catch (e) {
// eslint-disable-next-line no-console
console.error('スタンプの作成に失敗しました', e)
addErrorToast(formatResizeError(e, 'スタンプの作成に失敗しました'))
}
isCreating.value = false
clearModal()
}
return { isCreating, createStamp }
}
const { isCreating, createStamp } = useStampCreate(newStampName, stampImage)
const cancel = () => {
clearModal()
}
</script>

<style lang="scss" module>
.subtitle {
a {
pointer-events: none;
}
}
.item {
margin: 16px 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
.form {
margin: 8px 0;
}
.container {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
}
.rightContainer {
width: 100%;
}
.label {
@include color-ui-secondary;
margin-bottom: 16px;
}
.creator {
@include color-ui-primary;
}
.note {
@include color-ui-secondary;
margin-left: 12px;
margin-bottom: 12px;
}
.buttonContainer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
}
</style>
38 changes: 9 additions & 29 deletions src/components/Settings/ImageUpload.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
<template>
<div>
<form-button label="ファイルを選択" @click="selectImage" />
<div v-if="originalImgUrl">
<div :class="$style.cropper" :data-is-rounded="$boolAttr(rounded)">
<img ref="imgEle" :src="originalImgUrl" />
</div>
<p :class="$style.note">{{ cropperNote }}</p>
<form-button label="キャンセル" @click="cancel" />
</div>
<div
v-if="originalImgUrl"
:class="$style.cropper"
:data-is-rounded="$boolAttr(rounded)"
>
<img ref="imgEle" :src="originalImgUrl" />
</div>
</template>

<script lang="ts" setup>
import { ref, watchEffect, shallowRef, onUnmounted } from 'vue'
import Cropper from 'cropperjs'
import FormButton from '/@/components/UI/FormButton.vue'
import 'cropperjs/dist/cropper.css'
import useObjectURL from '/@/composables/dom/useObjectURL'
import { useModelValueSyncer } from '/@/composables/useModelSyncer'
import { useFileSelect } from '/@/composables/dom/useFileSelect'
const props = withDefaults(
defineProps<{
Expand Down Expand Up @@ -51,14 +46,10 @@ const cropperDefaultOptions = {
autoCrop: true,
dragMode: 'move' as const
} as const
const acceptImageType = ['image/jpeg', 'image/png', 'image/gif'].join(',')
const value = useModelValueSyncer(props, emit)
const originalImg = ref<File | undefined>()
const { selectImage } = useFileSelect({ accept: acceptImageType }, files => {
originalImg.value = files[0]
})
const originalImg = ref<File | undefined>(props.modelValue)
const originalImgUrl = useObjectURL(originalImg)
let cropper: Cropper | undefined
Expand Down Expand Up @@ -105,10 +96,6 @@ const updateImgView = () => {
}
}
cropperNote.value = isGif
? 'GIFは切り抜きできません'
: '画像の位置・サイズを編集できます'
if (cropper) cropper.destroy()
cropper = new Cropper(imgEle.value, options)
cropper.replace(originalImgUrl.value ?? '')
Expand All @@ -122,19 +109,15 @@ watchEffect(() => {
}
})
const cancel = () => {
emit('update:modelValue', undefined)
}
onUnmounted(() => {
if (cropper) cropper.destroy()
})
</script>

<style lang="scss" module>
.cropper {
width: 400px;
height: 400px;
width: 200px;
height: 200px;
margin: 12px;
&[data-is-rounded] {
:global(.cropper-view-box),
Expand All @@ -143,7 +126,4 @@ onUnmounted(() => {
}
}
}
.note {
margin: 12px;
}
</style>
Loading

0 comments on commit 4753f8d

Please sign in to comment.