Add Gallery

This commit is contained in:
Miloš Holba
2020-08-08 21:46:56 +02:00
parent a947882449
commit 785dade6b2
15 changed files with 144565 additions and 3629 deletions
+191 -188
View File
@@ -1,213 +1,216 @@
<template>
<div id="vue-file-manager" v-cloak>
<div id="vue-file-manager" v-cloak>
<!--System alerts-->
<Alert />
<!--System alerts-->
<Alert/>
<div id="application-wrapper" v-if="layout === 'authorized'">
<!-- Full File Preview -->
<FileFullPreview />
<div id="application-wrapper" v-if="layout === 'authorized'">
<!--Mobile Navigation-->
<MobileNavigation />
<!--Mobile Navigation-->
<MobileNavigation />
<!--Confirm Popup-->
<Confirm />
<!--Confirm Popup-->
<Confirm />
<!--Share Item setup-->
<ShareCreate />
<ShareEdit />
<!--Share Item setup-->
<ShareCreate/>
<ShareEdit/>
<!--Move item setup-->
<MoveItem />
<!--Move item setup-->
<MoveItem/>
<!--Mobile Menu-->
<MobileMenu />
<!--Mobile Menu-->
<MobileMenu/>
<!--Navigation Sidebar-->
<MenuBar />
<!--Navigation Sidebar-->
<MenuBar/>
<!--Toastr-->
<ToastrWrapper />
<!--Toastr-->
<ToastrWrapper/>
<!--File page-->
<keep-alive :include="['Admin', 'Users']">
<router-view :class="{'is-scaled-down': isScaledDown}"/>
</keep-alive>
</div>
<router-view v-if="layout === 'unauthorized'"/>
<CookieDisclaimer />
<!--Background vignette-->
<Vignette/>
<!--File page-->
<keep-alive :include="['Admin', 'Users']">
<router-view :class="{ 'is-scaled-down': isScaledDown }" />
</keep-alive>
</div>
<router-view v-if="layout === 'unauthorized'" />
<CookieDisclaimer />
<!--Background vignette-->
<Vignette />
</div>
</template>
<script>
import ToastrWrapper from '@/components/Others/Notifications/ToastrWrapper'
import MobileNavigation from '@/components/Others/MobileNavigation'
import CookieDisclaimer from '@/components/Others/CookieDisclaimer'
import MobileMenu from '@/components/FilesView/MobileMenu'
import ShareCreate from '@/components/Others/ShareCreate'
import Confirm from '@/components/Others/Popup/Confirm'
import ShareEdit from '@/components/Others/ShareEdit'
import MoveItem from '@/components/Others/MoveItem'
import Vignette from '@/components/Others/Vignette'
import MenuBar from '@/components/Sidebar/MenuBar'
import Alert from '@/components/FilesView/Alert'
import {includes} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from "./bus"
import ToastrWrapper from "@/components/Others/Notifications/ToastrWrapper";
import MobileNavigation from "@/components/Others/MobileNavigation";
import CookieDisclaimer from "@/components/Others/CookieDisclaimer";
import FileFullPreview from "@/components/FilesView/FileFullPreview";
import MobileMenu from "@/components/FilesView/MobileMenu";
import ShareCreate from "@/components/Others/ShareCreate";
import Confirm from "@/components/Others/Popup/Confirm";
import ShareEdit from "@/components/Others/ShareEdit";
import MoveItem from "@/components/Others/MoveItem";
import Vignette from "@/components/Others/Vignette";
import MenuBar from "@/components/Sidebar/MenuBar";
import Alert from "@/components/FilesView/Alert";
import { includes } from "lodash";
import { mapGetters } from "vuex";
import { events } from "./bus";
export default {
name: 'app',
components: {
MobileNavigation,
CookieDisclaimer,
ToastrWrapper,
ShareCreate,
MobileMenu,
ShareEdit,
MoveItem,
Vignette,
Confirm,
MenuBar,
Alert,
},
computed: {
...mapGetters([
'isLogged', 'isGuest', 'config'
]),
layout() {
if (includes([
'InstallationDisclaimer',
'SubscriptionService',
'StripeCredentials',
'SubscriptionPlans',
'ForgottenPassword',
'CreateNewPassword',
'EnvironmentSetup',
'VerifyByPassword',
'SaaSLandingPage',
'BillingsDetail',
'NotFoundShared',
'AdminAccount',
'PurchaseCode',
'DynamicPage',
'SharedPage',
'ContactUs',
'AppSetup',
'Database',
'Upgrade',
'SignIn',
'SignUp',
], this.$route.name)
) {
return 'unauthorized'
}
export default {
name: "app",
components: {
MobileNavigation,
CookieDisclaimer,
ToastrWrapper,
ShareCreate,
FileFullPreview,
MobileMenu,
ShareEdit,
MoveItem,
Vignette,
Confirm,
MenuBar,
Alert,
},
computed: {
...mapGetters(["isLogged", "isGuest", "config"]),
layout() {
if (
includes(
[
"InstallationDisclaimer",
"SubscriptionService",
"StripeCredentials",
"SubscriptionPlans",
"ForgottenPassword",
"CreateNewPassword",
"EnvironmentSetup",
"VerifyByPassword",
"SaaSLandingPage",
"BillingsDetail",
"NotFoundShared",
"AdminAccount",
"PurchaseCode",
"DynamicPage",
"SharedPage",
"ContactUs",
"AppSetup",
"Database",
"Upgrade",
"SignIn",
"SignUp",
],
this.$route.name
)
) {
return "unauthorized";
}
return 'authorized'
}
},
data() {
return {
isScaledDown: false,
}
},
beforeMount() {
return "authorized";
},
},
data() {
return {
isScaledDown: false,
};
},
beforeMount() {
// Store config to vuex
this.$store.commit("INIT", {
authCookie: this.$root.$data.config.hasAuthCookie,
config: this.$root.$data.config,
rootDirectory: {
name: this.$t("locations.home"),
location: "base",
unique_id: 0,
},
});
// Store config to vuex
this.$store.commit('INIT', {
authCookie: this.$root.$data.config.hasAuthCookie,
config: this.$root.$data.config,
rootDirectory: {
name: this.$t('locations.home'),
location: 'base',
unique_id: 0,
}
})
// Get installation state
let installation = this.$root.$data.config.installation;
// Get installation state
let installation = this.$root.$data.config.installation
// Redirect to database verify code
if ( installation === 'setup-database') {
this.$router.push({name: 'PurchaseCode'})
}
// Redirect to starting installation process
if ( installation === 'setup-disclaimer' ) {
this.$router.push({name: 'InstallationDisclaimer'})
}
},
mounted() {
// Handle mobile navigation scale animation
events.$on('show:mobile-navigation', () => this.isScaledDown = true)
events.$on('hide:mobile-navigation', () => this.isScaledDown = false)
events.$on('mobileMenu:show', () => this.isScaledDown = true)
events.$on('fileItem:deselect', () => this.isScaledDown = false)
}
// Redirect to database verify code
if (installation === "setup-database") {
this.$router.push({ name: "PurchaseCode" });
}
// Redirect to starting installation process
if (installation === "setup-disclaimer") {
this.$router.push({ name: "InstallationDisclaimer" });
}
},
mounted() {
// Handle mobile navigation scale animation
events.$on("show:mobile-navigation", () => (this.isScaledDown = true));
events.$on("hide:mobile-navigation", () => (this.isScaledDown = false));
events.$on("mobileMenu:show", () => (this.isScaledDown = true));
events.$on("fileItem:deselect", () => (this.isScaledDown = false));
},
};
</script>
<style lang="scss">
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;600;700;800;900&display=swap');
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;600;700;800;900&display=swap");
@import "@assets/vue-file-manager/_variables";
@import "@assets/vue-file-manager/_mixins";
[v-cloak],
[v-cloak] > * {
display: none
}
* {
outline: 0;
margin: 0;
padding: 0;
font-family: 'Nunito', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-size: 16px;
text-decoration: none;
color: $text;
}
#auth {
width: 100%;
height: 100%;
}
#vue-file-manager {
position: absolute;
width: 100%;
height: 100%;
overflow-y: auto;
scroll-behavior:smooth;
}
@media only screen and (max-width: 690px) {
.is-scaled-down {
@include transform(scale(0.95));
}
}
// Dark mode support
@media (prefers-color-scheme: dark) {
* {
color: $dark_mode_text_primary;
}
body, html {
background: $dark_mode_background;
color: $dark_mode_text_primary;
img {
opacity: .95;
}
}
[v-cloak],
[v-cloak] > * {
display: none;
}
* {
outline: 0;
margin: 0;
padding: 0;
font-family: "Nunito", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-size: 16px;
text-decoration: none;
color: $text;
}
#auth {
width: 100%;
height: 100%;
}
#vue-file-manager {
position: absolute;
width: 100%;
height: 100%;
overflow-y: auto;
scroll-behavior: smooth;
}
@media only screen and (max-width: 690px) {
.is-scaled-down {
@include transform(scale(0.95));
}
}
// Dark mode support
@media (prefers-color-scheme: dark) {
* {
color: $dark_mode_text_primary;
}
body,
html {
background: $dark_mode_background;
color: $dark_mode_text_primary;
img {
opacity: 0.95;
}
}
}
</style>
File diff suppressed because it is too large Load Diff
+470 -429
View File
@@ -1,473 +1,514 @@
<template>
<div
class="file-wrapper"
@click.stop="clickedItem"
@dblclick="goToItem"
spellcheck="false"
>
<!--List preview-->
<div
class="file-wrapper"
@click.stop="clickedItem"
@dblclick="goToItem"
spellcheck="false"
:draggable="canDrag"
@dragstart="$emit('dragstart')"
@drop="
$emit('drop');
area = false;
"
@dragleave="dragLeave"
@dragover.prevent="dragEnter"
class="file-item"
:class="{ 'is-clicked': isClicked, 'is-dragenter': area }"
>
<!--List preview-->
<div
:draggable="canDrag"
@dragstart="$emit('dragstart')"
@drop="
$emit('drop')
area = false
"
@dragleave="dragLeave"
@dragover.prevent="dragEnter"
class="file-item"
:class="{ 'is-clicked': isClicked, 'is-dragenter': area }"
<!--Thumbnail for item-->
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">
{{ data.mimetype | limitCharacters }}
</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file" />
<!--Image thumbnail-->
<img
v-if="isImage"
class="image"
:src="data.thumbnail"
:alt="data.name"
/>
<!--Else show only folder icon-->
<FontAwesomeIcon
v-if="isFolder"
:class="{ 'is-deleted': isDeleted }"
class="folder-icon"
icon="folder"
/>
</div>
<!--Name-->
<div class="item-name">
<!--Name-->
<b
ref="name"
@input="renameItem"
:contenteditable="canEditName"
class="name"
>
<!--Thumbnail for item-->
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">
{{ data.mimetype | limitCharacters }}
</span>
{{ itemName }}
</b>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
<div class="item-info">
<!--Shared Icon-->
<div
v-if="$checkPermission('master') && data.shared"
class="item-shared"
>
<link-icon size="12" class="shared-icon"></link-icon>
</div>
<!--Image thumbnail-->
<img v-if="isImage" class="image" :src="data.thumbnail" :alt="data.name"/>
<!--Participant owner Icon-->
<div
v-if="$checkPermission('master') && data.user_scope !== 'master'"
class="item-shared"
>
<user-plus-icon size="12" class="shared-icon"></user-plus-icon>
</div>
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" :class="{'is-deleted': isDeleted}" class="folder-icon" icon="folder"/>
</div>
<!--Filesize and timestamp-->
<span v-if="!isFolder" class="item-size"
>{{ data.filesize }}, {{ timeStamp }}</span
>
<!--Name-->
<div class="item-name">
<!--Name-->
<b
ref="name"
@input="renameItem"
:contenteditable="canEditName"
class="name"
>
{{ itemName }}
</b>
<div class="item-info">
<!--Shared Icon-->
<div v-if="$checkPermission('master') && data.shared" class="item-shared">
<link-icon size="12" class="shared-icon"></link-icon>
</div>
<!--Participant owner Icon-->
<div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared">
<user-plus-icon size="12" class="shared-icon"></user-plus-icon>
</div>
<!--Filesize and timestamp-->
<span v-if="! isFolder" class="item-size">{{ data.filesize }}, {{ timeStamp }}</span>
<!--Folder item counts-->
<span v-if="isFolder" class="item-length">
{{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }}
</span>
</div>
</div>
<!--Go Next icon-->
<div class="actions" v-if="$isMobile() && ! ( $checkPermission('visitor') && isFolder )">
<span @click.stop="showItemActions" class="show-actions">
<FontAwesomeIcon icon="ellipsis-v" class="icon-action"></FontAwesomeIcon>
</span>
</div>
<!--Folder item counts-->
<span v-if="isFolder" class="item-length">
{{
folderItems == 0
? $t("folder.empty")
: $tc("folder.item_counts", folderItems)
}}, {{ timeStamp }}
</span>
</div>
</div>
<!--Go Next icon-->
<div
class="actions"
v-if="$isMobile() && !($checkPermission('visitor') && isFolder)"
>
<span @click.stop="showItemActions" class="show-actions">
<FontAwesomeIcon
icon="ellipsis-v"
class="icon-action"
></FontAwesomeIcon>
</span>
</div>
</div>
</div>
</template>
<script>
import { LinkIcon, UserPlusIcon } from 'vue-feather-icons'
import {debounce} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import { LinkIcon, UserPlusIcon } from "vue-feather-icons";
import { debounce } from "lodash";
import { mapGetters } from "vuex";
import { events } from "@/bus";
export default {
name: 'FileItemList',
props: ['data'],
components: {
UserPlusIcon,
LinkIcon,
},
computed: {
...mapGetters(['FilePreviewType']),
isFolder() {
return this.data.type === 'folder'
},
isFile() {
return this.data.type !== 'folder' && this.data.type !== 'image'
},
isImage() {
return this.data.type === 'image'
},
canEditName() {
return !this.$isMobile()
&& !this.$isThisLocation(['trash', 'trash-root'])
&& !this.$checkPermission('visitor')
&& !(this.sharedDetail && this.sharedDetail.type === 'file')
},
canDrag() {
return !this.isDeleted && this.$checkPermission(['master', 'editor'])
},
timeStamp() {
return this.data.deleted_at ? this.$t('item_thumbnail.deleted_at', {time: this.data.deleted_at}) : this.data.created_at
},
folderItems() {
return this.data.deleted_at ? this.data.trashed_items : this.data.items
},
isDeleted() {
return this.data.deleted_at ? true : false
}
},
filters: {
limitCharacters(str) {
export default {
name: "FileItemList",
props: ["data"],
components: {
UserPlusIcon,
LinkIcon,
},
computed: {
...mapGetters(["FilePreviewType"]),
isFolder() {
return this.data.type === "folder";
},
isFile() {
return this.data.type !== "folder" && this.data.type !== "image";
},
isImage() {
return this.data.type === "image";
},
isPdf() {
return this.data.mimetype === "pdf";
},
isVideo() {
return this.data.type === "video";
},
canEditName() {
return (
!this.$isMobile() &&
!this.$isThisLocation(["trash", "trash-root"]) &&
!this.$checkPermission("visitor") &&
!(this.sharedDetail && this.sharedDetail.type === "file")
);
},
canDrag() {
return !this.isDeleted && this.$checkPermission(["master", "editor"]);
},
timeStamp() {
return this.data.deleted_at
? this.$t("item_thumbnail.deleted_at", { time: this.data.deleted_at })
: this.data.created_at;
},
folderItems() {
return this.data.deleted_at ? this.data.trashed_items : this.data.items;
},
isDeleted() {
return this.data.deleted_at ? true : false;
},
},
filters: {
limitCharacters(str) {
if (str.length > 3) {
return str.substring(0, 3) + "...";
} else {
return str.substring(0, 3);
}
},
},
data() {
return {
isClicked: false,
area: false,
itemName: undefined,
};
},
methods: {
showItemActions() {
// Load file info detail
this.$store.commit("GET_FILEINFO_DETAIL", this.data);
if (str.length > 3) {
return str.substring(0, 3) + '...';
} else {
return str.substring(0, 3);
}
//this.isClicked = true
}
},
data() {
return {
isClicked: false,
area: false,
itemName: undefined,
}
},
methods: {
showItemActions() {
// Load file info detail
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
events.$emit("mobileMenu:show");
},
dragEnter() {
if (this.data.type !== "folder") return;
//this.isClicked = true
this.area = true;
},
dragLeave() {
this.area = false;
},
clickedItem(e) {
events.$emit("contextMenu:hide");
events.$emit("fileItem:deselect");
events.$emit('mobileMenu:show')
},
dragEnter() {
if (this.data.type !== 'folder') return
// Set clicked item
this.isClicked = true;
this.area = true
},
dragLeave() {
this.area = false
},
clickedItem(e) {
events.$emit('contextMenu:hide')
events.$emit('fileItem:deselect')
// Open in mobile version on first click
if (this.$isMobile() && this.isFolder) {
// Go to folder
if (this.$isThisLocation("public")) {
this.$store.dispatch("browseShared", [
{ folder: this.data, back: false, init: false },
]);
} else {
this.$store.dispatch("getFolder", [
{ folder: this.data, back: false, init: false },
]);
}
}
// Set clicked item
this.isClicked = true
if (this.$isMobile()) {
if (this.isImage || this.isVideo) {
events.$emit("fileFullPreview:show");
}
}
// Open in mobile version on first click
if (this.$isMobile() && this.isFolder) {
// Load file info detail
this.$store.commit("GET_FILEINFO_DETAIL", this.data);
// Go to folder
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
}
}
// Get target classname
let itemClass = e.target.className;
// Load file info detail
this.$store.commit('GET_FILEINFO_DETAIL', this.data)
if (["name", "icon", "file-link", "file-icon-text"].includes(itemClass))
return;
},
goToItem() {
if (this.isImage || this.isVideo) {
// this.$openImageOnNewTab(this.data.file_url)
events.$emit("fileFullPreview:show");
}
// Get target classname
let itemClass = e.target.className
if (this.isFile && !this.isPdf && !this.isVideo) {
this.$downloadFile(
this.data.file_url,
this.data.name + "." + this.data.mimetype
);
}
if (['name', 'icon', 'file-link', 'file-icon-text'].includes(itemClass))
return
},
goToItem() {
if (this.isImage) {
this.$openImageOnNewTab(this.data.file_url)
}
if (this.isFolder) {
if (this.$isThisLocation("public")) {
this.$store.dispatch("browseShared", [
{ folder: this.data, back: false, init: false },
]);
} else {
this.$store.dispatch("getFolder", [
{ folder: this.data, back: false, init: false },
]);
}
}
},
renameItem: debounce(function (e) {
// Prevent submit empty string
if (e.target.innerText.trim() === "") return;
if (this.isFile) {
this.$downloadFile(
this.data.file_url,
this.data.name + '.' + this.data.mimetype
)
}
this.$store.dispatch("renameItem", {
unique_id: this.data.unique_id,
type: this.data.type,
name: e.target.innerText,
});
}, 300),
},
created() {
this.itemName = this.data.name;
if (this.isFolder) {
events.$on("fileItem:deselect", () => {
// Deselect file
this.isClicked = false;
});
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
}
}
},
renameItem: debounce(function (e) {
// Prevent submit empty string
if (e.target.innerText.trim() === '') return
this.$store.dispatch('renameItem', {
unique_id: this.data.unique_id,
type: this.data.type,
name: e.target.innerText
})
}, 300)
},
created() {
this.itemName = this.data.name
events.$on('fileItem:deselect', () => {
// Deselect file
this.isClicked = false
})
// Change item name
events.$on('change:name', (item) => {
if (this.data.unique_id == item.unique_id) this.itemName = item.name
})
},
}
// Change item name
events.$on("change:name", (item) => {
if (this.data.unique_id == item.unique_id) this.itemName = item.name;
});
},
};
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
@import "@assets/vue-file-manager/_variables";
@import "@assets/vue-file-manager/_mixins";
.file-wrapper {
user-select: none;
position: relative;
.file-wrapper {
user-select: none;
position: relative;
&:hover {
border-color: transparent;
&:hover {
border-color: transparent;
}
.actions {
text-align: right;
width: 50px;
.show-actions {
cursor: pointer;
padding: 12px 6px 12px;
.icon-action {
@include font-size(14);
path {
fill: $theme;
}
}
}
}
.actions {
text-align: right;
width: 50px;
.item-name {
display: block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.show-actions {
cursor: pointer;
padding: 12px 6px 12px;
.icon-action {
@include font-size(14);
path {
fill: $theme;
}
}
}
}
.item-name {
display: block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.item-info {
display: block;
line-height: 1;
}
.item-shared {
display: inline-block;
.label {
@include font-size(12);
font-weight: 400;
color: $theme;
}
.shared-icon {
vertical-align: middle;
path, circle, line {
stroke: $theme;
}
}
}
.item-size,
.item-length {
@include font-size(11);
font-weight: 400;
color: rgba($text, 0.7);
}
.name {
white-space: nowrap;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
&[contenteditable='true']:hover {
text-decoration: underline;
}
}
.name {
color: $text;
@include font-size(14);
font-weight: 700;
max-height: 40px;
overflow: hidden;
text-overflow: ellipsis;
&.actived {
max-height: initial;
}
}
}
&.selected {
.file-item {
background: $light_background;
}
}
.icon-item {
text-align: center;
position: relative;
flex: 0 0 50px;
line-height: 0;
margin-right: 20px;
.folder-icon {
@include font-size(52);
path {
fill: $theme;
}
&.is-deleted {
path {
fill: $dark_background;
}
}
}
.file-icon {
@include font-size(45);
path {
fill: #fafafc;
stroke: #dfe0e8;
stroke-width: 1;
}
}
.file-icon-text {
line-height: 1;
top: 40%;
@include font-size(11);
margin: 0 auto;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 50px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.image {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 50px;
height: 50px;
pointer-events: none;
}
}
.file-item {
border: 2px dashed transparent;
width: 100%;
display: flex;
align-items: center;
padding: 7px;
&.is-dragenter {
border: 2px dashed $theme;
border-radius: 8px;
}
&:hover,
&.is-clicked {
border-radius: 8px;
background: $light_background;
.item-name .name {
color: $theme;
}
}
}
.item-info {
display: block;
line-height: 1;
}
@media (prefers-color-scheme: dark) {
.item-shared {
display: inline-block;
.file-wrapper {
.label {
@include font-size(12);
font-weight: 400;
color: $theme;
}
.icon-item {
.file-icon {
.shared-icon {
vertical-align: middle;
path {
fill: $dark_mode_foreground;
stroke: #2F3C54;
}
}
.folder-icon {
&.is-deleted {
path {
fill: lighten($dark_mode_foreground, 5%);
}
}
}
}
.file-item {
&:hover,
&.is-clicked {
background: $dark_mode_foreground;
.file-icon {
path {
fill: $dark_mode_background;
}
}
}
}
.item-name {
.name {
color: $dark_mode_text_primary;
}
.item-size,
.item-length {
color: $dark_mode_text_secondary;
}
}
path,
circle,
line {
stroke: $theme;
}
}
}
.item-size,
.item-length {
@include font-size(11);
font-weight: 400;
color: rgba($text, 0.7);
}
.name {
white-space: nowrap;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
&[contenteditable="true"]:hover {
text-decoration: underline;
}
}
.name {
color: $text;
@include font-size(14);
font-weight: 700;
max-height: 40px;
overflow: hidden;
text-overflow: ellipsis;
&.actived {
max-height: initial;
}
}
}
&.selected {
.file-item {
background: $light_background;
}
}
.icon-item {
text-align: center;
position: relative;
flex: 0 0 50px;
line-height: 0;
margin-right: 20px;
.folder-icon {
@include font-size(52);
path {
fill: $theme;
}
&.is-deleted {
path {
fill: $dark_background;
}
}
}
.file-icon {
@include font-size(45);
path {
fill: #fafafc;
stroke: #dfe0e8;
stroke-width: 1;
}
}
.file-icon-text {
line-height: 1;
top: 40%;
@include font-size(11);
margin: 0 auto;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 50px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.image {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 50px;
height: 50px;
pointer-events: none;
}
}
.file-item {
border: 2px dashed transparent;
width: 100%;
display: flex;
align-items: center;
padding: 7px;
&.is-dragenter {
border: 2px dashed $theme;
border-radius: 8px;
}
&:hover,
&.is-clicked {
border-radius: 8px;
background: $light_background;
.item-name .name {
color: $theme;
}
}
}
}
@media (prefers-color-scheme: dark) {
.file-wrapper {
.icon-item {
.file-icon {
path {
fill: $dark_mode_foreground;
stroke: #2f3c54;
}
}
.folder-icon {
&.is-deleted {
path {
fill: lighten($dark_mode_foreground, 5%);
}
}
}
}
.file-item {
&:hover,
&.is-clicked {
background: $dark_mode_foreground;
.file-icon {
path {
fill: $dark_mode_background;
}
}
}
}
.item-name {
.name {
color: $dark_mode_text_primary;
}
.item-size,
.item-length {
color: $dark_mode_text_secondary;
}
}
}
}
</style>
File diff suppressed because it is too large Load Diff
@@ -1,75 +1,108 @@
<template>
<button class="button" :title="action">
<corner-down-right-icon v-if="source === 'move'" size="19"></corner-down-right-icon>
<folder-plus-icon v-if="source === 'folder-plus'" size="19"></folder-plus-icon>
<trash-2-icon v-if="source === 'trash'" size="19"></trash-2-icon>
<list-icon v-if="source === 'th-list'" size="19"></list-icon>
<info-icon v-if="source === 'info'" size="19"></info-icon>
<grid-icon v-if="source === 'th'" size="19"></grid-icon>
<link-icon v-if="source === 'share'" size="19"></link-icon>
</button>
<button class="button" :title="action">
<corner-down-right-icon
v-if="source === 'move'"
size="19"
></corner-down-right-icon>
<download-cloud-icon
v-if="source === 'download'"
size="19"
></download-cloud-icon>
<folder-plus-icon
v-if="source === 'folder-plus'"
size="19"
></folder-plus-icon>
<edit-2-icon v-if="source === 'rename'" size="19"></edit-2-icon>
<printer-icon v-if="source === 'print'" size="19"></printer-icon>
<trash-2-icon v-if="source === 'trash'" size="19"></trash-2-icon>
<list-icon v-if="source === 'th-list'" size="19"></list-icon>
<info-icon v-if="source === 'info'" size="19"></info-icon>
<grid-icon v-if="source === 'th'" size="19"></grid-icon>
<link-icon v-if="source === 'share'" size="19"></link-icon>
</button>
</template>
<script>
import {FolderPlusIcon, Trash2Icon, GridIcon, ListIcon, InfoIcon, CornerDownRightIcon, LinkIcon} from 'vue-feather-icons'
import {
FolderPlusIcon,
Trash2Icon,
GridIcon,
ListIcon,
Edit2Icon,
InfoIcon,
CornerDownRightIcon,
LinkIcon,
DownloadCloudIcon,
PrinterIcon,
} from "vue-feather-icons";
export default {
name: 'ToolbarButton',
props: ['source', 'action'],
components: {
CornerDownRightIcon,
FolderPlusIcon,
Trash2Icon,
ListIcon,
GridIcon,
InfoIcon,
LinkIcon,
},
}
export default {
name: "ToolbarButton",
props: ["source", "action"],
components: {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
PrinterIcon,
Trash2Icon,
Edit2Icon,
ListIcon,
GridIcon,
InfoIcon,
LinkIcon,
},
};
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
@import "@assets/vue-file-manager/_variables";
@import "@assets/vue-file-manager/_mixins";
.button {
height: 42px;
width: 42px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
text-align: center;
cursor: pointer;
white-space: nowrap;
outline: none;
border: none;
@include transition(150ms);
background: transparent;
.button {
height: 42px;
width: 42px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
text-align: center;
cursor: pointer;
white-space: nowrap;
outline: none;
border: none;
@include transition(150ms);
background: transparent;
&:hover {
background: $light_background;
&:hover {
background: $light_background;
path, line, polyline, rect, circle {
@include transition(150ms);
stroke: $theme;
}
}
path,
line,
polyline,
rect,
circle {
@include transition(150ms);
stroke: $theme;
}
}
}
@media (prefers-color-scheme: dark) {
.button {
background: transparent;
&:hover {
background: $dark_mode_foreground;
}
@media (prefers-color-scheme: dark) {
.button {
background: transparent;
&:hover {
background: $dark_mode_foreground;
}
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
path,
line,
polyline,
rect,
circle {
stroke: $dark_mode_text_primary;
}
}
}
</style>
+240 -232
View File
@@ -1,276 +1,284 @@
import store from './store/index'
import {debounce, includes} from "lodash";
import {events} from './bus'
import axios from 'axios'
import router from '@/router'
import store from "./store/index";
import { debounce, includes, truncate } from "lodash";
import { events } from "./bus";
import axios from "axios";
import router from "@/router";
const Helpers = {
install(Vue) {
install(Vue) {
Vue.prototype.$updateText = debounce(function(route, name, value) {
if (value === "") return;
Vue.prototype.$updateText = debounce(function (route, name, value) {
axios
.patch(this.$store.getters.api + route, { name, value })
.catch((error) => {
events.$emit("alert:open", {
title: this.$t("popup_error.title"),
message: this.$t("popup_error.message"),
});
});
}, 150);
if (value === '') return
Vue.prototype.$updateImage = function(route, name, image) {
// Create form
let formData = new FormData();
axios.patch(this.$store.getters.api + route, {name, value})
.catch(error => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
})
}, 150)
// Add image to form
formData.append("name", name);
formData.append(name, image);
formData.append("_method", "PATCH");
Vue.prototype.$updateImage = function (route, name, image) {
axios
.post(this.$store.getters.api + route, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.catch((error) => {
events.$emit("alert:open", {
title: this.$t("popup_error.title"),
message: this.$t("popup_error.message"),
});
});
};
// Create form
let formData = new FormData()
Vue.prototype.$scrollTop = function() {
var container = document.getElementById("vue-file-manager");
// Add image to form
formData.append('name', name)
formData.append(name, image)
formData.append('_method', 'PATCH')
if (container) {
container.scrollTop = 0;
}
};
axios.post(this.$store.getters.api + route, formData, {
headers: {
'Content-Type': 'multipart/form-data',
}
})
.catch(error => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
})
Vue.prototype.$getImage = function(source) {
return source ? "/" + source : "";
};
Vue.prototype.$getCreditCardBrand = function(brand) {
return `/assets/icons/${brand}.svg`;
};
Vue.prototype.$getInvoiceLink = function(customer, id) {
return "/invoice/" + customer + "/" + id;
};
Vue.prototype.$openImageOnNewTab = function(source) {
let win = window.open(source, "_blank");
win.focus();
};
Vue.prototype.$createFolder = function(folderName) {
this.$store.dispatch("createFolder", folderName);
};
Vue.prototype.$handleUploading = async function(files, parent_id) {
let fileBuffer = [];
// Append the file list to fileBuffer array
Array.prototype.push.apply(fileBuffer, files);
let fileSucceed = 0;
// Update files count in progressbar
store.commit("UPDATE_FILE_COUNT_PROGRESS", {
current: fileSucceed,
total: files.length,
});
// Reset upload progress to 0
store.commit("UPLOADING_FILE_PROGRESS", 0);
// Get parent id
let parentFolder = this.$store.getters.currentFolder
? this.$store.getters.currentFolder.unique_id
: 0;
let rootFolder = parent_id ? parent_id : parentFolder;
// Upload files
do {
let file = fileBuffer.shift(),
chunks = [];
// Calculate ceils
let size = this.$store.getters.config.chunkSize,
chunksCeil = Math.ceil(file.size / size);
// Create chunks
for (let i = 0; i < chunksCeil; i++) {
chunks.push(
file.slice(
i * size,
Math.min(i * size + size, file.size),
file.type
)
);
}
Vue.prototype.$scrollTop = function () {
var container = document.getElementById('vue-file-manager')
// Set Data
let formData = new FormData(),
uploadedSize = 0,
isNotGeneralError = true,
filename =
Array(16)
.fill(0)
.map((x) =>
Math.random()
.toString(36)
.charAt(2)
)
.join("") +
"-" +
file.name +
".part";
if (container) {
container.scrollTop = 0
}
}
do {
let isLast = chunks.length === 1,
chunk = chunks.shift(),
attempts = 0;
Vue.prototype.$getImage = function (source) {
return source ? '/' + source : ''
}
// Set form data
formData.set("file", chunk, filename);
formData.set("parent_id", rootFolder);
formData.set("is_last", isLast);
Vue.prototype.$getCreditCardBrand = function (brand) {
return `/assets/icons/${brand}.svg`
}
// Upload chunks
do {
await store
.dispatch("uploadFiles", {
form: formData,
fileSize: file.size,
totalUploadedSize: uploadedSize,
})
.then(() => {
uploadedSize = uploadedSize + chunk.size;
})
.catch((error) => {
// Count attempts
attempts++;
Vue.prototype.$getInvoiceLink = function (customer, id) {
return '/invoice/' + customer + '/' + id
}
// Break uploading proccess
if (error.response.status === 500) isNotGeneralError = false;
Vue.prototype.$openImageOnNewTab = function (source) {
let win = window.open(source, '_blank')
// Show Error
if (attempts === 3) this.$isSomethingWrong();
});
} while (isNotGeneralError && attempts !== 0 && attempts !== 3);
} while (isNotGeneralError && chunks.length !== 0);
win.focus()
}
fileSucceed++;
Vue.prototype.$createFolder = function (folderName) {
this.$store.dispatch('createFolder', folderName)
}
// Progress file log
store.commit("UPDATE_FILE_COUNT_PROGRESS", {
current: fileSucceed,
total: files.length,
});
} while (fileBuffer.length !== 0);
Vue.prototype.$handleUploading = async function (files, parent_id) {
store.commit("UPDATE_FILE_COUNT_PROGRESS", undefined);
};
let fileBuffer = []
Vue.prototype.$uploadFiles = async function(files) {
this.$handleUploading(files, undefined);
};
// Append the file list to fileBuffer array
Array.prototype.push.apply(fileBuffer, files);
Vue.prototype.$uploadExternalFiles = async function(event, parent_id) {
// Prevent submit empty files
if (event.dataTransfer.items.length == 0) return;
let fileSucceed = 0
// Get files
let files = [...event.dataTransfer.items].map((item) => item.getAsFile());
// Update files count in progressbar
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileSucceed,
total: files.length
})
this.$handleUploading(files, parent_id);
};
// Reset upload progress to 0
store.commit('UPLOADING_FILE_PROGRESS', 0)
Vue.prototype.$downloadFile = function(url, filename) {
var anchor = document.createElement("a");
// Get parent id
let parentFolder = this.$store.getters.currentFolder ? this.$store.getters.currentFolder.unique_id : 0
let rootFolder = parent_id ? parent_id : parentFolder
anchor.href = url;
// Upload files
do {
let file = fileBuffer.shift(),
chunks = []
anchor.download = filename;
// Calculate ceils
let size = this.$store.getters.config.chunkSize,
chunksCeil = Math.ceil(file.size / size);
document.body.appendChild(anchor);
// Create chunks
for (let i = 0; i < chunksCeil; i++) {
chunks.push(file.slice(
i * size, Math.min(i * size + size, file.size), file.type
));
}
anchor.click();
};
// Set Data
let formData = new FormData(),
uploadedSize = 0,
isNotGeneralError = true,
filename = Array(16).fill(0).map(x => Math.random().toString(36).charAt(2)).join('') + '-' + file.name + '.part'
Vue.prototype.$closePopup = function() {
events.$emit("popup:close");
};
do {
let isLast = chunks.length === 1,
chunk = chunks.shift(),
attempts = 0
Vue.prototype.$isThisRoute = function(route, locations) {
return includes(locations, route.name);
};
// Set form data
formData.set('file', chunk, filename);
formData.set('parent_id', rootFolder)
formData.set('is_last', isLast);
Vue.prototype.$isThisLocation = function(location) {
// Get current location
let currentLocation =
store.getters.currentFolder && store.getters.currentFolder.location
? store.getters.currentFolder.location
: undefined;
// Upload chunks
do {
await store.dispatch('uploadFiles', {
form: formData,
fileSize: file.size,
totalUploadedSize: uploadedSize
}).then(() => {
uploadedSize = uploadedSize + chunk.size
}).catch((error) => {
// Check if type is object
if (typeof location === "Object" || location instanceof Object) {
return includes(location, currentLocation);
} else {
return currentLocation === location;
}
};
// Count attempts
attempts++
Vue.prototype.$checkPermission = function(type) {
let currentPermission = store.getters.permission;
// Break uploading proccess
if (error.response.status === 500)
isNotGeneralError = false
// Check if type is object
if (typeof type === "Object" || type instanceof Object) {
return includes(type, currentPermission);
} else {
return currentPermission === type;
}
};
// Show Error
if (attempts === 3)
this.$isSomethingWrong()
})
} while (isNotGeneralError && attempts !== 0 && attempts !== 3)
Vue.prototype.$isMobile = function() {
const toMatch = [
/Android/i,
/webOS/i,
/iPhone/i,
/iPad/i,
/iPod/i,
/BlackBerry/i,
/Windows Phone/i,
];
} while (isNotGeneralError && chunks.length !== 0)
return toMatch.some((toMatchItem) => {
return navigator.userAgent.match(toMatchItem);
});
};
fileSucceed++
Vue.prototype.$isMinimalScale = function() {
let sizeType = store.getters.filesViewWidth;
// Progress file log
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileSucceed,
total: files.length
})
return sizeType === "minimal-scale";
};
} while (fileBuffer.length !== 0)
Vue.prototype.$isCompactScale = function() {
let sizeType = store.getters.filesViewWidth;
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
}
return sizeType === "compact-scale";
};
Vue.prototype.$uploadFiles = async function (files) {
Vue.prototype.$isFullScale = function() {
let sizeType = store.getters.filesViewWidth;
this.$handleUploading(files, undefined)
}
return sizeType === "full-scale";
};
Vue.prototype.$uploadExternalFiles = async function (event, parent_id) {
Vue.prototype.$isSomethingWrong = function() {
events.$emit("alert:open", {
title: this.$t("popup_error.title"),
message: this.$t("popup_error.message"),
});
};
},
};
// Prevent submit empty files
if (event.dataTransfer.items.length == 0) return
// Get files
let files = [...event.dataTransfer.items].map(item => item.getAsFile());
this.$handleUploading(files, parent_id)
}
Vue.prototype.$downloadFile = function (url, filename) {
var anchor = document.createElement('a')
anchor.href = url
anchor.download = filename
document.body.appendChild(anchor)
anchor.click()
}
Vue.prototype.$closePopup = function () {
events.$emit('popup:close')
}
Vue.prototype.$isThisRoute = function (route, locations) {
return includes(locations, route.name)
}
Vue.prototype.$isThisLocation = function (location) {
// Get current location
let currentLocation = store.getters.currentFolder && store.getters.currentFolder.location ? store.getters.currentFolder.location : undefined
// Check if type is object
if (typeof location === 'Object' || location instanceof Object) {
return includes(location, currentLocation)
} else {
return currentLocation === location
}
}
Vue.prototype.$checkPermission = function (type) {
let currentPermission = store.getters.permission
// Check if type is object
if (typeof type === 'Object' || type instanceof Object) {
return includes(type, currentPermission)
} else {
return currentPermission === type
}
}
Vue.prototype.$isMobile = function () {
const toMatch = [
/Android/i,
/webOS/i,
/iPhone/i,
/iPad/i,
/iPod/i,
/BlackBerry/i,
/Windows Phone/i
]
return toMatch.some(toMatchItem => {
return navigator.userAgent.match(toMatchItem)
})
}
Vue.prototype.$isMinimalScale = function () {
let sizeType = store.getters.filesViewWidth
return sizeType === 'minimal-scale'
}
Vue.prototype.$isCompactScale = function () {
let sizeType = store.getters.filesViewWidth
return sizeType === 'compact-scale'
}
Vue.prototype.$isFullScale = function () {
let sizeType = store.getters.filesViewWidth
return sizeType === 'full-scale'
}
Vue.prototype.$isSomethingWrong = function () {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
}
}
}
export default Helpers
export default Helpers;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+92 -93
View File
@@ -1,100 +1,99 @@
require('./bootstrap');
import Vue from 'vue'
import VueRouter from 'vue-router'
import router from './router'
import i18n from './i18n/index.js'
import App from './App.vue'
import store from './store'
import Helpers from './helpers'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
require("./bootstrap");
import Vue from "vue";
import VueRouter from "vue-router";
import router from "./router";
import i18n from "./i18n/index.js";
import App from "./App.vue";
import store from "./store";
import Helpers from "./helpers";
import { library } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import {
faLock,
faLockOpen,
faDownload,
faUserFriends,
faCheck,
faLink,
faUserEdit,
faUser,
faFileAudio,
faFileVideo,
faSyncAlt,
faShare,
faHome,
faEyeSlash,
faBars,
faSearch,
faEllipsisV,
faChevronLeft,
faChevronRight,
faChevronDown,
faUpload,
faFolderPlus,
faTh,
faThList,
faInfo,
faFolder,
faFile,
faFileImage,
faTimes,
faSort,
faTrashAlt,
faHdd,
faEllipsisH,
faPencilAlt,
} from '@fortawesome/free-solid-svg-icons'
faLock,
faLockOpen,
faDownload,
faUserFriends,
faCheck,
faLink,
faUserEdit,
faUser,
faFileAudio,
faFileVideo,
faSyncAlt,
faShare,
faHome,
faEyeSlash,
faBars,
faSearch,
faEllipsisV,
faChevronLeft,
faChevronRight,
faChevronDown,
faUpload,
faFolderPlus,
faTh,
faThList,
faInfo,
faFolder,
faFile,
faFileImage,
faTimes,
faSort,
faTrashAlt,
faHdd,
faEllipsisH,
faPencilAlt,
} from "@fortawesome/free-solid-svg-icons";
library.add(
faLock,
faLockOpen,
faDownload,
faUserFriends,
faCheck,
faLink,
faUserEdit,
faUser,
faFileAudio,
faFileVideo,
faHdd,
faSyncAlt,
faShare,
faHome,
faEyeSlash,
faBars,
faSearch,
faEllipsisV,
faChevronLeft,
faChevronRight,
faChevronDown,
faUpload,
faTrashAlt,
faFolderPlus,
faTh,
faThList,
faInfo,
faFolder,
faFile,
faFileImage,
faTimes,
faSort,
faEllipsisH,
faPencilAlt,
)
Vue.component('FontAwesomeIcon', FontAwesomeIcon)
faLock,
faLockOpen,
faDownload,
faUserFriends,
faCheck,
faLink,
faUserEdit,
faUser,
faFileAudio,
faFileVideo,
faHdd,
faSyncAlt,
faShare,
faHome,
faEyeSlash,
faBars,
faSearch,
faEllipsisV,
faChevronLeft,
faChevronRight,
faChevronDown,
faUpload,
faTrashAlt,
faFolderPlus,
faTh,
faThList,
faInfo,
faFolder,
faFile,
faFileImage,
faTimes,
faSort,
faEllipsisH,
faPencilAlt
);
Vue.component("FontAwesomeIcon", FontAwesomeIcon);
Vue.use(VueRouter)
Vue.use(Helpers)
Vue.use(VueRouter);
Vue.use(Helpers);
Vue.config.productionTip = false
Vue.config.productionTip = false;
var vueFileManager = new Vue({
i18n,
store,
router,
data: {
config,
},
render: h => h(App)
}).$mount('#app')
i18n,
store,
router,
data: {
config,
},
render: (h) => h(App),
}).$mount("#app");