aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZephyrrus <[email protected]>2020-07-05 04:18:08 +0300
committerZephyrrus <[email protected]>2020-07-05 04:18:08 +0300
commit766e74cc51138b32482f65f0f2647eb9d943103e (patch)
treed675cdaa4dc4a89356009131707b11b511069494
parentfeat: refactor single album page to use vuex (diff)
downloadhost.fuwn.me-766e74cc51138b32482f65f0f2647eb9d943103e.tar.xz
host.fuwn.me-766e74cc51138b32482f65f0f2647eb9d943103e.zip
feat: add video preview on hover to dashboard and apply new linter rules to some of the files
-rw-r--r--src/site/components/footer/Footer.vue62
-rw-r--r--src/site/components/grid/Grid.vue435
-rw-r--r--src/site/components/grid/waterfall/Waterfall.vue56
-rw-r--r--src/site/components/grid/waterfall/WaterfallItem.vue29
-rw-r--r--src/site/constants/alertTypes.js10
-rw-r--r--src/site/plugins/notifier.js7
-rw-r--r--src/site/store/auth.js20
-rw-r--r--src/site/store/images.js4
8 files changed, 342 insertions, 281 deletions
diff --git a/src/site/components/footer/Footer.vue b/src/site/components/footer/Footer.vue
index 44e34f4..19d18f2 100644
--- a/src/site/components/footer/Footer.vue
+++ b/src/site/components/footer/Footer.vue
@@ -1,12 +1,18 @@
<template>
+ <!-- eslint-disable max-len -->
<footer>
- <svg viewBox="0 0 1920 250"
+ <svg
+ viewBox="0 0 1920 250"
class="waves">
- <path d="M1920 250H0V0s126.707 78.536 349.975 80.05c177.852 1.203 362.805-63.874 553.803-63.874 290.517 0 383.458 57.712 603.992 61.408 220.527 3.696 278.059-61.408 412.23-17.239"
+
+ <path
+ d="M1920 250H0V0s126.707 78.536 349.975 80.05c177.852 1.203 362.805-63.874 553.803-63.874 290.517 0 383.458 57.712 603.992 61.408 220.527 3.696 278.059-61.408 412.23-17.239"
class="wave-1" />
- <path d="M1920 144s-467.917 116.857-1027.243-17.294C369.986 1.322 0 45.578 0 45.578V250h1920V144z"
+ <path
+ d="M1920 144s-467.917 116.857-1027.243-17.294C369.986 1.322 0 45.578 0 45.578V250h1920V144z"
class="wave-2" />
- <path d="M0 195.553s208.547-75.581 701.325-20.768c376.707 41.908 520.834-67.962 722.545-67.962 222.926 0 311.553 83.523 496.129 86.394V250H0v-54.447z"
+ <path
+ d="M0 195.553s208.547-75.581 701.325-20.768c376.707 41.908 520.834-67.962 722.545-67.962 222.926 0 311.553 83.523 496.129 86.394V250H0v-54.447z"
class="wave-3" />
</svg>
<div>
@@ -15,7 +21,8 @@
<div class="column is-narrow">
<h4>lolisafe</h4>
<span>© 2017-2020
- <a href="https://github.com/pitu"
+ <a
+ href="https://github.com/pitu"
class="no-block">Pitu</a>
</span><br>
<span>v{{ version }}</span>
@@ -24,20 +31,33 @@
<div class="columns is-gapless">
<div class="column" />
<div class="column">
- <nuxt-link to="/">Home</nuxt-link>
- <nuxt-link to="/faq">FAQ</nuxt-link>
+ <nuxt-link to="/">
+ Home
+ </nuxt-link>
+ <nuxt-link to="/faq">
+ FAQ
+ </nuxt-link>
</div>
<div class="column">
- <nuxt-link to="/dashboard">Dashboard</nuxt-link>
- <nuxt-link to="/dashboard">Files</nuxt-link>
- <nuxt-link to="/dashboard/albums">Albums</nuxt-link>
- <nuxt-link to="/dashboard/account">Account</nuxt-link>
+ <nuxt-link to="/dashboard">
+ Dashboard
+ </nuxt-link>
+ <nuxt-link to="/dashboard">
+ Files
+ </nuxt-link>
+ <nuxt-link to="/dashboard/albums">
+ Albums
+ </nuxt-link>
+ <nuxt-link to="/dashboard/account">
+ Account
+ </nuxt-link>
</div>
<div class="column">
<a href="https://github.com/weebdev/lolisafe">GitHub</a>
</div>
<div class="column">
- <a v-if="loggedIn"
+ <a
+ v-if="loggedIn"
@click="createShareXThing">ShareX Config</a>
<a href="https://chrome.google.com/webstore/detail/lolisafe-uploader/enkkmplljfjppcdaancckgilmgoiofnj">Chrome Extension</a>
</div>
@@ -48,7 +68,10 @@
</div>
</footer>
</template>
+
<script>
+/* eslint-disable no-restricted-globals */
+
import { mapState, mapGetters } from 'vuex';
import { saveAs } from 'file-saver';
@@ -56,19 +79,21 @@ export default {
computed: {
...mapGetters({ loggedIn: 'auth/isLoggedIn' }),
...mapState({
- version: state => state.config.version
- })
+ version: (state) => state.config.version,
+ serviceName: (state) => state.config.serviceName,
+ token: (state) => state.auth.token,
+ }),
},
methods: {
createShareXThing() {
const sharexFile = `{
- "Name": "${this.$store.state.config.serviceName}",
+ "Name": "${this.serviceName}",
"DestinationType": "ImageUploader, FileUploader",
"RequestType": "POST",
"RequestURL": "${location.origin}/api/upload",
"FileFormName": "files[]",
"Headers": {
- "authorization": "Bearer ${this.$store.state.token}",
+ "authorization": "Bearer ${this.token}",
"accept": "application/vnd.lolisafe.json"
},
"ResponseType": "Text",
@@ -77,10 +102,11 @@ export default {
}`;
const sharexBlob = new Blob([sharexFile], { type: 'application/octet-binary' });
saveAs(sharexBlob, `${location.hostname}.sxcu`);
- }
- }
+ },
+ },
};
</script>
+
<style lang="scss" scoped>
@import '~/assets/styles/_colors.scss';
footer {
diff --git a/src/site/components/grid/Grid.vue b/src/site/components/grid/Grid.vue
index 0fff307..d4fe067 100644
--- a/src/site/components/grid/Grid.vue
+++ b/src/site/components/grid/Grid.vue
@@ -6,18 +6,13 @@
<slot name="pagination" />
</div>
</div>
- <div v-if="enableToolbar"
- class="level-right toolbar">
+ <div v-if="enableToolbar" class="level-right toolbar">
<div class="level-item">
<div class="block">
- <b-radio v-model="showList"
- name="name"
- :native-value="true">
+ <b-radio v-model="showList" name="name" :native-value="true">
List
</b-radio>
- <b-radio v-model="showList"
- name="name"
- :native-value="false">
+ <b-radio v-model="showList" name="name" :native-value="false">
Grid
</b-radio>
</div>
@@ -26,66 +21,71 @@
</nav>
<template v-if="!showList">
- <Waterfall v-if="showWaterfall"
+ <Waterfall
+ v-if="showWaterfall"
:gutterWidth="10"
:gutterHeight="4">
- <WaterfallItem v-for="(item, index) in gridFiles"
+ <WaterfallItem
+ v-for="(item, index) in gridFiles"
:key="item.id"
:width="width"
- :video="!!item.preview"
move-class="item-move">
<template v-if="isPublic">
- <a :href="`${item.url}`"
- target="_blank">
+ <a
+ :href="`${item.url}`"
+ class="preview-container"
+ target="_blank"
+ @mouseenter.self.stop.prevent="item.preview && mouseOver(item.id)"
+ @mouseleave.self.stop.prevent="item.preview && mouseOut(item.id)">
+
<img :src="item.thumb ? item.thumb : blank">
- <span v-if="!item.thumb && item.name"
- class="extension">{{ item.name.split('.').pop() }}</span>
+ <div v-if="item.preview && isHovered(item.id)" class="preview">
+ <video ref="video" class="preview" autoplay loop muted>
+ <source :src="item.preview" type="video/mp4">
+ </video>
+ </div>
+
+ <span v-if="!item.thumb && item.name" class="extension">{{
+ item.name.split('.').pop()
+ }}</span>
</a>
</template>
<template v-else>
- <img v-if="!item.preview" :class="{'hidden': item.preview}"
- :src="item.thumb ? item.thumb : blank">
- <video v-if="item.preview" autoplay loop>
- <source :src="item.preview" type="video/mp4" />
- </video>
- <span v-if="!item.thumb && item.name"
- class="extension">{{ item.name.split('.').pop() }}</span>
- <div v-if="!isPublic"
+ <img :src="item.thumb ? item.thumb : blank">
+ <div v-if="item.preview && isHovered(item.id)" class="preview">
+ <video ref="video" class="preview" autoplay loop muted>
+ <source :src="item.preview" type="video/mp4">
+ </video>
+ </div>
+
+ <span v-if="!item.thumb && item.name" class="extension">{{ item.name.split('.').pop() }}</span>
+ <div
+ v-if="!isPublic"
:class="{ fixed }"
- class="actions">
- <b-tooltip label="Link"
- position="is-top">
- <a :href="`${item.url}`"
- target="_blank"
- class="btn">
+ class="actions"
+ @mouseenter.self.stop.prevent="item.preview && mouseOver(item.id)"
+ @mouseleave.self.stop.prevent="item.preview && mouseOut(item.id)">
+ <b-tooltip label="Link" position="is-top">
+ <a :href="`${item.url}`" target="_blank" class="btn">
<i class="icon-web-code" />
</a>
</b-tooltip>
- <b-tooltip label="Tags"
- position="is-top">
- <a class="btn"
- @click="manageTags(item)">
+ <b-tooltip label="Tags" position="is-top">
+ <a class="btn" @click="manageTags(item)">
<i class="icon-ecommerce-tag-c" />
</a>
</b-tooltip>
- <b-tooltip label="Albums"
- position="is-top">
- <a class="btn"
- @click="openAlbumModal(item)">
+ <b-tooltip label="Albums" position="is-top">
+ <a class="btn" @click="openAlbumModal(item)">
<i class="icon-interface-window" />
</a>
- </b-tooltip>
- <b-tooltip label="Delete"
- position="is-top">
- <a class="btn"
- @click="deleteFile(item, index)">
+ </b-tooltip>
+ <b-tooltip label="Delete" position="is-top">
+ <a class="btn" @click="deleteFile(item, index)">
<i class="icon-editorial-trash-a-l" />
</a>
</b-tooltip>
- <b-tooltip v-if="user && user.isAdmin"
- label="More info"
- position="is-top"
- class="more">
+ <b-tooltip v-if="user && user.isAdmin" label="More info" position="is-top" class="more">
<nuxt-link :to="`/dashboard/admin/file/${item.id}`">
<i class="icon-interface-more" />
</nuxt-link>
@@ -96,58 +96,42 @@
</Waterfall>
</template>
<div v-else>
- <b-table
- :data="gridFiles || []"
- :mobile-cards="true">
+ <b-table :data="gridFiles || []" :mobile-cards="true">
<template slot-scope="props">
<template v-if="!props.row.hideFromList">
- <b-table-column field="url"
- label="URL">
- <a :href="props.row.url"
- target="_blank">{{ props.row.url }}</a>
+ <b-table-column field="url" label="URL">
+ <a :href="props.row.url" target="_blank">{{ props.row.url }}</a>
</b-table-column>
- <b-table-column field="albums"
- label="Albums"
- centered>
+ <b-table-column field="albums" label="Albums" centered>
<template v-for="(album, index) in props.row.albums">
- <nuxt-link :key="index"
- :to="`/dashboard/albums/${album.id}`">
+ <nuxt-link :key="index" :to="`/dashboard/albums/${album.id}`">
{{ album.name }}
</nuxt-link>
- <template v-if="index < props.row.albums.length - 1">, </template>
+ <template v-if="index < props.row.albums.length - 1">
+ ,
+ </template>
</template>
{{ props.row.username }}
</b-table-column>
- <b-table-column field="uploaded"
- label="Uploaded"
- centered>
+ <b-table-column field="uploaded" label="Uploaded" centered>
<span><timeago :since="props.row.createdAt" /></span>
</b-table-column>
- <b-table-column field="purge"
- centered>
- <b-tooltip label="Albums"
- position="is-top">
- <a class="btn"
- @click="openAlbumModal(props.row)">
+ <b-table-column field="purge" centered>
+ <b-tooltip label="Albums" position="is-top">
+ <a class="btn" @click="openAlbumModal(props.row)">
<i class="icon-interface-window" />
</a>
</b-tooltip>
- <b-tooltip label="Delete"
- position="is-top"
- class="is-danger">
- <a class="is-danger"
- @click="deleteFile(props.row)">
+ <b-tooltip label="Delete" position="is-top" class="is-danger">
+ <a class="is-danger" @click="deleteFile(props.row)">
<i class="icon-editorial-trash-a-l" />
</a>
</b-tooltip>
- <b-tooltip v-if="user && user.isAdmin"
- label="More info"
- position="is-top"
- class="more">
+ <b-tooltip v-if="user && user.isAdmin" label="More info" position="is-top" class="more">
<nuxt-link :to="`/dashboard/admin/file/${props.row.id}`">
<i class="icon-interface-more" />
</nuxt-link>
@@ -170,21 +154,23 @@
</template>
</b-table>
</div>
- <b-modal :active.sync="isAlbumsModalActive"
- :width="640"
- scroll="keep">
+ <b-modal :active.sync="isAlbumsModalActive" :width="640" scroll="keep">
<div class="card albumsModal">
<div class="card-content">
<div class="content">
- <h3 class="subtitle">Select the albums this file should be a part of</h3>
+ <h3 class="subtitle">
+ Select the albums this file should be a part of
+ </h3>
<hr>
+
<div class="albums-container">
- <div v-for="(album, index) in albums"
- :key="index"
- class="album">
+ <div v-for="(album, index) in albums" :key="index" class="album">
<div class="field">
- <b-checkbox :value="isAlbumSelected(album.id)"
- @input="albumCheckboxClicked($event, album.id)">{{ album.name }}</b-checkbox>
+ <b-checkbox
+ :value="isAlbumSelected(album.id)"
+ @input="albumCheckboxClicked($event, album.id)">
+ {{ album.name }}
+ </b-checkbox>
</div>
</div>
</div>
@@ -204,37 +190,37 @@ import WaterfallItem from './waterfall/WaterfallItem.vue';
export default {
components: {
Waterfall,
- WaterfallItem
+ WaterfallItem,
},
props: {
files: {
type: Array,
- default: () => []
+ default: () => [],
},
total: {
type: Number,
- default: 0
+ default: 0,
},
fixed: {
type: Boolean,
- default: false
+ default: false,
},
isPublic: {
type: Boolean,
- default: false
+ default: false,
},
width: {
type: Number,
- default: 150
+ default: 150,
},
enableSearch: {
type: Boolean,
- default: true
+ default: true,
},
enableToolbar: {
type: Boolean,
- default: true
- }
+ default: true,
+ },
},
data() {
return {
@@ -242,18 +228,20 @@ export default {
searchTerm: null,
showList: false,
albums: [],
+ hoveredItems: [],
isAlbumsModalActive: false,
showingModalForFile: null,
filesOffsetWaterfall: 0,
filesOffsetEndWaterfall: 50,
- filesPerPageWaterfall: 50
+ filesPerPageWaterfall: 50,
};
},
computed: {
...mapState({
- user: state => state.auth.user
+ user: (state) => state.auth.user,
}),
blank() {
+ // eslint-disable-next-line global-require, import/no-unresolved
return require('@/assets/images/blank.png');
},
gridFiles() {
@@ -262,12 +250,7 @@ export default {
},
methods: {
async search() {
- const data = await this.$search.do(this.searchTerm, [
- 'name',
- 'original',
- 'type',
- 'albums:name'
- ]);
+ const data = await this.$search.do(this.searchTerm, ['name', 'original', 'type', 'albums:name']);
console.log('> Search result data', data);
},
deleteFile(file, index) {
@@ -290,13 +273,13 @@ export default {
});
}
return this.$buefy.toast.open(response.message);
- }
+ },
});
},
isAlbumSelected(id) {
- if (!this.showingModalForFile) return;
- const found = this.showingModalForFile.albums.find(el => el.id === id);
- return found && found.id ? true : false;
+ if (!this.showingModalForFile) return false;
+ const found = this.showingModalForFile.albums.find((el) => el.id === id);
+ return !!(found && found.id);
},
async openAlbumModal(file) {
this.showingModalForFile = file;
@@ -311,7 +294,7 @@ export default {
async albumCheckboxClicked(value, id) {
const response = await this.$axios.$post(`file/album/${value ? 'add' : 'del'}`, {
albumId: id,
- fileId: this.showingModalForFile.id
+ fileId: this.showingModalForFile.id,
});
this.$buefy.toast.open(response.message);
@@ -319,134 +302,166 @@ export default {
this.$parent.getFiles();
},
async getAlbums() {
- const response = await this.$axios.$get(`albums/dropdown`);
+ const response = await this.$axios.$get('albums/dropdown');
this.albums = response.albums;
this.$forceUpdate();
},
- mouseOverTest() {
- console.log('aaaaaa');
- }
- }
+ mouseOver(id) {
+ console.log('in', id);
+ const foundIndex = this.hoveredItems.indexOf(id);
+ if (foundIndex > -1) return;
+ this.hoveredItems.push(id);
+ /// XXX: THIS IS NOT OK!
+ this.$nextTick(() => {
+ this.$refs.video.forEach((e) => e.play().catch(() => {}));
+ });
+ },
+ mouseOut(id) {
+ console.log('out', id);
+ const foundIndex = this.hoveredItems.indexOf(id);
+ if (foundIndex > -1) this.hoveredItems.splice(foundIndex, 1);
+ },
+ isHovered(id) {
+ return this.hoveredItems.includes(id);
+ },
+ },
};
</script>
<style lang="scss" scoped>
- @import '~/assets/styles/_colors.scss';
- .item-move {
- transition: all .25s cubic-bezier(.55,0,.1,1);
- }
+@import '~/assets/styles/_colors.scss';
+.item-move {
+ transition: all 0.25s cubic-bezier(0.55, 0, 0.1, 1);
+}
- div.toolbar {
- padding: 1rem;
+div.toolbar {
+ padding: 1rem;
- .block {
- text-align: right;
- }
+ .block {
+ text-align: right;
}
+}
- span.extension {
- position: absolute;
- width: 100%;
- height: 100%;
- z-index: 0;
- top: 0;
- left: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 2rem;
- pointer-events: none;
- opacity: .75;
- max-width: 150px;
- }
+span.extension {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+ top: 0;
+ left: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2rem;
+ pointer-events: none;
+ opacity: 0.75;
+ max-width: 150px;
+}
- div.actions {
- opacity: 0;
- -webkit-transition: opacity 0.1s linear;
- -moz-transition: opacity 0.1s linear;
- -ms-transition: opacity 0.1s linear;
- -o-transition: opacity 0.1s linear;
- transition: opacity 0.1s linear;
- position: absolute;
- top: 0px;
- left: 0px;
- width: 100%;
- height: calc(100% - 6px);
- background: rgba(0, 0, 0, 0.5);
- display: flex;
- justify-content: center;
- align-items: center;
-
- span {
- padding: 3px;
- &.more {
- position: absolute;
- top: 0;
- right: 0;
- }
+div.preview {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: calc(100% - 6px);
+ overflow: hidden;
+}
- &:nth-child(1), &:nth-child(2) {
- align-items: flex-end;
- }
+.preview-container {
+ display: inline-block;
+}
- &:nth-child(1), &:nth-child(3) {
- justify-content: flex-end;
- }
+div.actions {
+ opacity: 0;
+ -webkit-transition: opacity 0.1s linear;
+ -moz-transition: opacity 0.1s linear;
+ -ms-transition: opacity 0.1s linear;
+ -o-transition: opacity 0.1s linear;
+ transition: opacity 0.1s linear;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: calc(100% - 6px);
+ // background: rgba(0, 0, 0, 0.5);
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.5) 0px, rgba(0, 0, 0, 0) 60px),
+ linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0px, rgba(0, 0, 0, 0) 45px);
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+
+ span {
+ padding: 3px;
+
+ &.more {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+
+ &:nth-child(1),
+ &:nth-child(2) {
+ align-items: flex-end;
+ padding-bottom: 10px;
+ }
+
+ &:nth-child(3),
+ &:nth-child(4) {
+ justify-content: flex-end;
+ padding-bottom: 10px;
+ }
- a {
+ a {
+ width: 30px;
+ height: 30px;
+ color: white;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ &.btn:before {
+ content: '';
width: 30px;
height: 30px;
- color: white;
- justify-content: center;
- align-items: center;
- display: flex;
- &.btn:before {
- content: '';
- width: 30px;
- height: 30px;
- border: 1px solid white;
- border-radius: 50%;
- position: absolute;
- }
+ border: 1px solid white;
+ border-radius: 50%;
+ position: absolute;
}
}
+ }
- &.fixed {
- position: relative;
- opacity: 1;
- background: none;
-
- a {
- width: auto;
- height: auto;
- color: $defaultTextColor;
- &:before {
- display: none;
- }
- }
+ &.fixed {
+ position: relative;
+ opacity: 1;
+ background: none;
+ a {
+ width: auto;
+ height: auto;
+ color: $defaultTextColor;
+ &:before {
+ display: none;
+ }
}
}
+}
- .albums-container {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- .album {
- flex-basis: 33%;
- text-align: left;
- }
+.albums-container {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ .album {
+ flex-basis: 33%;
+ text-align: left;
}
+}
- img.hidden {
- display: none;
- }
-</style>
+.hidden {
+ display: none;
+}
-<style lang="scss">
- .waterfall-item:hover {
- div.actions {
- opacity: 1
- }
+.waterfall-item:hover {
+ div.actions {
+ opacity: 1;
}
+}
</style>
diff --git a/src/site/components/grid/waterfall/Waterfall.vue b/src/site/components/grid/waterfall/Waterfall.vue
index af1bd72..cccc3ac 100644
--- a/src/site/components/grid/waterfall/Waterfall.vue
+++ b/src/site/components/grid/waterfall/Waterfall.vue
@@ -9,8 +9,6 @@
</div>
</template>
<script>
-// import {quickSort, getMinIndex, _, sum} from './util'
-
const quickSort = (arr, type) => {
const left = [];
const right = [];
@@ -25,10 +23,10 @@ const quickSort = (arr, type) => {
right.push(arr[i]);
}
}
- return quickSort(left, type).concat(povis, quickSort(right, type))
+ return quickSort(left, type).concat(povis, quickSort(right, type));
};
-const getMinIndex = arr => {
+const getMinIndex = (arr) => {
let pos = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[pos] > arr[i]) {
@@ -44,49 +42,53 @@ const _ = {
},
off(el, type, func, capture = false) {
el.removeEventListener(type, func, capture);
- }
+ },
};
-const sum = arr => arr.reduce((sum, val) => sum + val);
+const sum = (arr) => arr.reduce((acc, val) => acc + val, 0);
+
export default {
name: 'Waterfall',
props: {
gutterWidth: {
type: Number,
- default: 0
+ default: 0,
},
gutterHeight: {
type: Number,
- default: 0
+ default: 0,
},
resizable: {
type: Boolean,
- default: true
+ default: true,
},
align: {
type: String,
- default: 'center'
+ default: 'center',
},
fixWidth: {
- type: Number
+ type: Number,
+ default: 0,
},
minCol: {
type: Number,
- default: 1
+ default: 1,
},
maxCol: {
- type: Number
+ type: Number,
+ default: 0,
},
percent: {
- type: Array
- }
+ type: Array,
+ default: null,
+ },
},
data() {
return {
timer: null,
colNum: 0,
lastWidth: 0,
- percentWidthArr: []
+ percentWidthArr: [],
};
},
created() {
@@ -105,16 +107,16 @@ export default {
},
methods: {
calulate(arr) {
- let pageWidth = this.fixWidth ? this.fixWidth : this.$el.offsetWidth;
+ const pageWidth = this.fixWidth ? this.fixWidth : this.$el.offsetWidth;
// 百分比布局计算
if (this.percent) {
this.colNum = this.percent.length;
const total = sum(this.percent);
- this.percentWidthArr = this.percent.map(value => (value / total) * pageWidth);
+ this.percentWidthArr = this.percent.map((value) => (value / total) * pageWidth);
this.lastWidth = 0;
// 正常布局计算
} else {
- this.colNum = parseInt(pageWidth / (arr.width + this.gutterWidth));
+ this.colNum = parseInt(pageWidth / (arr.width + this.gutterWidth), 10);
if (this.minCol && this.colNum < this.minCol) {
this.colNum = this.minCol;
this.lastWidth = 0;
@@ -136,14 +138,14 @@ export default {
render() {
// 重新排序
let childArr = [];
- childArr = this.$children.map(child => child.getMeta());
+ childArr = this.$children.map((child) => child.getMeta());
childArr = quickSort(childArr, 'order');
// 计算列数
- this.calulate(childArr[0])
- let offsetArr = Array(this.colNum).fill(0);
+ this.calulate(childArr[0]);
+ const offsetArr = Array(this.colNum).fill(0);
// 渲染
- childArr.forEach(child => {
- let position = getMinIndex(offsetArr);
+ childArr.forEach((child) => {
+ const position = getMinIndex(offsetArr);
// 百分比布局渲染
if (this.percent) {
let left = 0;
@@ -171,10 +173,10 @@ export default {
}
child.el.style.top = `${offsetArr[position]}px`;
offsetArr[position] += child.height + this.gutterHeight;
- this.$el.style.height = `${Math.max.apply(Math, offsetArr)}px`;
+ this.$el.style.height = `${Math.max(...offsetArr)}px`;
});
this.$emit('rendered', this);
- }
- }
+ },
+ },
};
</script>
diff --git a/src/site/components/grid/waterfall/WaterfallItem.vue b/src/site/components/grid/waterfall/WaterfallItem.vue
index 7c4d814..2a5c69a 100644
--- a/src/site/components/grid/waterfall/WaterfallItem.vue
+++ b/src/site/components/grid/waterfall/WaterfallItem.vue
@@ -12,26 +12,27 @@
<script>
import imagesLoaded from 'imagesloaded';
+
export default {
name: 'WaterfallItem',
props: {
order: {
type: Number,
- default: 0
+ default: 0,
},
width: {
type: Number,
- default: 150
+ default: 150,
},
video: {
type: Boolean,
- default: false
- }
+ default: false,
+ },
},
data() {
return {
itemWidth: 0,
- height: 0
+ height: 0,
};
},
created() {
@@ -42,18 +43,18 @@ export default {
this.$el.style.width = `${this.width}px`;
this.emit();
if (this.video) {
- const videoEl = this.$slots.default.find(e => e.tag?.toLowerCase() === 'video');
+ // find first video object
+ const videoEl = this.$slots.default.find((e) => e.tag?.toLowerCase() === 'video');
const el = videoEl.elm;
- console.log(videoEl);
- console.log(el);
+
+ // add event listener for video loaded
el.onloadeddata = () => {
- console.log('loaded');
this.$el.style.left = '-9999px';
this.$el.style.top = '-9999px';
this.$el.style.display = 'block';
this.height = el.offsetHeight + 5;
this.itemWidth = el.offsetWidth;
- }
+ };
} else {
imagesLoaded(this.$el, () => {
this.$el.style.left = '-9999px';
@@ -73,9 +74,9 @@ export default {
el: this.$el,
height: this.height,
width: this.itemWidth,
- order: this.order
+ order: this.order,
};
- }
- }
-}
+ },
+ },
+};
</script>
diff --git a/src/site/constants/alertTypes.js b/src/site/constants/alertTypes.js
new file mode 100644
index 0000000..1b830bc
--- /dev/null
+++ b/src/site/constants/alertTypes.js
@@ -0,0 +1,10 @@
+export default {
+ PRIMARY: 'is-primary',
+ INFO: 'is-info',
+ SUCCESS: 'is-success',
+ WARNING: 'is-warning',
+ ERROR: 'is-danger',
+ DARK: 'is-dark',
+ LIGHT: 'is-light',
+ WHITE: 'is-white',
+};
diff --git a/src/site/plugins/notifier.js b/src/site/plugins/notifier.js
new file mode 100644
index 0000000..bcf7878
--- /dev/null
+++ b/src/site/plugins/notifier.js
@@ -0,0 +1,7 @@
+export default ({ store }, inject) => {
+ inject('notifier', {
+ showMessage({ content = '', type = '' }) {
+ store.commit('alert/set', { content, color });
+ }
+ });
+};
diff --git a/src/site/store/auth.js b/src/site/store/auth.js
index 73976d6..55009ce 100644
--- a/src/site/store/auth.js
+++ b/src/site/store/auth.js
@@ -4,14 +4,14 @@ const getDefaultState = () => ({
loggedIn: false,
isLoading: false,
user: null,
- token: null
+ token: null,
});
export const state = getDefaultState;
export const getters = {
- isLoggedIn: state => state.loggedIn,
- getApiKey: state => state.user?.apiKey
+ isLoggedIn: (state) => state.loggedIn,
+ getApiKey: (state) => state.user?.apiKey,
};
export const actions = {
@@ -27,7 +27,7 @@ export const actions = {
commit('loginRequest');
try {
- const data = await this.$axios.$post(`auth/login`, { username, password });
+ const data = await this.$axios.$post('auth/login', { username, password });
this.$axios.setToken(data.token, 'Bearer');
commit('setToken', data.token);
@@ -38,7 +38,7 @@ export const actions = {
},
async fetchCurrentUser({ commit, dispatch }) {
try {
- const data = await this.$axios.$get(`users/me`);
+ const data = await this.$axios.$get('users/me');
commit('setUser', data.user);
} catch (e) {
dispatch('alert/set', { text: e.message, error: true }, { root: true });
@@ -46,9 +46,9 @@ export const actions = {
},
async changePassword({ dispatch }, { password, newPassword }) {
try {
- const response = await this.$axios.$post(`user/password/change`, {
+ const response = await this.$axios.$post('user/password/change', {
password,
- newPassword
+ newPassword,
});
return response;
@@ -58,7 +58,7 @@ export const actions = {
},
async requestAPIKey({ commit, dispatch }) {
try {
- const response = await this.$axios.$post(`user/apikey/change`);
+ const response = await this.$axios.$post('user/apikey/change');
commit('setApiKey', response.apiKey);
return response;
@@ -68,7 +68,7 @@ export const actions = {
},
logout({ commit }) {
commit('logout');
- }
+ },
};
export const mutations = {
@@ -94,5 +94,5 @@ export const mutations = {
this.$cookies.remove('token');
// reset state to default
Object.assign(state, getDefaultState());
- }
+ },
};
diff --git a/src/site/store/images.js b/src/site/store/images.js
index f6dae1b..14f475a 100644
--- a/src/site/store/images.js
+++ b/src/site/store/images.js
@@ -25,7 +25,7 @@ export const actions = {
try {
const response = await this.$axios.$get(`files`, { params: { limit: state.pagination.limit, page } });
- commit('updateFiles', { files: response.files });
+ commit('setFiles', { files: response.files });
commit('updatePaginationMeta', { totalFiles: response.count, page });
} catch (e) {
dispatch('alert/set', { text: e.message, error: true }, { root: true });
@@ -45,7 +45,7 @@ export const mutations = {
setIsLoading(state) {
state.isLoading = true;
},
- updateFiles(state, { files }) {
+ setFiles(state, { files }) {
state.files = files || [];
state.isLoading = false;
},