Add version filter controls to Changelog and Versions pages (#378)

This commit is contained in:
Prospector
2022-03-05 08:18:44 -08:00
committed by GitHub
parent 2df93c2ceb
commit 1c07a74bef
6 changed files with 324 additions and 195 deletions

View File

@@ -55,7 +55,7 @@ export default {
color: var(--color-badge-green-text); color: var(--color-badge-green-text);
.circle { .circle {
background-color: var(--color-badge-green-bg); background-color: var(--color-brand);
} }
} }

View File

@@ -0,0 +1,132 @@
<template>
<div class="card search-controls">
<Multiselect
v-model="selectedLoaders"
:options="getValidLoaders()"
:multiple="true"
:searchable="true"
:show-no-results="false"
:close-on-select="false"
:clear-search-on-select="false"
:show-labels="false"
:selectable="() => selectedLoaders.length <= 6"
placeholder="Filter loaders..."
@input="updateVersionFilters()"
></Multiselect>
<Multiselect
v-model="selectedGameVersions"
:options="getValidVersions()"
:multiple="true"
:searchable="true"
:show-no-results="false"
:close-on-select="false"
:clear-search-on-select="false"
:show-labels="false"
:selectable="() => selectedGameVersions.length <= 6"
placeholder="Filter versions..."
@input="updateVersionFilters()"
></Multiselect>
<Checkbox
v-model="showSnapshots"
label="Include snapshots"
description="Include snapshots"
style="margin-bottom: 0.5rem"
:border="false"
/>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
import Checkbox from '~/components/ui/Checkbox'
export default {
name: 'VersionFilterControl',
components: {
Multiselect,
Checkbox,
},
props: {
versions: {
type: Array,
required: true,
},
},
data() {
return {
query: '',
showSnapshots: false,
cachedValidVersions: null,
cachedValidLoaders: null,
selectedGameVersions: [],
selectedLoaders: [],
}
},
methods: {
getValidVersions() {
if (!this.cachedValidVersions) {
const temp = new Set()
for (const version of this.versions) {
version.game_versions.forEach((v) => {
temp.add(v)
})
}
this.cachedValidVersions = Array.from(temp)
this.cachedValidVersions.sort().reverse()
}
return this.cachedValidVersions
},
getValidLoaders() {
if (!this.cachedValidLoaders) {
const temp = new Set()
for (const version of this.versions) {
version.loaders.forEach((v) => {
temp.add(v)
})
}
this.cachedValidLoaders = Array.from(temp)
this.cachedValidLoaders.sort()
}
return this.cachedValidLoaders
},
updateVersionFilters() {
const temp = this.versions.filter(
(projectVersion) =>
(this.selectedGameVersions.length === 0 ||
this.selectedGameVersions.some((gameVersion) =>
projectVersion.game_versions.includes(gameVersion)
)) &&
(this.selectedLoaders.length === 0 ||
this.selectedLoaders.some((loader) =>
projectVersion.loaders.includes(loader)
))
)
this.$emit('updateVersions', temp)
},
},
}
</script>
<style lang="scss">
.search-controls {
display: flex;
flex-direction: row;
gap: var(--spacing-card-md);
align-items: center;
flex-wrap: wrap;
.multiselect {
flex-grow: 1;
max-height: unset;
width: fit-content;
input::placeholder {
color: var(--color-text);
}
}
.checkbox-outer {
margin-bottom: 0 !important;
}
}
</style>

View File

@@ -1,87 +1,77 @@
<template> <template>
<div class="content card"> <div class="content">
<ThisOrThat class="filters" v-model="filterMode" :items="filters" /> <VersionFilterControl
<div class="card"
v-for="version in versions.filter((x) => x.loaders.includes(filterMode))" :versions="versions"
:key="version.id" @updateVersions="updateVersions"
> />
<div class="version-header"> <div class="card">
<span :class="'circle ' + version.version_type" /> <div v-for="version in filteredVersions" :key="version.id">
<div class="version-header-text"> <div class="version-header">
<h2 class="name"> <span :class="'circle ' + version.version_type" />
<nuxt-link <div class="version-header-text">
:to="`/${project.project_type}/${ <h2 class="name">
project.slug ? project.slug : project.id <nuxt-link
}/version/${encodeURIComponent(version.version_number)}`" :to="`/${project.project_type}/${
>{{ version.name }}</nuxt-link project.slug ? project.slug : project.id
}/version/${encodeURIComponent(version.version_number)}`"
>{{ version.name }}</nuxt-link
>
</h2>
<span v-if="members.find((x) => x.user.id === version.author_id)">
by
<nuxt-link
class="text-link"
:to="
'/user/' +
members.find((x) => x.user.id === version.author_id).user
.username
"
>{{
members.find((x) => x.user.id === version.author_id).user
.username
}}</nuxt-link
>
</span>
<span>
on
{{ $dayjs(version.date_published).format('MMM D, YYYY') }}</span
> >
</h2> </div>
<span v-if="members.find((x) => x.user.id === version.author_id)"> <a
by :href="$parent.findPrimary(version).url"
<nuxt-link class="iconified-button download"
class="text-link" :title="`Download ${version.name}`"
:to="
'/user/' +
members.find((x) => x.user.id === version.author_id).user
.username
"
>{{
members.find((x) => x.user.id === version.author_id).user
.username
}}</nuxt-link
>
</span>
<span>
on {{ $dayjs(version.date_published).format('MMM D, YYYY') }}</span
> >
<DownloadIcon aria-hidden="true" />
Download
</a>
</div> </div>
<a <div
:href="$parent.findPrimary(version).url" v-highlightjs
class="iconified-button download" :class="'markdown-body ' + version.version_type"
:title="`Download ${version.name}`" v-html="
> version.changelog
<DownloadIcon aria-hidden="true" /> ? $xss($md.render(version.changelog))
Download : 'No changelog specified.'
</a> "
/>
</div> </div>
<div
v-highlightjs
:class="'markdown-body ' + version.version_type"
v-html="
version.changelog
? $xss($md.render(version.changelog))
: 'No changelog specified.'
"
/>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import DownloadIcon from '~/assets/images/utils/download.svg?inline' import DownloadIcon from '~/assets/images/utils/download.svg?inline'
import ThisOrThat from '~/components/ui/ThisOrThat' import VersionFilterControl from '~/components/ui/VersionFilterControl'
export default { export default {
components: { components: {
DownloadIcon, DownloadIcon,
ThisOrThat, VersionFilterControl,
}, },
data() { data() {
return { return {
filters: [], filteredVersions: this.versions,
filterMode: '',
}
},
fetch() {
for (const version of this.versions) {
for (const loader of version.loaders) {
if (!this.filters.includes(loader)) {
this.filters.push(loader)
}
if (this.filterMode === '') {
this.filterMode = loader
}
}
} }
}, },
auth: false, auth: false,
@@ -105,18 +95,15 @@ export default {
}, },
}, },
}, },
methods: {
updateVersions(updatedVersions) {
this.filteredVersions = updatedVersions
},
},
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.content {
max-width: calc(100% - (2 * var(--spacing-card-lg)));
}
.filters {
margin-bottom: 1rem;
}
.version-header { .version-header {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -45,7 +45,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="buttons"> <div class="card buttons">
<button <button
v-if="currentMember" v-if="currentMember"
class="iconified-button" class="iconified-button"
@@ -495,13 +495,7 @@ export default {
display: flex; display: flex;
button { button {
background-color: var(--color-raised-bg);
margin-right: 0.5rem; margin-right: 0.5rem;
margin-bottom: var(--spacing-card-md);
&:hover {
background-color: var(--color-button-bg);
}
&.brand-button-colors { &.brand-button-colors {
background-color: var(--color-brand); background-color: var(--color-brand);

View File

@@ -1,124 +1,134 @@
<template> <template>
<div class="content card"> <div class="content">
<nuxt-link <div class="card" v-if="currentMember">
v-if="currentMember" <nuxt-link to="version/create" class="iconified-button new-version">
to="version/create" <UploadIcon />
class="iconified-button new-version" Upload
> </nuxt-link>
<UploadIcon /> </div>
Upload <VersionFilterControl
</nuxt-link> class="card"
<table> :versions="versions"
<thead> @updateVersions="updateVersions"
<tr> />
<th role="presentation"></th> <div class="card">
<th>Version</th> <table>
<th>Supports</th> <thead>
<th>Stats</th> <tr>
</tr> <th role="presentation"></th>
</thead> <th>Version</th>
<tbody> <th>Supports</th>
<tr v-for="version in versions" :key="version.id"> <th>Stats</th>
<td> </tr>
<a </thead>
:href="$parent.findPrimary(version).url" <tbody>
class="download-button" <tr v-for="version in filteredVersions" :key="version.id">
:title="`Download ${version.name}`" <td>
> <a
<DownloadIcon aria-hidden="true" /> :href="$parent.findPrimary(version).url"
</a> class="download-button"
</td> :class="version.version_type"
<td> :title="`Download ${version.name}`"
<div class="info"> >
<div class="top"> <DownloadIcon aria-hidden="true" />
<nuxt-link </a>
:to="`/${project.project_type}/${ </td>
project.slug ? project.slug : project.id <td>
}/version/${encodeURIComponent(version.version_number)}`" <div class="info">
> <div class="top">
{{ version.name }} <nuxt-link
</nuxt-link> :to="`/${project.project_type}/${
project.slug ? project.slug : project.id
}/version/${encodeURIComponent(version.version_number)}`"
>
{{ version.name }}
</nuxt-link>
</div>
<div class="bottom">
<VersionBadge
v-if="version.version_type === 'release'"
type="release"
color="green"
/>
<VersionBadge
v-else-if="version.version_type === 'beta'"
type="beta"
color="yellow"
/>
<VersionBadge
v-else-if="version.version_type === 'alpha'"
type="alpha"
color="red"
/>
<span class="divider" />
<span class="version_number">{{
version.version_number
}}</span>
</div>
<div class="mobile-info">
<p>
{{
version.loaders
.map((x) => x.charAt(0).toUpperCase() + x.slice(1))
.join(', ') +
' ' +
$formatVersion(version.game_versions)
}}
</p>
<p></p>
<p>
<strong>{{ $formatNumber(version.downloads) }}</strong>
downloads
</p>
<p>
Published on
<strong>{{
$dayjs(version.date_published).format('MMM D, YYYY')
}}</strong>
</p>
</div>
</div> </div>
<div class="bottom"> </td>
<VersionBadge <td>
v-if="version.version_type === 'release'" <p>
type="release" {{
color="green" version.loaders
/> .map((x) => x.charAt(0).toUpperCase() + x.slice(1))
<VersionBadge .join(', ')
v-else-if="version.version_type === 'beta'" }}
type="beta" </p>
color="yellow" <p>{{ $formatVersion(version.game_versions) }}</p>
/> </td>
<VersionBadge <td>
v-else-if="version.version_type === 'alpha'" <p>
type="alpha" <span>{{ $formatNumber(version.downloads) }}</span>
color="red" downloads
/> </p>
<span class="divider" /> <p>
<span class="version_number">{{ version.version_number }}</span> Published on
</div> <span>{{
<div class="mobile-info"> $dayjs(version.date_published).format('MMM D, YYYY')
<p> }}</span>
{{ </p>
version.loaders </td>
.map((x) => x.charAt(0).toUpperCase() + x.slice(1)) </tr>
.join(', ') + </tbody>
' ' + </table>
$formatVersion(version.game_versions) </div>
}}
</p>
<p></p>
<p>
<strong>{{ $formatNumber(version.downloads) }}</strong>
downloads
</p>
<p>
Published on
<strong>{{
$dayjs(version.date_published).format('MMM D, YYYY')
}}</strong>
</p>
</div>
</div>
</td>
<td>
<p>
{{
version.loaders
.map((x) => x.charAt(0).toUpperCase() + x.slice(1))
.join(', ')
}}
</p>
<p>{{ $formatVersion(version.game_versions) }}</p>
</td>
<td>
<p>
<span>{{ $formatNumber(version.downloads) }}</span>
downloads
</p>
<p>
Published on
<span>{{
$dayjs(version.date_published).format('MMM D, YYYY')
}}</span>
</p>
</td>
</tr>
</tbody>
</table>
</div> </div>
</template> </template>
<script> <script>
import UploadIcon from '~/assets/images/utils/upload.svg?inline' import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import DownloadIcon from '~/assets/images/utils/download.svg?inline' import DownloadIcon from '~/assets/images/utils/download.svg?inline'
import VersionBadge from '~/components/ui/Badge' import VersionBadge from '~/components/ui/Badge'
import VersionFilterControl from '~/components/ui/VersionFilterControl'
export default { export default {
components: { components: {
UploadIcon, UploadIcon,
DownloadIcon, DownloadIcon,
VersionBadge, VersionBadge,
VersionFilterControl,
}, },
auth: false, auth: false,
props: { props: {
@@ -141,14 +151,20 @@ export default {
}, },
}, },
}, },
data() {
return {
filteredVersions: this.versions,
}
},
methods: {
updateVersions(updatedVersions) {
this.filteredVersions = updatedVersions
},
},
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.content {
max-width: calc(100% - (2 * var(--spacing-card-lg)));
}
.new-version { .new-version {
max-width: 5.25rem; max-width: 5.25rem;
} }

View File

@@ -142,7 +142,7 @@
<section class="normal-page__content"> <section class="normal-page__content">
<div class="card search-controls"> <div class="card search-controls">
<div class="iconified-input"> <div class="iconified-input">
<label class="hidden" for="search">Search Mods</label> <label class="hidden" for="search">Search</label>
<SearchIcon aria-hidden="true" /> <SearchIcon aria-hidden="true" />
<input <input
id="search" id="search"