From 860a7d1eeb6a8c14dff812f65fc5901d292213b0 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sat, 8 Feb 2020 15:11:12 +0900 Subject: [PATCH] wip --- locales/ja-JP.yml | 10 +- src/client/app.vue | 5 + src/client/pages/my-groups/group.vue | 194 +++++++++++++++++++++++++++ src/client/pages/my-groups/index.vue | 99 ++++++++++++++ src/client/pages/my-lists/index.vue | 2 +- src/client/pages/my-lists/list.vue | 50 +++++-- src/client/router.ts | 2 + 7 files changed, 350 insertions(+), 12 deletions(-) create mode 100644 src/client/pages/my-groups/group.vue create mode 100644 src/client/pages/my-groups/index.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1ac8d6f49b..7f2c2a2d95 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -81,8 +81,6 @@ manageLists: "リストの管理" error: "問題が発生しました" retry: "再試行" enterListName: "リスト名を入力" -renameList: "リスト名を変更" -deleteList: "リストを削除" privacy: "プライバシー" makeFollowManuallyApprove: "フォローを承認制にする" defaultNoteVisibility: "デフォルトの公開範囲" @@ -362,6 +360,14 @@ markAsReadAllTalkMessages: "すべてのトークを既読にする" help: "ヘルプ" inputMessageHere: "ここにメッセージを入力" close: "閉じる" +groups: "グループ" +createGroup: "グループを作成" +ownedGroups: "所有グループ" +joinedGroups: "参加しているグループ" +invite: "招待" +invites: "招待" +groupName: "グループ名" +members: "メンバー" _2fa: alreadyRegistered: "既に設定は完了しています。" diff --git a/src/client/app.vue b/src/client/app.vue index e260d3070c..081f4f22d3 100644 --- a/src/client/app.vue +++ b/src/client/app.vue @@ -409,6 +409,11 @@ export default Vue.extend({ text: this.$t('lists'), to: '/my/lists', icon: faListUl, + }, { + type: 'link', + text: this.$t('groups'), + to: '/my/groups', + icon: faUsers, }, { type: 'link', text: this.$t('antennas'), diff --git a/src/client/pages/my-groups/group.vue b/src/client/pages/my-groups/group.vue new file mode 100644 index 0000000000..67f5f9754f --- /dev/null +++ b/src/client/pages/my-groups/group.vue @@ -0,0 +1,194 @@ +<template> +<div class="mk-group-page"> + <portal to="icon"><fa :icon="faUsers"/></portal> + <portal to="title">{{ group.name }}</portal> + + <transition name="zoom" mode="out-in"> + <div v-if="group" class="_card"> + <div class="_content"> + <mk-button inline @click="renameGroup()">{{ $t('rename') }}</mk-button> + <mk-button inline @click="deleteGroup()">{{ $t('delete') }}</mk-button> + </div> + </div> + </transition> + + <transition name="zoom" mode="out-in"> + <div v-if="group" class="_card members"> + <div class="_title">{{ $t('members') }}</div> + <div class="_content"> + <div class="users"> + <div class="user" v-for="user in users" :key="user.id"> + <mk-avatar :user="user" class="avatar"/> + <div class="body"> + <mk-user-name :user="user" class="name"/> + <mk-acct :user="user" class="acct"/> + </div> + <div class="action"> + <button class="_button" @click="removeUser(user)"><fa :icon="faTimes"/></button> + </div> + </div> + </div> + </div> + <div class="_footer"> + <mk-button inline @click="invite()">{{ $t('invite') }}</mk-button> + </div> + </div> + </transition> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faTimes, faUsers } from '@fortawesome/free-solid-svg-icons'; +import i18n from '../../i18n'; +import Progress from '../../scripts/loading'; +import MkButton from '../../components/ui/button.vue'; +import MkUserSelect from '../../components/user-select.vue'; + +export default Vue.extend({ + i18n, + + metaInfo() { + return { + title: this.group ? `${this.group.name} | ${this.$t('manageGroups')}` : this.$t('manageGroups') + }; + }, + + components: { + MkButton + }, + + data() { + return { + group: null, + users: [], + faTimes, faUsers + }; + }, + + watch: { + $route: 'fetch' + }, + + created() { + this.fetch(); + }, + + methods: { + fetch() { + Progress.start(); + this.$root.api('users/groups/show', { + groupId: this.$route.params.group + }).then(group => { + this.group = group; + this.$root.api('users/show', { + userIds: this.group.userIds + }).then(users => { + this.users = users; + Progress.done(); + }); + }); + }, + + invite() { + this.$root.new(MkUserSelect, {}).$once('selected', user => { + this.$root.api('users/groups/invite', { + groupId: this.group.id, + userId: user.id + }).then(() => { + this.$root.dialog({ + type: 'success', + iconOnly: true, autoClose: true + }); + }).catch(e => { + this.$root.dialog({ + type: 'error', + text: e + }); + }); + }); + }, + + removeUser(user) { + this.$root.api('users/groups/pull', { + groupId: this.group.id, + userId: user.id + }).then(() => { + this.users = this.users.filter(x => x.id !== user.id); + }); + }, + + async renameGroup() { + const { canceled, result: name } = await this.$root.dialog({ + title: this.$t('groupName'), + input: { + default: this.group.name + } + }); + if (canceled) return; + + await this.$root.api('users/groups/update', { + groupId: this.group.id, + name: name + }); + + this.group.name = name; + }, + + async deleteGroup() { + const { canceled } = await this.$root.dialog({ + type: 'warning', + text: this.$t('removeAreYouSure', { x: this.group.name }), + showCancelButton: true + }); + if (canceled) return; + + await this.$root.api('users/groups/delete', { + groupId: this.group.id + }); + this.$root.dialog({ + type: 'success', + iconOnly: true, autoClose: true + }); + this.$router.push('/my/groups'); + } + } +}); +</script> + +<style lang="scss" scoped> +.mk-group-page { + > .members { + > ._content { + max-height: 400px; + overflow: auto; + + > .users { + > .user { + display: flex; + align-items: center; + + > .avatar { + width: 50px; + height: 50px; + } + + > .body { + flex: 1; + padding: 8px; + + > .name { + display: block; + font-weight: bold; + } + + > .acct { + opacity: 0.5; + } + } + } + } + } + } +} +</style> diff --git a/src/client/pages/my-groups/index.vue b/src/client/pages/my-groups/index.vue new file mode 100644 index 0000000000..733c481aa6 --- /dev/null +++ b/src/client/pages/my-groups/index.vue @@ -0,0 +1,99 @@ +<template> +<div class=""> + <portal to="icon"><fa :icon="faUsers"/></portal> + <portal to="title">{{ $t('groups') }}</portal> + + <mk-button @click="create" primary style="margin: 0 auto var(--margin) auto;"><fa :icon="faPlus"/> {{ $t('createGroup') }}</mk-button> + + <mk-container :body-togglable="true"> + <template #header><fa :icon="faUsers"/> {{ $t('ownedGroups') }}</template> + <mk-pagination :pagination="ownedPagination" #default="{items}" ref="owned"> + <div class="" v-for="group in items" :key="group.id"> + <router-link :to="`/my/groups/${ group.id }`">{{ group.name }}</router-link> + </div> + </mk-pagination> + </mk-container> + + <mk-container :body-togglable="true"> + <template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template> + <mk-pagination :pagination="invitePagination" #default="{items}"> + <div class="" v-for="invite in items" :key="invite.id"> + <div class="name">{{ invite.group.name }}</div> + <x-avatars :user-ids="invite.group.userIds" style="margin-top:8px;"/> + <ui-horizon-group> + <ui-button @click="acceptInvite(invite)"><fa :icon="faCheck"/> {{ $t('accept') }}</ui-button> + <ui-button @click="rejectInvite(invite)"><fa :icon="faBan"/> {{ $t('reject') }}</ui-button> + </ui-horizon-group> + </div> + </mk-pagination> + </mk-container> + + <mk-container :body-togglable="true"> + <template #header><fa :icon="faUsers"/> {{ $t('joinedGroups') }}</template> + <mk-pagination :pagination="joinedPagination" #default="{items}"> + <div class="" v-for="group in items" :key="group.id"> + <div>{{ group.name }}</div> + </div> + </mk-pagination> + </mk-container> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faUsers, faPlus, faEnvelopeOpenText } from '@fortawesome/free-solid-svg-icons'; +import MkPagination from '../../components/ui/pagination.vue'; +import MkButton from '../../components/ui/button.vue'; +import MkContainer from '../../components/ui/container.vue'; + +export default Vue.extend({ + metaInfo() { + return { + title: this.$t('groups') as string, + }; + }, + + components: { + MkPagination, + MkButton, + MkContainer, + }, + + data() { + return { + ownedPagination: { + endpoint: 'users/groups/owned', + limit: 10, + }, + joinedPagination: { + endpoint: 'users/groups/joined', + limit: 10, + }, + invitePagination: { + endpoint: 'i/user-group-invites', + limit: 10, + }, + faUsers, faPlus, faEnvelopeOpenText + }; + }, + + methods: { + async create() { + const { canceled, result: name } = await this.$root.dialog({ + title: this.$t('groupName'), + input: true + }); + if (canceled) return; + await this.$root.api('users/groups/create', { name: name }); + this.$refs.owned.reload(); + this.$root.dialog({ + type: 'success', + iconOnly: true, autoClose: true + }); + }, + } +}); +</script> + +<style lang="scss" scoped> +</style> diff --git a/src/client/pages/my-lists/index.vue b/src/client/pages/my-lists/index.vue index 505998209e..c3f6d9c774 100644 --- a/src/client/pages/my-lists/index.vue +++ b/src/client/pages/my-lists/index.vue @@ -62,7 +62,7 @@ export default Vue.extend({ <style lang="scss" scoped> .qkcjvfiv { > .add { - margin: 0 auto 16px auto; + margin: 0 auto var(--margin) auto; } > .lists { diff --git a/src/client/pages/my-lists/list.vue b/src/client/pages/my-lists/list.vue index ded79b89c7..cf85a80ccb 100644 --- a/src/client/pages/my-lists/list.vue +++ b/src/client/pages/my-lists/list.vue @@ -1,11 +1,23 @@ <template> <div class="mk-list-page"> + <portal to="icon"><fa :icon="faListUl"/></portal> + <portal to="title">{{ list.name }}</portal> + <transition name="zoom" mode="out-in"> - <div v-if="list" :key="list.id" class="_card list"> - <div class="_title">{{ list.name }}</div> + <div v-if="list" class="_card"> + <div class="_content"> + <mk-button inline @click="renameList()">{{ $t('rename') }}</mk-button> + <mk-button inline @click="deleteList()">{{ $t('delete') }}</mk-button> + </div> + </div> + </transition> + + <transition name="zoom" mode="out-in"> + <div v-if="list" class="_card members"> + <div class="_title">{{ $t('members') }}</div> <div class="_content"> <div class="users"> - <div class="user" v-for="(user, i) in users" :key="user.id"> + <div class="user" v-for="user in users" :key="user.id"> <mk-avatar :user="user" class="avatar"/> <div class="body"> <mk-user-name :user="user" class="name"/> @@ -18,8 +30,7 @@ </div> </div> <div class="_footer"> - <mk-button inline @click="renameList()">{{ $t('renameList') }}</mk-button> - <mk-button inline @click="deleteList()">{{ $t('deleteList') }}</mk-button> + <mk-button inline @click="addUser()">{{ $t('addUser') }}</mk-button> </div> </div> </transition> @@ -28,10 +39,11 @@ <script lang="ts"> import Vue from 'vue'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { faTimes, faListUl } from '@fortawesome/free-solid-svg-icons'; import i18n from '../../i18n'; import Progress from '../../scripts/loading'; import MkButton from '../../components/ui/button.vue'; +import MkUserSelect from '../../components/user-select.vue'; export default Vue.extend({ i18n, @@ -50,7 +62,7 @@ export default Vue.extend({ return { list: null, users: [], - faTimes + faTimes, faListUl }; }, @@ -78,6 +90,26 @@ export default Vue.extend({ }); }, + addUser() { + this.$root.new(MkUserSelect, {}).$once('selected', user => { + this.$root.api('users/lists/push', { + listId: this.list.id, + userId: user.id + }).then(() => { + this.users.push(user); + this.$root.dialog({ + type: 'success', + iconOnly: true, autoClose: true + }); + }).catch(e => { + this.$root.dialog({ + type: 'error', + text: e + }); + }); + }); + }, + removeUser(user) { this.$root.api('users/lists/pull', { listId: this.list.id, @@ -107,7 +139,7 @@ export default Vue.extend({ async deleteList() { const { canceled } = await this.$root.dialog({ type: 'warning', - text: this.$t('deleteListConfirm', { list: this.list.name }), + text: this.$t('removeAreYouSure', { x: this.list.name }), showCancelButton: true }); if (canceled) return; @@ -127,7 +159,7 @@ export default Vue.extend({ <style lang="scss" scoped> .mk-list-page { - > .list { + > .members { > ._content { max-height: 400px; overflow: auto; diff --git a/src/client/router.ts b/src/client/router.ts index f32673f432..949eb1ccbd 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -40,6 +40,8 @@ export const router = new VueRouter({ { path: '/my/follow-requests', component: page('follow-requests') }, { path: '/my/lists', component: page('my-lists/index') }, { path: '/my/lists/:list', component: page('my-lists/list') }, + { path: '/my/groups', component: page('my-groups/index') }, + { path: '/my/groups/:group', component: page('my-groups/group') }, { path: '/my/antennas', component: page('my-antennas/index') }, { path: '/instance', component: page('instance/index') }, { path: '/instance/emojis', component: page('instance/emojis') },