feat: refactor most of the album components to use store for presentation and actions

This commit is contained in:
Zephyrrus 2020-07-04 03:26:35 +03:00
parent 836a01327d
commit 92be4504cc
12 changed files with 291 additions and 138 deletions

View File

@ -10,6 +10,10 @@ $backgroundAccent: #20222b;
$backgroundAccentLighter: #53555e;
$backgroundLight1: #f5f6f8;
$scheme-main: $background;
$scheme-main-bis: $backgroundAccent;
$scheme-main-ter: $backgroundAccentLighter;
// customize navbar
$navbar-background-color: $backgroundAccent;
$navbar-item-color: #f5f6f8;
@ -47,6 +51,10 @@ $pagination-current-background-color: $base-3;
$pagination-current-border-color: $base-2;
// loading
$loading-background: rgba(0, 0, 0, 0.8);
$loading-background: rgba(40, 40, 40, 0.66);
// dialogs
$modal-card-body-background-color: $background;
$modal-card-head-background-color: $backgroundAccent;
$modal-card-foot-border-top: 1px solid rgba(255, 255, 255, 0.1098);

View File

@ -1,15 +1,16 @@
// Let's first take care of having the customized colors ready.
@import "./_colors.scss";
@import './_colors.scss';
// Bulma/Buefy customization
@import "../../../node_modules/bulma/sass/utilities/_all.sass";
@import '../../../node_modules/bulma/sass/utilities/_all.sass';
$body-size: 14px !default;
$family-primary: 'Nunito', BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
$family-primary: 'Nunito', BlinkMacSystemFont, -apple-system, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
$size-normal: 1rem;
@import "../../../node_modules/bulma/bulma.sass";
@import "../../../node_modules/buefy/src/scss/buefy.scss";
@import '../../../node_modules/bulma/bulma.sass';
@import '../../../node_modules/buefy/src/scss/buefy.scss';
html {
// font-size: 100%;
@ -18,9 +19,9 @@ html {
}
a {
color: #5E81AC;
color: #5e81ac;
&:hover {
color: #81A1C1;
color: #81a1c1;
text-decoration: underline;
}
}
@ -43,10 +44,18 @@ h4 {
}
@for $i from 1 through 10 {
.mt#{$i} { margin-top: $i + em !important; }
.mb#{$i} { margin-bottom: $i + em !important; }
.ml#{$i} { margin-left: $i + em !important; }
.mr#{$i} { margin-right: $i + em !important; }
.mt#{$i} {
margin-top: $i + em !important;
}
.mb#{$i} {
margin-bottom: $i + em !important;
}
.ml#{$i} {
margin-left: $i + em !important;
}
.mr#{$i} {
margin-right: $i + em !important;
}
}
.text-center {
@ -58,8 +67,12 @@ hr {
height: 1px;
}
// Bulma color changes.
.tooltip.is-top.is-primary:before { border-top: 5px solid #20222b; }
.tooltip.is-primary:after { background: #20222b; }
.tooltip.is-top.is-primary:before {
border-top: 5px solid #20222b;
}
.tooltip.is-primary:after {
background: #20222b;
}
div#drag-overlay {
position: fixed;
@ -93,7 +106,6 @@ div#drag-overlay {
}
}
section.hero {
&.dashboard {
// background-color: $backgroundLight1 !important;
@ -103,10 +115,12 @@ section.hero {
}
}
section input, section a.button {
section input,
section a.button {
font-size: 14px !important;
}
section input, section p.control a.button {
section input,
section p.control a.button {
border-left: 0px !important;
border-top: 0px !important;
border-right: 0px !important;
@ -114,13 +128,15 @@ section input, section p.control a.button {
box-shadow: 0 0 0 !important;
}
section p.control a.button { margin-left: 10px !important; }
section p.control a.button {
margin-left: 10px !important;
}
section p.control button {
height: 100%;
font-size: 12px;
}
.switch input[type=checkbox] + .check:before {
.switch input[type='checkbox'] + .check:before {
background: #fbfbfb;
}
@ -128,7 +144,8 @@ section p.control button {
Register and Login forms
*/
section.hero.is-login, section.hero.is-register {
section.hero.is-login,
section.hero.is-register {
a {
font-size: 1.25em;
line-height: 2.5em;
@ -174,18 +191,22 @@ section#register a.is-text {
font-size: 1.25em;
line-height: 2.5em;
}
*/
.modal-card-head, .modal-card-foot {
background: $backgroundLight1;
}
*/
.switch {
margin-top: 5px;
}
.input, .taginput .taginput-container.is-focusable, .textarea, .select select {
.input,
.taginput .taginput-container.is-focusable,
.textarea,
.select select {
border: 2px solid #21252d;
border-radius: .3em !important;
border-radius: 0.3em !important;
background: rgba(0, 0, 0, 0.15);
padding: 1rem;
color: $textColor;
@ -203,9 +224,9 @@ button.button.is-primary {
border: 2px solid #21252d;
color: $textColor;
font-size: 1rem;
border-top: 0;
border-left: 0;
border-right: 0;
border-top: 0;
border-left: 0;
border-right: 0;
&:hover {
background-color: $base-2;
}
@ -224,13 +245,16 @@ svg.waves {
user-select: none;
overflow: hidden;
}
div.field-body > div.field { text-align: left; }
div.field-body > div.field {
text-align: left;
}
table.table {
background: $base-2;
color: $textColor;
border: 0;
thead {
th, td {
th,
td {
color: $textColor;
}
}
@ -244,60 +268,55 @@ table.table {
}
}
}
th, td {
th,
td {
border-color: #ffffff1c;
}
}
// vue-bar
.vb > .vb-dragger {
z-index: 5;
width: 12px;
right: 0;
z-index: 5;
width: 12px;
right: 0;
}
.vb > .vb-dragger > .vb-dragger-styler {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: rotate3d(0,0,0,0);
transform: rotate3d(0,0,0,0);
-webkit-transition:
background-color 100ms ease-out,
margin 100ms ease-out,
height 100ms ease-out;
transition:
background-color 100ms ease-out,
margin 100ms ease-out,
height 100ms ease-out;
background-color: $backgroundAccent;
margin: 5px 5px 5px 0;
border-radius: 20px;
height: calc(100% - 10px);
display: block;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: rotate3d(0, 0, 0, 0);
transform: rotate3d(0, 0, 0, 0);
-webkit-transition: background-color 100ms ease-out, margin 100ms ease-out, height 100ms ease-out;
transition: background-color 100ms ease-out, margin 100ms ease-out, height 100ms ease-out;
background-color: $backgroundAccent;
margin: 5px 5px 5px 0;
border-radius: 20px;
height: calc(100% - 10px);
display: block;
}
.vb.vb-scrolling-phantom > .vb-dragger > .vb-dragger-styler {
background-color: $backgroundAccentLighter;
background-color: $backgroundAccentLighter;
}
.vb > .vb-dragger:hover > .vb-dragger-styler {
background-color: $backgroundAccentLighter;
margin: 0px;
height: 100%;
background-color: $backgroundAccentLighter;
margin: 0px;
height: 100%;
}
.vb.vb-dragging > .vb-dragger > .vb-dragger-styler {
background-color: $backgroundAccentLighter;
margin: 0px;
height: 100%;
background-color: $backgroundAccentLighter;
margin: 0px;
height: 100%;
}
.vb.vb-dragging-phantom > .vb-dragger > .vb-dragger-styler {
background-color: $backgroundAccentLighter;
background-color: $backgroundAccentLighter;
}
.vb-content{
overflow: auto !important
.vb-content {
overflow: auto !important;
}
// helpers
@ -313,7 +332,7 @@ table.table {
height: max-content;
}
.pagination a, .pagination a:hover {
.pagination a,
.pagination a:hover {
text-decoration: none;
}

View File

@ -25,13 +25,13 @@
label="Allow download"
centered>
<b-switch v-model="props.row.enableDownload"
@input="linkOptionsChanged(props.row)" />
@input="updateLinkOptions(albumId, props.row)" />
</b-table-column>
<b-table-column field="enabled"
numeric>
<button class="button is-danger"
@click="promptDeleteAlbumLink(props.row.identifier)">Delete link</button>
@click="promptDeleteAlbumLink(albumId, props.row.identifier)">Delete link</button>
</b-table-column>
</template>
<template slot="empty">
@ -42,6 +42,7 @@
Nothing here
</div>
</template>
<template slot="footer">
<div class="level is-paddingless">
<div class="level-left">
@ -70,7 +71,7 @@
</template>
<script>
import { mapState } from 'vuex';
import { mapState, mapActions } from 'vuex';
export default {
props: {
@ -90,55 +91,65 @@ export default {
},
computed: mapState(['config']),
methods: {
...mapActions({
deleteAlbumAction: 'albums/deleteAlbum',
deleteAlbumLinkAction: 'albums/deleteLink',
updateLinkOptionsAction: 'albums/updateLinkOptions',
createLinkAction: 'albums/createLink',
alert: 'alert/set'
}),
promptDeleteAlbum(id) {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Are you sure you want to delete this album?',
onConfirm: () => this.deleteAlbum(id)
});
},
async deleteAlbum(id) {
const response = await this.$axios.$delete(`album/${id}`);
this.getAlbums();
return this.$buefy.toast.open(response.message);
},
promptDeleteAlbumLink(identifier) {
promptDeleteAlbumLink(albumId, identifier) {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Are you sure you want to delete this album link?',
onConfirm: () => this.deleteAlbumLink(identifier)
onConfirm: () => this.deleteAlbumLink({ albumId, identifier })
});
},
async deleteAlbumLink(identifier) {
const response = await this.$axios.$delete(`album/link/delete/${identifier}`);
return this.$buefy.toast.open(response.message);
},
async linkOptionsChanged(link) {
const response = await this.$axios.$post(`album/link/edit`,
{
identifier: link.identifier,
enableDownload: link.enableDownload,
enabled: link.enabled
});
this.$buefy.toast.open(response.message);
},
async createLink(album) {
album.isCreatingLink = true;
// Since we actually want to change the state even if the call fails, use a try catch
async deleteAlbum(id) {
try {
const response = await this.$axios.$post(`album/link/new`,
{ albumId: album.id });
this.$buefy.toast.open(response.message);
album.links.push({
identifier: response.identifier,
views: 0,
enabled: true,
enableDownload: true,
expiresAt: null
});
} catch (error) {
//
const response = await this.deleteAlbumAction(id);
this.alert({ text: response.message, error: false });
} catch (e) {
this.alert({ text: e.message, error: true });
}
},
async deleteAlbumLink(id) {
try {
const response = await this.deleteAlbumLinkAction(id);
this.alert({ text: response.message, error: false });
} catch (e) {
this.alert({ text: e.message, error: true });
}
},
async createLink(albumId) {
this.isCreatingLink = true;
try {
const response = await this.createLinkAction(albumId);
this.alert({ text: response.message, error: false });
} catch (e) {
this.alert({ text: e.message, error: true });
} finally {
album.isCreatingLink = false;
this.isCreatingLink = false;
}
},
async updateLinkOptions(albumId, linkOpts) {
try {
const response = await this.updateLinkOptionsAction({ albumId, linkOpts });
this.alert({ text: response.message, error: false });
} catch (e) {
this.alert({ text: e.message, error: true });
}
}
}
};

View File

@ -14,7 +14,9 @@
<h4>
<router-link :to="`/dashboard/albums/${album.id}`">{{ album.name }}</router-link>
</h4>
<span>Updated <timeago :since="album.editedAt" /></span>
<span>
Created <span class="is-inline has-text-weight-semibold"><timeago :since="album.createdAt" /></span>
</span>
<span>{{ album.fileCount || 0 }} files</span>
</div>
<div class="latest is-hidden-mobile">
@ -40,7 +42,7 @@
</div>
<AlbumDetails v-if="isExpanded"
:details="getDetails"
:details="getDetails(album.id)"
:albumId="album.id" />
</div>
</template>
@ -62,13 +64,10 @@ export default {
computed: {
...mapGetters({
isExpandedGetter: 'albums/isExpanded',
getDetailsGetter: 'albums/getDetails'
getDetails: 'albums/getDetails'
}),
isExpanded() {
return this.isExpandedGetter(this.album.id);
},
getDetails() {
return this.getDetailsGetter(this.album.id);
}
},
methods: {

View File

@ -29,12 +29,6 @@
<Waterfall v-if="showWaterfall"
:gutterWidth="10"
:gutterHeight="4">
<input v-if="enableSearch"
v-model="searchTerm"
type="text"
placeholder="Search..."
@input="search()"
@keyup.enter="search()">
<WaterfallItem v-for="(item, index) in gridFiles"
:key="item.id"
:width="width"
@ -167,7 +161,7 @@
</template>
<template slot="footer">
<div class="has-text-right has-text-default">
{{ files.length }} files
Showing {{ files.length }} files ({{ total }} total)
</div>
</template>
</b-table>
@ -213,6 +207,10 @@ export default {
type: Array,
default: () => []
},
total: {
type: Number,
default: 0
},
fixed: {
type: Boolean,
default: false

View File

@ -130,6 +130,7 @@ export default {
methods: {
promptDisableUser() {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Are you sure you want to disable the account of the user that uploaded this file?',
onConfirm: () => this.disableUser()
});
@ -142,6 +143,7 @@ export default {
},
promptBanIP() {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Are you sure you want to ban the IP this file was uploaded from?',
onConfirm: () => this.banIP()
});

View File

@ -85,6 +85,7 @@ export default {
methods: {
promptDisableUser() {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Are you sure you want to disable the account of the user that uploaded this file?',
onConfirm: () => this.disableUser()
});

View File

@ -10,25 +10,53 @@
<Sidebar />
</div>
<div class="column">
<h2 class="subtitle">Files</h2>
<nav class="level">
<div class="level-left">
<div class="level-item">
<h2 class="subtitle">Files</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<b-field>
<b-input
placeholder="Search"
type="search"/>
<p class="control">
<button
outlined
class="button is-primary">
Search
</button>
</p>
</b-field>
</div>
</div>
</nav>
<hr>
<Grid v-if="files.length"
:files="files" />
<b-pagination
v-if="count > perPage"
:total="count"
:per-page="perPage"
:current.sync="current"
class="pagination"
icon-prev="icon-interface-arrow-left"
icon-next="icon-interface-arrow-right"
icon-pack="icon"
aria-next-label="Next page"
aria-previous-label="Previous page"
aria-page-label="Page"
aria-current-label="Current page" />
:files="files"
:total="count">
<template v-slot:pagination>
<b-pagination
v-if="count > perPage"
:total="count"
:per-page="perPage"
:current.sync="current"
range-before="2"
range-after="2"
class="pagination-slot"
icon-prev="icon-interface-arrow-left"
icon-next="icon-interface-arrow-right"
icon-pack="icon"
aria-next-label="Next page"
aria-previous-label="Previous page"
aria-page-label="Page"
aria-current-label="Current page" />
</template>
</Grid>
</div>
</div>
</div>

View File

@ -18,7 +18,8 @@
@keyup.enter.native="createAlbum" />
<p class="control">
<button outlined
class="button is-primary"
class="button is-black"
:disabled="isCreatingAlbum"
@click="createAlbum">Create album</button>
</p>
</b-field>
@ -37,7 +38,7 @@
</template>
<script>
import { mapState } from 'vuex';
import { mapState, mapActions } from 'vuex';
import Sidebar from '~/components/sidebar/Sidebar.vue';
import AlbumEntry from '~/components/album/AlbumEntry.vue';
@ -51,7 +52,8 @@ export default {
}],
data() {
return {
newAlbumName: null
newAlbumName: null,
isCreatingAlbum: false
};
},
computed: mapState(['config', 'albums']),
@ -59,13 +61,23 @@ export default {
return { title: 'Uploads' };
},
methods: {
...mapActions({
'alert': 'alert/set'
}),
async createAlbum() {
if (!this.newAlbumName || this.newAlbumName === '') return;
const response = await this.$axios.$post(`album/new`,
{ name: this.newAlbumName });
this.newAlbumName = null;
this.$buefy.toast.open(response.message);
this.getAlbums();
this.isCreatingAlbum = true;
try {
const response = await this.$store.dispatch('albums/createAlbum', this.newAlbumName);
this.alert({ text: response.message, error: false });
} catch (e) {
this.alert({ text: e.message, error: true });
} finally {
this.isCreatingAlbum = false;
this.newAlbumName = null;
}
}
}
};

View File

@ -228,12 +228,14 @@ export default {
methods: {
promptDeleteTag(id) {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Are you sure you want to delete this tag?',
onConfirm: () => this.promptPurgeTag(id)
});
},
promptPurgeTag(id) {
this.$buefy.dialog.confirm({
type: 'is-danger',
message: 'Would you like to delete every file associated with this tag?',
cancelText: 'No',
confirmText: 'Yes',

0
src/site/store/album.js Normal file
View File

View File

@ -1,4 +1,6 @@
/* eslint-disable no-shadow */
import Vue from 'vue';
export const state = () => ({
list: [],
isListLoading: false,
@ -18,10 +20,13 @@ export const actions = {
const response = await this.$axios.$get(`albums/mini`);
commit('setAlbums', response.albums);
return response;
} catch (e) {
dispatch('alert/set', { text: e.message, error: true }, { root: true });
}
},
async fetchDetails({ commit }, albumId) {
const response = await this.$axios.$get(`album/${albumId}/links`);
@ -31,6 +36,52 @@ export const actions = {
links: response.links
}
});
return response;
},
async createAlbum({ commit }, name) {
const response = await this.$axios.$post(`album/new`, { name });
commit('addAlbum', response.data);
return response;
},
async deleteAlbum({ commit }, albumId) {
const response = await this.$axios.$delete(`album/${albumId}`);
commit('removeAlbum', albumId);
return response;
},
async createLink({ commit }, albumId) {
const response = await this.$axios.$post(`album/link/new`, { albumId });
commit('addAlbumLink', { albumId, data: response.data });
return response;
},
async updateLinkOptions({ commit }, { albumId, linkOpts }) {
const response = await this.$axios.$post(`album/link/edit`, {
identifier: linkOpts.identifier,
enableDownload: linkOpts.enableDownload,
enabled: linkOpts.enabled
});
commit('updateAlbumLinkOpts', { albumId, linkOpts: response.data });
return response;
},
async deleteLink({ commit }, { albumId, identifier }) {
const response = await this.$axios.$delete(`album/link/delete/${identifier}`);
commit('removeAlbumLink', { albumId, identifier });
return response;
}
};
@ -42,8 +93,30 @@ export const mutations = {
state.list = albums;
state.isLoading = false;
},
addAlbum(state, album) {
state.list.unshift(album);
},
removeAlbum(state, albumId) {
// state.list = state.list.filter(({ id }) => id !== albumId);
const foundIndex = state.list.findIndex(({ id }) => id === albumId);
state.list.splice(foundIndex, 1);
},
setDetails(state, { id, details }) {
state.albumDetails[id] = details;
Vue.set(state.albumDetails, id, details);
},
addAlbumLink(state, { albumId, data }) {
state.albumDetails[albumId].links.push(data);
},
updateAlbumLinkOpts(state, { albumId, linkOpts }) {
const foundIndex = state.albumDetails[albumId].links.findIndex(
({ identifier }) => identifier === linkOpts.identifier
);
const link = state.albumDetails[albumId].links[foundIndex];
state.albumDetails[albumId].links[foundIndex] = { ...link, ...linkOpts };
},
removeAlbumLink(state, { albumId, identifier }) {
const foundIndex = state.albumDetails[albumId].links.findIndex(({ identifier: id }) => id === identifier);
state.albumDetails[albumId].links.splice(foundIndex, 1);
},
toggleExpandedState(state, id) {
const foundIndex = state.expandedAlbums.indexOf(id);