Skip to content

Commit df274eb

Browse files
committed
fix img (Authorization), thumbnails lazy load
1 parent ccbd010 commit df274eb

File tree

9 files changed

+1406
-999
lines changed

9 files changed

+1406
-999
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "laravel-file-manager",
3-
"version": "2.3.1",
3+
"version": "2.3.2",
44
"description": "File manager for Laravel",
55
"keywords": [
66
"laravel",

src/components/manager/GridView.vue

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,13 @@
3232
v-on:dblclick="selectAction(file.path, file.extension)"
3333
v-on:contextmenu.prevent="contextMenu(file, $event)">
3434
<div class="fm-item-icon">
35-
<template v-if="acl && file.acl === 0">
36-
<i class="fas fa-unlock-alt fa-5x pb-2"></i>
37-
</template>
38-
<template v-else-if="thisImage(file.extension)">
39-
<img class="img-thumbnail"
40-
v-bind:alt="file.filename"
41-
v-bind:src="createImgUrl(file.path, file.timestamp)">
42-
</template>
43-
<template v-else>
44-
<i class="far fa-5x pb-2"
45-
v-bind:class="extensionToIcon(file.extension)"></i>
46-
</template>
35+
<i v-if="acl && file.acl === 0" class="fas fa-unlock-alt fa-5x pb-2"></i>
36+
<thumbnail v-else-if="thisImage(file.extension)"
37+
v-bind:disk="disk"
38+
v-bind:file="file">
39+
</thumbnail>
40+
<i v-else class="far fa-5x pb-2"
41+
v-bind:class="extensionToIcon(file.extension)"></i>
4742
</div>
4843
<div class="fm-item-info">
4944
{{ `${file.filename}.${file.extension}` }}
@@ -59,9 +54,11 @@
5954
import translate from './../../mixins/translate';
6055
import helper from './../../mixins/helper';
6156
import managerHelper from './mixins/manager';
57+
import Thumbnail from './Thumbnail.vue';
6258
6359
export default {
6460
name: 'grid-view',
61+
components: { Thumbnail },
6562
mixins: [translate, helper, managerHelper],
6663
data() {
6764
return {
@@ -101,16 +98,6 @@ export default {
10198
10299
return this.imageExtensions.includes(extension.toLowerCase());
103100
},
104-
105-
/**
106-
* Create url for image
107-
* @param path
108-
* @param timestamp
109-
* @returns {string}
110-
*/
111-
createImgUrl(path, timestamp) {
112-
return `${this.$store.getters['fm/settings/baseUrl']}thumbnails?disk=${this.disk}&path=${path}&v=${timestamp}`;
113-
},
114101
},
115102
};
116103
</script>
@@ -141,7 +128,8 @@ export default {
141128
cursor: pointer;
142129
}
143130
144-
.fm-item-icon > i {
131+
.fm-item-icon > i,
132+
.fm-item-icon > figure > i {
145133
color: #6d757d;
146134
}
147135

src/components/manager/Thumbnail.vue

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<template>
2+
<figure class="fm-thumbnail">
3+
<transition name="fade" mode="out-in">
4+
<i v-if="!src" class="far fa-file-image fa-5x pb-2"></i>
5+
<img v-else
6+
v-bind:src="src"
7+
v-bind:alt="file.filename"
8+
class="img-thumbnail">
9+
</transition>
10+
</figure>
11+
</template>
12+
13+
<script>
14+
import GET from './../../http/get';
15+
16+
export default {
17+
name: 'Thumbnail',
18+
data() {
19+
return {
20+
src: '',
21+
};
22+
},
23+
props: {
24+
disk: {
25+
type: String,
26+
required: true,
27+
},
28+
file: {
29+
type: Object,
30+
required: true,
31+
},
32+
},
33+
watch: {
34+
'file.timestamp': 'loadImage',
35+
},
36+
mounted() {
37+
if (window.IntersectionObserver) {
38+
const observer = new IntersectionObserver(
39+
(entries, obs) => {
40+
entries.forEach((entry) => {
41+
if (entry.isIntersecting) {
42+
this.loadImage();
43+
obs.unobserve(this.$el);
44+
}
45+
});
46+
},
47+
{
48+
root: null,
49+
threshold: '0.5',
50+
},
51+
);
52+
53+
// add observer for template
54+
observer.observe(this.$el);
55+
} else {
56+
this.loadImage();
57+
}
58+
},
59+
computed: {
60+
/**
61+
* Authorization required
62+
* @return {*}
63+
*/
64+
auth() {
65+
return this.$store.getters['fm/settings/authHeader'];
66+
},
67+
},
68+
methods: {
69+
/**
70+
* Load image
71+
*/
72+
loadImage() {
73+
// if authorization required
74+
if (this.auth) {
75+
GET.thumbnail(
76+
this.disk,
77+
this.file.path,
78+
).then((response) => {
79+
const mimeType = response.headers['content-type'].toLowerCase();
80+
const imgBase64 = Buffer.from(response.data, 'binary').toString('base64');
81+
82+
this.src = `data:${mimeType};base64,${imgBase64}`;
83+
});
84+
} else {
85+
this.src = `${this.$store.getters['fm/settings/baseUrl']}thumbnails?disk=${this.disk}&path=${encodeURIComponent(this.file.path)}&v=${this.file.timestamp}`;
86+
}
87+
},
88+
},
89+
};
90+
</script>
91+
92+
<style lang="scss">
93+
.fm-thumbnail {
94+
display: flex;
95+
justify-content: center;
96+
align-items: center;
97+
98+
.img-thumbnail {
99+
width: 88px;
100+
height: 88px;
101+
}
102+
103+
.fade-enter-active, .fade-leave-active {
104+
transition: opacity .3s;
105+
}
106+
.fade-enter, .fade-leave-to {
107+
opacity: 0;
108+
}
109+
}
110+
</style>

src/components/modals/additions/Cropper.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div class="fm-additions-cropper">
33
<div class="row" v-bind:style="{'max-height': maxHeight+'px'}">
44
<div class="col-sm-9 cropper-block">
5-
<img v-bind:src="imgUrl"
5+
<img v-bind:src="imgSrc"
66
ref="fmCropper"
77
v-bind:alt="selectedItem.basename">
88
</div>
@@ -136,7 +136,7 @@ export default {
136136
name: 'Cropper',
137137
mixins: [translate],
138138
props: {
139-
imgUrl: { type: String, required: true },
139+
imgSrc: { required: true },
140140
maxHeight: { type: Number, required: true },
141141
},
142142
data() {

src/components/modals/views/Preview.vue

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@
1111
</div>
1212
<div class="modal-body text-center">
1313
<template v-if="showCropperModule">
14-
<cropper-module v-bind:imgUrl="imgUrl"
14+
<cropper-module v-bind:imgSrc="imgSrc"
1515
v-bind:maxHeight="maxHeight"
1616
v-on:closeCropper="closeCropper"></cropper-module>
1717
</template>
18-
<template v-else>
19-
<img v-bind:src="imgUrl"
18+
<transition v-else name="fade" mode="out-in">
19+
<i v-if="!imgSrc" class="fas fa-spinner fa-spin fa-5x p-5 text-muted"></i>
20+
<img v-else
21+
v-bind:src="imgSrc"
2022
v-bind:alt="selectedItem.basename"
2123
v-bind:style="{'max-height': maxHeight+'px'}">
22-
</template>
24+
</transition>
2325
</div>
2426
<div v-if="showFooter" class="d-flex justify-content-between">
2527
<span class="d-block">
@@ -40,6 +42,7 @@ import CropperModule from './../additions/Cropper.vue';
4042
import modal from './../mixins/modal';
4143
import translate from './../../../mixins/translate';
4244
import helper from './../../../mixins/helper';
45+
import GET from './../../../http/get';
4346
4447
export default {
4548
name: 'Preview',
@@ -48,14 +51,21 @@ export default {
4851
data() {
4952
return {
5053
showCropperModule: false,
51-
imgUrl: '',
54+
imgSrc: '',
5255
};
5356
},
5457
created() {
55-
// Create image URL
56-
this.setImgUrl();
58+
this.loadImage();
5759
},
5860
computed: {
61+
/**
62+
* Authorization required
63+
* @return {*}
64+
*/
65+
auth() {
66+
return this.$store.getters['fm/settings/authHeader'];
67+
},
68+
5969
/**
6070
* Selected disk
6171
* @returns {*}
@@ -103,18 +113,31 @@ export default {
103113
},
104114
105115
/**
106-
* Set image URL
116+
* Close cropper
107117
*/
108-
setImgUrl() {
109-
this.imgUrl = `${this.$store.getters['fm/settings/baseUrl']}preview?disk=${this.selectedDisk}&path=${encodeURIComponent(this.selectedItem.path)}&v=${this.selectedItem.timestamp}`;
118+
closeCropper() {
119+
this.showCropperModule = false;
120+
this.loadImage();
110121
},
111122
112123
/**
113-
* Close cropper
124+
* Load image
114125
*/
115-
closeCropper() {
116-
this.showCropperModule = false;
117-
this.setImgUrl();
126+
loadImage() {
127+
// if authorization required
128+
if (this.auth) {
129+
GET.preview(
130+
this.selectedDisk,
131+
this.selectedItem.path,
132+
).then((response) => {
133+
const mimeType = response.headers['content-type'].toLowerCase();
134+
const imgBase64 = Buffer.from(response.data, 'binary').toString('base64');
135+
136+
this.imgSrc = `data:${mimeType};base64,${imgBase64}`;
137+
});
138+
} else {
139+
this.imgSrc = `${this.$store.getters['fm/settings/baseUrl']}preview?disk=${this.selectedDisk}&path=${encodeURIComponent(this.selectedItem.path)}&v=${this.selectedItem.timestamp}`;
140+
}
118141
},
119142
},
120143
};

src/http/get.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,30 @@ export default {
6767
getFile(disk, path) {
6868
return HTTP.get('download', { params: { disk, path } });
6969
},
70+
71+
/**
72+
* Image thumbnail
73+
* @param disk
74+
* @param path
75+
* @returns {*}
76+
*/
77+
thumbnail(disk, path) {
78+
return HTTP.get('thumbnails', {
79+
responseType: 'arraybuffer',
80+
params: { disk, path },
81+
});
82+
},
83+
84+
/**
85+
* Image preview
86+
* @param disk
87+
* @param path
88+
* @return {*}
89+
*/
90+
preview(disk, path) {
91+
return HTTP.get('preview', {
92+
responseType: 'arraybuffer',
93+
params: { disk, path },
94+
});
95+
},
7096
};

src/store/settings/getters.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,13 @@ export default {
1616
headers(state) {
1717
return state.headers;
1818
},
19+
20+
/**
21+
* Headers has Authorization
22+
* @param state
23+
* @return {boolean}
24+
*/
25+
authHeader(state) {
26+
return Object.prototype.hasOwnProperty.call(state.headers, 'Authorization');
27+
},
1928
};

src/store/settings/store.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default {
1414
acl: null,
1515

1616
// App version
17-
version: '2.3.1',
17+
version: '2.3.2',
1818

1919
// axios headers
2020
headers: null,

0 commit comments

Comments
 (0)