channel manage page layout

This commit is contained in:
MiniDigger 2021-03-20 16:50:18 +01:00
parent 1a6f46e670
commit 494c982559
9 changed files with 134 additions and 39 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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: {

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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;

View File

@ -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));

View File

@ -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