mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-11-21 01:21:54 +08:00
channel manage page layout
This commit is contained in:
parent
1a6f46e670
commit
494c982559
@ -20,7 +20,7 @@ import { Tag } from 'hangar-api';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
@Component
|
||||
export default class DocsPage extends Vue {
|
||||
export default class TagComponent extends Vue {
|
||||
@Prop({ type: String })
|
||||
name!: string;
|
||||
|
||||
|
@ -4,19 +4,19 @@
|
||||
<slot name="activator" :on="on" :attrs="attrs" />
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('channel.new.title') }}</v-card-title>
|
||||
<v-card-title>{{ edit ? $t('channel.modal.titleEdit') : $t('channel.modal.titleNew') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="validForm">
|
||||
<v-text-field
|
||||
v-model.trim="form.name"
|
||||
:label="$t('channel.new.name')"
|
||||
:label="$t('channel.modal.name')"
|
||||
:rules="[
|
||||
$util.$vc.require($t('channel.new.name')),
|
||||
$util.$vc.regex($t('channel.new.name'), validations.project.channels.regex),
|
||||
$util.$vc.require($t('channel.modal.name')),
|
||||
$util.$vc.regex($t('channel.modal.name'), validations.project.channels.regex),
|
||||
$util.$vc.maxLength(validations.project.channels.max),
|
||||
]"
|
||||
/>
|
||||
<v-card-subtitle class="pa-0 text-center">{{ $t('channel.new.color') }}</v-card-subtitle>
|
||||
<v-card-subtitle class="pa-0 text-center">{{ $t('channel.modal.color') }}</v-card-subtitle>
|
||||
<v-item-group v-model="form.color" mandatory>
|
||||
<v-container>
|
||||
<v-row v-for="(arr, arrIndex) in swatches" :key="arrIndex" justify="center">
|
||||
@ -32,7 +32,7 @@
|
||||
@click="toggle"
|
||||
>
|
||||
<v-fade-transition>
|
||||
<v-icon v-show="active" small class="ma-auto"> mdi-checkbox-marked-circle </v-icon>
|
||||
<v-icon v-show="active" small class="ma-auto"> mdi-checkbox-marked-circle</v-icon>
|
||||
</v-fade-transition>
|
||||
</v-card>
|
||||
</v-item>
|
||||
@ -40,25 +40,31 @@
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-item-group>
|
||||
<v-checkbox v-model="form.nonReviewed" :label="$t('channel.new.reviewQueue')" />
|
||||
<v-checkbox v-model="form.nonReviewed" :label="$t('channel.modal.reviewQueue')" />
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-end">
|
||||
<v-btn color="error" text @click="dialog = false">{{ $t('general.close') }}</v-btn>
|
||||
<v-btn color="success" :disabled="!isValid" @click="createChannel">{{ $t('general.create') }}</v-btn>
|
||||
<v-btn color="success" :disabled="!isValid" @click="createChannel">{{ edit ? $t('general.save') : $t('general.create') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, State } from 'nuxt-property-decorator';
|
||||
import { Component, Prop, State } from 'nuxt-property-decorator';
|
||||
import { Color, ProjectChannel } from 'hangar-internal';
|
||||
import { HangarFormModal } from '../mixins';
|
||||
import { RootState } from '~/store';
|
||||
|
||||
@Component
|
||||
export default class NewChannelModal extends HangarFormModal {
|
||||
export default class ChannelModal extends HangarFormModal {
|
||||
@Prop({ default: false })
|
||||
edit!: Boolean;
|
||||
|
||||
@Prop()
|
||||
channel!: ProjectChannel;
|
||||
|
||||
colors: Color[] = [];
|
||||
form: ProjectChannel = {
|
||||
name: '',
|
||||
@ -67,6 +73,10 @@ export default class NewChannelModal extends HangarFormModal {
|
||||
temp: true,
|
||||
};
|
||||
|
||||
mounted() {
|
||||
this.form = { ...this.channel };
|
||||
}
|
||||
|
||||
async fetch() {
|
||||
this.colors = await this.$api.requestInternal<Color[]>('data/channelColors', false).catch<any>(this.$util.handleRequestError);
|
||||
}
|
@ -341,8 +341,9 @@ const msgs: LocaleMessageObject = {
|
||||
platforms: 'Platforms',
|
||||
},
|
||||
channel: {
|
||||
new: {
|
||||
title: 'Add a new channel',
|
||||
modal: {
|
||||
titleNew: 'Add a new channel',
|
||||
titleEdit: 'Edit channel',
|
||||
name: 'Channel Name',
|
||||
color: 'Channel Color',
|
||||
reviewQueue: 'Exclude from moderation review queue?',
|
||||
@ -353,6 +354,18 @@ const msgs: LocaleMessageObject = {
|
||||
duplicateName: 'This project already has a channel with this name',
|
||||
},
|
||||
},
|
||||
manage: {
|
||||
title: 'Release channels',
|
||||
subtitle: 'Release channels represent the state of a plugin release. A project may have up to five release channels.',
|
||||
channelName: 'Channel Name',
|
||||
versionCount: 'Version Count',
|
||||
reviewed: 'Reviewed',
|
||||
edit: 'Edit',
|
||||
trash: 'Trash',
|
||||
editButton: 'Edit',
|
||||
deleteButton: 'Delete',
|
||||
add: 'Add Channel',
|
||||
},
|
||||
},
|
||||
organization: {
|
||||
new: {
|
||||
|
@ -1,13 +1,97 @@
|
||||
<template>
|
||||
<div>{{ $nuxt.$route.name }}</div>
|
||||
<v-col cols="12" md="8" offset-md="2">
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('channel.manage.title') }}</v-card-title>
|
||||
<v-card-subtitle>{{ $t('channel.manage.subtitle') }}</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-simple-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><v-icon>mdi-tag</v-icon>{{ $t('channel.manage.channelName') }}</th>
|
||||
<th><v-icon>mdi-format-list-numbered</v-icon>{{ $t('channel.manage.versionCount') }}</th>
|
||||
<th><v-icon>mdi-file-find</v-icon>{{ $t('channel.manage.reviewed') }}</th>
|
||||
<th><v-icon>mdi-pencil</v-icon>{{ $t('channel.manage.edit') }}</th>
|
||||
<th><v-icon>mdi-delete</v-icon>{{ $t('channel.manage.trash') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="channel in channels" :key="channel.name">
|
||||
<td><Tag :name="channel.name" :color="{ background: channel.color }"></Tag></td>
|
||||
<!-- todo get number of versions in channel -->
|
||||
<td>X</td>
|
||||
<td>
|
||||
<v-icon v-if="channel.nonReviewed">mdi-checkbox-blank-circle-outline</v-icon>
|
||||
<v-icon v-else>mdi-check-circle</v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<ChannelModal :edit="true" :channel="channel" @create="editChannel">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn color="warning" v-bind="attrs" v-on="on">{{ $t('channel.manage.editButton') }}</v-btn>
|
||||
</template>
|
||||
</ChannelModal>
|
||||
</td>
|
||||
<td>
|
||||
<!-- todo we need to properly think about how channel deletion works -->
|
||||
<v-btn color="error" @click="deleteChannel(channel.name)">{{ $t('channel.manage.deleteButton') }}</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-simple-table>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<ChannelModal @create="addChannel">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn v-if="channels.length < validations.project.maxChannelCount" color="primary" v-bind="attrs" v-on="on">
|
||||
{{ $t('channel.manage.add') }}
|
||||
<v-icon right>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</ChannelModal>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'nuxt-property-decorator';
|
||||
import { Component, State } from 'nuxt-property-decorator';
|
||||
import { ProjectChannel } from 'hangar-internal';
|
||||
import { Context } from '@nuxt/types';
|
||||
import { HangarProjectMixin } from '~/components/mixins';
|
||||
import Tag from '~/components/Tag.vue';
|
||||
import ChannelModal from '~/components/modals/ChannelModal.vue';
|
||||
import { RootState } from '~/store';
|
||||
|
||||
// TODO implement ProjectChannelsPage
|
||||
@Component
|
||||
export default class ProjectChannelsPage extends Vue {}
|
||||
@Component({
|
||||
components: { ChannelModal, Tag },
|
||||
})
|
||||
export default class ProjectChannelsPage extends HangarProjectMixin {
|
||||
channels!: ProjectChannel[];
|
||||
|
||||
// TODO editChannel
|
||||
editChannel(name: String) {
|
||||
console.log('edit channel ', name);
|
||||
}
|
||||
|
||||
// TODO deleteChannel
|
||||
deleteChannel(name: String) {
|
||||
console.log('delete channel ', name);
|
||||
}
|
||||
|
||||
// TODO addChannel
|
||||
addChannel(channel: ProjectChannel) {
|
||||
this.channels.push(Object.assign({}, channel));
|
||||
}
|
||||
|
||||
async asyncData({ $api, $util, params }: Context) {
|
||||
const channels = await $api
|
||||
.requestInternal<ProjectChannel[]>(`channels/${params.author}/${params.slug}`, false)
|
||||
.catch<any>($util.handlePageRequestError);
|
||||
return { channels };
|
||||
}
|
||||
|
||||
@State((state: RootState) => state.validations)
|
||||
validations!: RootState['validations'];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -104,15 +104,6 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<!-- TODO I'm not sure the versions page needs to have this? It's got a bunch of stuff with the filters and this stuff is on the docs page -->
|
||||
<!--<v-col md="4" lg="12">
|
||||
<MemberList
|
||||
:can-edit="$perms.canManageSubjectMembers"
|
||||
:manage-url="`/${project.namespace.owner}/${project.namespace.slug}/settings`"
|
||||
:members="project.members"
|
||||
class="sidebar-card"
|
||||
/>
|
||||
</v-col>-->
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
@ -74,14 +74,14 @@
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col class="flex-grow-0 pl-0 pr-4" align-self="center">
|
||||
<NewChannelModal @create="addChannel">
|
||||
<ChannelModal @create="addChannel">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn v-if="channels.length < validations.project.maxChannelCount" color="info" v-bind="attrs" v-on="on">
|
||||
{{ $t('version.new.form.addChannel') }}
|
||||
<v-icon right>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</NewChannelModal>
|
||||
</ChannelModal>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-sheet color="accent darken-1" elevation="1" rounded class="mt-2">
|
||||
@ -179,12 +179,12 @@ import { HangarProjectMixin } from '~/components/mixins';
|
||||
import { ProjectPermission } from '~/utils/perms';
|
||||
import { NamedPermission, Platform } from '~/types/enums';
|
||||
import { MarkdownEditor } from '~/components/markdown';
|
||||
import NewChannelModal from '~/components/modals/NewChannelModal.vue';
|
||||
import ChannelModal from '~/components/modals/ChannelModal.vue';
|
||||
import { RootState } from '~/store';
|
||||
import DependencyTable from '~/components/modals/versions/DependencyTable.vue';
|
||||
|
||||
@Component({
|
||||
components: { DependencyTable, NewChannelModal, MarkdownEditor },
|
||||
components: { DependencyTable, ChannelModal, MarkdownEditor },
|
||||
})
|
||||
@ProjectPermission(NamedPermission.CREATE_VERSION)
|
||||
export default class ProjectVersionsNewPage extends HangarProjectMixin {
|
||||
|
@ -36,7 +36,7 @@ public class PendingVersion {
|
||||
@Validate(SpEL = "@validate.optionalRegex(#root, @hangarConfig.urlRegex)", message = "general.error.invalidUrl")
|
||||
private final String externalUrl;
|
||||
@NotBlank(message = "version.new.error.channel.noName")
|
||||
@Validate(SpEL = "@validate.regex(#root, @hangarConfig.channels.nameRegex)", message = "channel.new.error.invalidName")
|
||||
@Validate(SpEL = "@validate.regex(#root, @hangarConfig.channels.nameRegex)", message = "channel.modal.error.invalidName")
|
||||
private final String channelName;
|
||||
@NotNull(message = "version.new.error.channel.noColor")
|
||||
private final Color channelColor;
|
||||
|
@ -22,20 +22,20 @@ public class ChannelService extends HangarService {
|
||||
|
||||
public ProjectChannelTable createProjectChannel(String name, Color color, long projectId, boolean nonReviewed) {
|
||||
if (!config.channels.isValidChannelName(name)) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.new.error.invalidName");
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.modal.error.invalidName");
|
||||
}
|
||||
|
||||
List<ProjectChannelTable> existingTables = projectChannelsDAO.getProjectChannels(projectId);
|
||||
if (existingTables.size() >= config.projects.getMaxChannels()) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.new.error.maxChannels", config.projects.getMaxChannels());
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.modal.error.maxChannels", config.projects.getMaxChannels());
|
||||
}
|
||||
|
||||
if (existingTables.stream().anyMatch(ch -> ch.getColor() == color)) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.new.error.duplicateColor");
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.modal.error.duplicateColor");
|
||||
}
|
||||
|
||||
if (existingTables.stream().anyMatch(ch -> ch.getName().equalsIgnoreCase(name))) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.new.error.duplicateName");
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "channel.modal.error.duplicateName");
|
||||
}
|
||||
|
||||
return projectChannelsDAO.insert(new ProjectChannelTable(name, color, projectId, nonReviewed));
|
||||
|
@ -51,9 +51,6 @@ hangar:
|
||||
-
|
||||
text: "This is a staging server for testing purposes. Data could be deleted at any time. Please use our production server at (TODO: prod url, lol) for uploading your plugins!"
|
||||
color: "#ff544b"
|
||||
-
|
||||
text: "This is an announcement. It's cool"
|
||||
color: "green"
|
||||
|
||||
sponsors:
|
||||
- name: Beer
|
||||
|
Loading…
Reference in New Issue
Block a user