diff options
| -rw-r--r-- | src/site/assets/styles/_colors.scss | 10 | ||||
| -rw-r--r-- | src/site/assets/styles/style.scss | 139 | ||||
| -rw-r--r-- | src/site/components/album/AlbumDetails.vue | 85 | ||||
| -rw-r--r-- | src/site/components/album/AlbumEntry.vue | 11 | ||||
| -rw-r--r-- | src/site/components/grid/Grid.vue | 12 | ||||
| -rw-r--r-- | src/site/pages/dashboard/admin/file/_id.vue | 2 | ||||
| -rw-r--r-- | src/site/pages/dashboard/admin/user/_id.vue | 1 | ||||
| -rw-r--r-- | src/site/pages/dashboard/albums/_id.vue | 60 | ||||
| -rw-r--r-- | src/site/pages/dashboard/albums/index.vue | 28 | ||||
| -rw-r--r-- | src/site/pages/dashboard/tags/index.vue | 2 | ||||
| -rw-r--r-- | src/site/store/album.js | 0 | ||||
| -rw-r--r-- | src/site/store/albums.js | 75 |
12 files changed, 289 insertions, 136 deletions
diff --git a/src/site/assets/styles/_colors.scss b/src/site/assets/styles/_colors.scss index b8861d2..6693d8a 100644 --- a/src/site/assets/styles/_colors.scss +++ b/src/site/assets/styles/_colors.scss @@ -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); diff --git a/src/site/assets/styles/style.scss b/src/site/assets/styles/style.scss index 69d15d3..6c939b5 100644 --- a/src/site/assets/styles/style.scss +++ b/src/site/assets/styles/style.scss @@ -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; } - diff --git a/src/site/components/album/AlbumDetails.vue b/src/site/components/album/AlbumDetails.vue index a02fe55..b646cb0 100644 --- a/src/site/components/album/AlbumDetails.vue +++ b/src/site/components/album/AlbumDetails.vue @@ -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 deleteAlbum(id) { + try { + const response = await this.deleteAlbumAction(id); + + this.alert({ text: response.message, error: false }); + } catch (e) { + this.alert({ text: e.message, error: true }); + } }, - 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 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(album) { - album.isCreatingLink = true; - // Since we actually want to change the state even if the call fails, use a try catch + async createLink(albumId) { + this.isCreatingLink = true; 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.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 }); + } } } }; diff --git a/src/site/components/album/AlbumEntry.vue b/src/site/components/album/AlbumEntry.vue index 4d23d6c..28e434a 100644 --- a/src/site/components/album/AlbumEntry.vue +++ b/src/site/components/album/AlbumEntry.vue @@ -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: { diff --git a/src/site/components/grid/Grid.vue b/src/site/components/grid/Grid.vue index a06eabf..956dd28 100644 --- a/src/site/components/grid/Grid.vue +++ b/src/site/components/grid/Grid.vue @@ -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 diff --git a/src/site/pages/dashboard/admin/file/_id.vue b/src/site/pages/dashboard/admin/file/_id.vue index 5821292..5853770 100644 --- a/src/site/pages/dashboard/admin/file/_id.vue +++ b/src/site/pages/dashboard/admin/file/_id.vue @@ -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() }); diff --git a/src/site/pages/dashboard/admin/user/_id.vue b/src/site/pages/dashboard/admin/user/_id.vue index 2a56c34..8c73037 100644 --- a/src/site/pages/dashboard/admin/user/_id.vue +++ b/src/site/pages/dashboard/admin/user/_id.vue @@ -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() }); diff --git a/src/site/pages/dashboard/albums/_id.vue b/src/site/pages/dashboard/albums/_id.vue index 47e7057..c082b63 100644 --- a/src/site/pages/dashboard/albums/_id.vue +++ b/src/site/pages/dashboard/albums/_id.vue @@ -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> diff --git a/src/site/pages/dashboard/albums/index.vue b/src/site/pages/dashboard/albums/index.vue index 2a54ab8..a010254 100644 --- a/src/site/pages/dashboard/albums/index.vue +++ b/src/site/pages/dashboard/albums/index.vue @@ -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; + } } } }; diff --git a/src/site/pages/dashboard/tags/index.vue b/src/site/pages/dashboard/tags/index.vue index 7c295b7..a9476da 100644 --- a/src/site/pages/dashboard/tags/index.vue +++ b/src/site/pages/dashboard/tags/index.vue @@ -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', diff --git a/src/site/store/album.js b/src/site/store/album.js new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/site/store/album.js diff --git a/src/site/store/albums.js b/src/site/store/albums.js index a33181c..d5d45ce 100644 --- a/src/site/store/albums.js +++ b/src/site/store/albums.js @@ -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); |