From 7d1992b081c62a069ce4385174a7cc391fb8ed23 Mon Sep 17 00:00:00 2001
From: Muah
Date: Tue, 16 Jan 2018 09:30:57 +0200
Subject: [PATCH] v2.5.0
- image editor https://github.com/ctf0/Laravel-Media-Manager/wiki/Optimize-Edited-Images-On-Save
- update readme
- update wiki
- update resources
---
README.md | 45 +-
logs/v2.4.1.txt | 5 -
logs/v2.5.0.txt | 9 +
src/Controllers/MediaController.php | 99 ++++-
src/MediaRoutes.php | 3 +
.../js/components/ImageEditor/caman.vue | 84 ++++
.../js/components/ImageEditor/cropper.vue | 397 ++++++++++++++++++
src/resources/assets/js/components/media.vue | 51 ++-
.../assets/js/components/modules/cache.js | 6 +-
.../assets/js/components/modules/form.js | 79 +++-
.../assets/js/components/modules/selected.js | 8 +-
.../assets/js/components/modules/utils.js | 14 +-
.../assets/js/components/modules/watch.js | 8 +-
src/resources/assets/js/icons.js | 60 +++
src/resources/assets/js/manager.js | 34 +-
src/resources/assets/sass/media.scss | 149 ++++++-
.../assets/sass/packages/switcher.scss | 2 +-
src/resources/lang/en/messages.php | 15 +-
src/resources/views/_manager.blade.php | 167 ++++++--
src/resources/views/cards/vertical.blade.php | 8 +-
20 files changed, 1089 insertions(+), 154 deletions(-)
delete mode 100644 logs/v2.4.1.txt
create mode 100644 logs/v2.5.0.txt
create mode 100644 src/resources/assets/js/components/ImageEditor/caman.vue
create mode 100644 src/resources/assets/js/components/ImageEditor/cropper.vue
create mode 100644 src/resources/assets/js/icons.js
diff --git a/README.md b/README.md
index 683adae..db678c8 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
The only media manager with this number of features & flexibility.
-
+
- to optimize uploaded files on the fly try [approached](https://github.com/approached/laravel-image-optimizer) or [spatie](https://github.com/spatie/laravel-image-optimizer)
@@ -31,15 +31,15 @@ The only media manager with this number of features & flexibility.
`php artisan vendor:publish --provider="ctf0\MediaManager\MediaManagerServiceProvider"`
- after installation, package will auto-add
- + package routes to `routes/web.php`
- + package assets compiling to `webpack.mix.js`
+ + package routes to `routes/web.php`
+ + package assets compiling to `webpack.mix.js`
- install dependencies
```bash
-yarn add vue vue-ls vue-multi-ref vue-tippy@v1 vue2-filters vue-bounty vue-notif vue-clipboard2 vue-awesome vue-touch@next axios dropzone keycode babel-preset-es2015-node6 babel-preset-stage-2
+yarn add vue vue-ls vue-multi-ref vue-tippy@v1 vue2-filters vue-bounty vue-notif vue-clipboard2 vue-awesome vue-touch@next axios dropzone cropperjs keycode babel-preset-es2015-node6 babel-preset-stage-2
# or
-npm install vue vue-ls vue-multi-ref vue-tippy@v1 vue2-filters vue-bounty vue-notif vue-clipboard2 vue-awesome vue-touch@next axios dropzone keycode babel-preset-es2015-node6 babel-preset-stage-2 --save
+npm install vue vue-ls vue-multi-ref vue-tippy@v1 vue2-filters vue-bounty vue-notif vue-clipboard2 vue-awesome vue-touch@next axios dropzone cropperjs keycode babel-preset-es2015-node6 babel-preset-stage-2 --save
```
- add this one liner to your main js file and run `npm run watch` to compile your `js/css` files.
@@ -57,12 +57,13 @@ new Vue({
## Features
+- [image editor](https://github.com/ctf0/Laravel-Media-Manager/wiki/Image-Editor)
- multi
+ upload
+ move/copy
+ delete
+- upload an image from a url
- bulk selection
-- restrict access to [folders](https://github.com/ctf0/Laravel-Media-Manager/wiki/Folder-Restriction)
- dynamically hide [files](https://github.com/ctf0/Laravel-Media-Manager/wiki/Hide-Files-With-Extension)
- dynamically hide [folders](https://github.com/ctf0/Laravel-Media-Manager/wiki/Hide-Folders)
- toggle between `random names` & `original names` for uploaded files
@@ -100,8 +101,9 @@ new Vue({
| navigation | button | keyboard | click / tap | touch |
|----------------|--------------------------------------------|---------------|--------------------------|-------------------------|
| | upload *(toolbar)* | u | * | |
- | | refresh *(toolbar)* | r | * / hold *"clear cache"* | |
+ | | refresh *(toolbar)* | r | * / hold *(clear cache)* | |
| | move *(toolbar)* | m | * | swipe up |
+ | | editor *(toolbar)* | e | * | |
| | delete *(toolbar)* | d/del | * | swipe down |
| | lock/unlock *(toolbar)* | l | * | |
| | (reset) bulk select *(toolbar)* | b | * | |
@@ -120,6 +122,7 @@ new Vue({
| | | | | |
| | limit bulk select *(files container)* | shift + click | | |
| | preview image/pdf/text *(files container)* | space | ** | |
+ | | image editor *(files container)* | | hold | |
| | hide *(preview)* | space/esc | * | |
| select next | | right / down | * | swipe left *(preview)* |
| select prev | | left / up | * | swipe right *(preview)* |
@@ -130,17 +133,23 @@ new Vue({
- events
- | type | event-name | description |
- |---------|---------------------------------------|------------------------------------------|
- | [JS](https://github.com/gocanto/vuemit) |
- | | modal-show | when modal is showen |
- | | modal-hide | when modal is hidden |
- | | file_selected *(when inside modal)* | get selected file url |
- | [Laravel](https://laravel.com/docs/5.5/events#manually-registering-events) |
- | | MMFileUploaded($file_path) | get uploaded file full path |
- | | MMFileDeleted($file_path, $is_folder) | get deleted file/folder full path |
- | | MMFileRenamed($old_path, $new_path) | get renamed file/folder "old & new" path |
- | | MMFileMoved($old_path, $new_path) | get moved file/folder "old & new" path |
+ | type | event-name | description |
+ |-----------------|---------------------------------------|--------------------------------------------------|
+ | [JS][js] | | |
+ | | modal-show | when modal is showen |
+ | | modal-hide | when modal is hidden |
+ | | file_selected *(when inside modal)* | get selected file url |
+ | [Laravel][lara] | | |
+ | | MMFileUploaded($file_path) | get uploaded file full [path][path] |
+ | | [MMFileSaved][event]($file_path) | get saved(edited/link) image full [path][path] |
+ | | MMFileDeleted($file_path, $is_folder) | get deleted file/folder full [path][path] |
+ | | MMFileRenamed($old_path, $new_path) | get renamed file/folder "old & new" [path][path] |
+ | | MMFileMoved($old_path, $new_path) | get moved file/folder "old & new" [path][path] |
+
+[js]: https://github.com/gocanto/vuemit
+[lara]: https://laravel.com/docs/5.5/events#manually-registering-events
+[event]: https://github.com/ctf0/Laravel-Media-Manager/wiki/Image-Editor#optimize-edited-images-on-save
+[path]: https://gist.github.com/ctf0/9fa6013954654384052d2e2e809b9bf6
diff --git a/logs/v2.4.1.txt b/logs/v2.4.1.txt
deleted file mode 100644
index 3243ed1..0000000
--- a/logs/v2.4.1.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-- limit swipe “up & down” to the file it self instead of the whole container, so now you can swipe as usual without getting the modal showing up.
-- add press & hold for refresh button so you can clear the manager browser storage/cache.
-- disable file preview from sidebar in bulk selection
-- update rdme
-- update resources
\ No newline at end of file
diff --git a/logs/v2.5.0.txt b/logs/v2.5.0.txt
new file mode 100644
index 0000000..a12764d
--- /dev/null
+++ b/logs/v2.5.0.txt
@@ -0,0 +1,9 @@
+- added full image editor https://github.com/ctf0/Laravel-Media-Manager/wiki/Image-Editor
+- you can now save images from url too, it follows the same flow as the normal upload (random names, upload to current folder, events)
+- added new event "MMFileSaved" for both of the above features.
+- fix setting selected file twice
+- fix clearing cache twice
+
+- update readme
+- update wiki
+- update resources
\ No newline at end of file
diff --git a/src/Controllers/MediaController.php b/src/Controllers/MediaController.php
index f892ba7..723d942 100755
--- a/src/Controllers/MediaController.php
+++ b/src/Controllers/MediaController.php
@@ -127,7 +127,7 @@ public function upload(Request $request)
$original = $one->getClientOriginalName();
$name_only = pathinfo($original, PATHINFO_FILENAME);
$ext_only = pathinfo($original, PATHINFO_EXTENSION);
- $file_name = $random_name ? uniqid() . ".$ext_only" : $this->cleanName($name_only, null) . ".$ext_only";
+ $file_name = $random_name ? $this->sanitizedText . ".$ext_only" : $this->cleanName($name_only, null) . ".$ext_only";
$file_type = $one->getMimeType();
$destination = "$upload_path/$file_name";
@@ -149,13 +149,11 @@ public function upload(Request $request)
event('MMFileUploaded', $this->getFilePath($saved_name));
$result[] = [
- 'path' => preg_replace('/^public\//', '', $saved_name),
'success' => true,
'message' => $file_name,
];
} catch (Exception $e) {
$result[] = [
- 'path' => '',
'success' => false,
'message' => "\"$file_name\" " . $e->getMessage(),
];
@@ -165,6 +163,101 @@ public function upload(Request $request)
return response()->json(['data'=>$result]);
}
+ /**
+ * save cropped image.
+ *
+ * @param Request $request [description]
+ *
+ * @return [type] [description]
+ */
+ public function uploadCropped(Request $request)
+ {
+ $path = $request->path;
+ $data = explode(',', $request->data)[1];
+ $original = $request->name;
+
+ $name_only = pathinfo($original, PATHINFO_FILENAME) . '_' . $this->sanitizedText;
+ $ext_only = pathinfo($original, PATHINFO_EXTENSION);
+ $file_name = "$name_only.$ext_only";
+ $destination = "$path/$file_name";
+
+ try {
+ // check existence
+ if ($this->storageDisk->exists($destination)) {
+ throw new Exception(trans('MediaManager::messages.error_already_exists'));
+ }
+
+ // save file
+ $this->storageDisk->put($destination, base64_decode($data));
+
+ // fire event
+ event('MMFileSaved', $this->getFilePath($destination));
+
+ $result = [
+ 'success' => true,
+ 'message' => $file_name,
+ ];
+ } catch (Exception $e) {
+ $result = [
+ 'success' => false,
+ 'message' => "\"$file_name\" " . $e->getMessage(),
+ ];
+ }
+
+ return response()->json($result);
+ }
+
+ /**
+ * save image from link.
+ *
+ * @param Request $request [description]
+ *
+ * @return [type] [description]
+ */
+ public function uploadLink(Request $request)
+ {
+ $url = $request->url;
+ $path = $request->path;
+ $random_name = $request->random_names;
+
+ $original = substr($url, strrpos($url, '/') + 1);
+ $name_only = pathinfo($original, PATHINFO_FILENAME);
+ $ext_only = pathinfo($original, PATHINFO_EXTENSION);
+ $file_name = $random_name ? $this->sanitizedText . ".$ext_only" : $this->cleanName($name_only, null) . ".$ext_only";
+ $destination = "$path/$file_name";
+ $file_type = image_type_to_mime_type(exif_imagetype($url));
+
+ try {
+ // check for mime type
+ if (str_contains($file_type, $this->unallowedMimes)) {
+ throw new Exception(trans('MediaManager::messages.not_allowed_file_ext', ['attr'=>$file_type]));
+ }
+
+ // check existence
+ if ($this->storageDisk->exists($destination)) {
+ throw new Exception(trans('MediaManager::messages.error_already_exists'));
+ }
+
+ // save file
+ $this->storageDisk->put($destination, file_get_contents($url));
+
+ // fire event
+ event('MMFileSaved', $this->getFilePath($destination));
+
+ $result = [
+ 'success' => true,
+ 'message' => $file_name,
+ ];
+ } catch (Exception $e) {
+ $result = [
+ 'success' => false,
+ 'message' => "\"$file_name\" " . $e->getMessage(),
+ ];
+ }
+
+ return response()->json($result);
+ }
+
/**
* create new folder.
*
diff --git a/src/MediaRoutes.php b/src/MediaRoutes.php
index ee1fcc5..d1bd56c 100644
--- a/src/MediaRoutes.php
+++ b/src/MediaRoutes.php
@@ -16,6 +16,9 @@ public static function routes()
], function () use ($controller) {
Route::get('/', ['uses' => "$controller@index", 'as' => 'index']);
Route::post('upload', ['uses' => "$controller@upload", 'as' => 'upload']);
+ Route::post('upload-cropped', ['uses' => "$controller@uploadCropped", 'as' => 'uploadCropped']);
+ Route::post('upload-link', ['uses' => "$controller@uploadLink", 'as' => 'uploadLink']);
+
Route::post('files', ['uses' => "$controller@get_files", 'as' => 'files']);
Route::post('directories', ['uses' => "$controller@get_dirs", 'as' => 'directories']);
Route::post('new_folder', ['uses' => "$controller@new_folder", 'as' => 'new_folder']);
diff --git a/src/resources/assets/js/components/ImageEditor/caman.vue b/src/resources/assets/js/components/ImageEditor/caman.vue
new file mode 100644
index 0000000..4edad40
--- /dev/null
+++ b/src/resources/assets/js/components/ImageEditor/caman.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/resources/assets/js/components/ImageEditor/cropper.vue b/src/resources/assets/js/components/ImageEditor/cropper.vue
new file mode 100644
index 0000000..3f0e7fd
--- /dev/null
+++ b/src/resources/assets/js/components/ImageEditor/cropper.vue
@@ -0,0 +1,397 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/resources/assets/js/components/media.vue b/src/resources/assets/js/components/media.vue
index 8fbf801..ecc20f9 100644
--- a/src/resources/assets/js/components/media.vue
+++ b/src/resources/assets/js/components/media.vue
@@ -12,9 +12,10 @@ import Watchers from './modules/watch'
import Computed from './modules/computed'
import Bounty from 'vue-bounty'
+import Cropper from './ImageEditor/cropper.vue'
export default {
- components: {Bounty},
+ components: {Bounty, Cropper},
name: 'media-manager',
mixins: [
Utilities,
@@ -45,11 +46,11 @@ export default {
],
data() {
return {
- isLoading: false,
no_files: false,
loading_files: false,
no_search: false,
ajax_error: false,
+ isLoading: false,
toggleInfo: true,
toggleUploadArea: false,
showProgress: false,
@@ -63,6 +64,7 @@ export default {
randomNames: false,
useCopy: false,
toolBar: true,
+ imageWasEdited: false,
files: [],
folders: [],
@@ -78,11 +80,12 @@ export default {
currentFilterName: null,
searchItemsCount: null,
searchFor: null,
- new_folder_name: null,
- new_filename: null,
- active_modal: null,
+ urlToUpload: null,
+ newFolderName: null,
+ newFilename: null,
+ activeModal: null,
- navDirection: null,
+ imageSlideDirection: null,
uploadPanelGradients: [
'linear-gradient(141deg, #009e6c 0%, #00d1b2 71%, #00e7eb 100%)',
'linear-gradient(141deg, #04a6d7 0%, #209cee 71%, #3287f5 100%)',
@@ -106,6 +109,11 @@ export default {
},
mounted() {
this.fileUpload()
+
+ // check if image was edited
+ EventHub.listen('image-edited', () => {
+ this.imageWasEdited = true
+ })
},
updated() {
this.autoPlay()
@@ -133,7 +141,7 @@ export default {
shortCuts(e) {
if (!(this.isLoading || e.altKey || e.ctrlKey || e.metaKey)) {
// when modal isnt visible
- if (!this.active_modal) {
+ if (!this.activeModal) {
// when search is not focused
if (document.activeElement.dataset.search == undefined) {
// when no bulk selecting
@@ -181,6 +189,11 @@ export default {
this.toggleModal('preview_modal')
}
}
+
+ // image editor
+ if (keycode(e) == 'e') {
+ this.imageEditor()
+ }
}
// end of when there are files
@@ -285,7 +298,20 @@ export default {
/* end of short cuts */
refresh() {
- this.getFiles(this.folders, null, this.selectedFile.name)
+ this.getFiles(this.folders, null, this.selectedFile ? this.selectedFile.name : null)
+ },
+ moveItem() {
+ if (this.$refs.move[0].disabled) {
+ return
+ }
+
+ this.toggleModal('move_file_modal')
+ },
+ renameItem() {
+ this.toggleModal('rename_file_modal')
+ },
+ imageEditor() {
+ return this.$refs['image_editor'].click()
},
deleteItem() {
if (this.$refs.delete[0].disabled) {
@@ -308,16 +334,7 @@ export default {
this.toggleModal('confirm_delete_modal')
},
- moveItem() {
- if (this.$refs.move[0].disabled) {
- return
- }
- this.toggleModal('move_file_modal')
- },
- renameItem() {
- this.toggleModal('rename_file_modal')
- },
blkSlct() {
this.bulkSelect = !this.bulkSelect
this.bulkSelectAll = false
diff --git a/src/resources/assets/js/components/modules/cache.js b/src/resources/assets/js/components/modules/cache.js
index 8a4196a..37a8715 100644
--- a/src/resources/assets/js/components/modules/cache.js
+++ b/src/resources/assets/js/components/modules/cache.js
@@ -32,11 +32,13 @@ export default {
: cacheName == 'root_' ? `/${destination}` : `${cacheName}${destination}`
}
- let items = destination ? [extra, cacheName] : [cacheName]
+ let items = destination
+ ? extra == cacheName ? [cacheName] : [extra, cacheName]
+ : [cacheName]
items.forEach((one) => {
return localforage.removeItem(one).then(() => {
- console.log(`${one} is cleared!`)
+ console.log(`${one} cache is cleared!`)
}).catch((err) => {
console.warn('localforage.removeItem', err)
})
diff --git a/src/resources/assets/js/components/modules/form.js b/src/resources/assets/js/components/modules/form.js
index 093db42..2b4041e 100644
--- a/src/resources/assets/js/components/modules/form.js
+++ b/src/resources/assets/js/components/modules/form.js
@@ -62,10 +62,55 @@ export default {
})
},
+ // upload image from link
+ saveLinkForm(event) {
+ let url = this.urlToUpload
+
+ if (!url) {
+ return this.showNotif(this.trans('no_val'), 'warning')
+ }
+
+ this.toggleUploadArea = false
+ this.toggleLoading()
+ this.loadingFiles('show')
+
+ axios.post(event.target.action, {
+ path: this.files.path,
+ url: url,
+ random_names: this.randomNames
+ }).then(({data}) => {
+ this.toggleLoading()
+ this.loadingFiles('hide')
+
+ if (!data.success) {
+ return this.showNotif(data.message, 'danger')
+ }
+
+ this.resetInput('urlToUpload')
+ this.$nextTick(() => {
+ this.$refs.save_link_modal_input.focus()
+ })
+
+ this.showNotif(`${this.trans('save_success')} "${data.message}"`)
+ this.removeCachedResponse('../')
+ this.getFiles(this.folders, null, data.message)
+
+ }).catch((err) => {
+ console.error(err)
+ this.toggleLoading()
+ this.toggleModal()
+ this.loadingFiles('hide')
+ this.resetInput('urlToUpload')
+
+ this.ajaxError()
+ })
+ },
+
/* Main */
getFiles(folders = '/', prev_folder = null, prev_file = null) {
this.resetInput(['searchFor', 'sortBy', 'currentFilterName', 'selectedFile', 'currentFileIndex'])
+
this.noFiles('hide')
if (!this.loading_files) {
this.toggleInfo = false
@@ -149,11 +194,6 @@ export default {
})
}
- // we have files
- if (this.allItemsCount) {
- this.selectFirst()
- }
-
this.$nextTick(() => {
// check for prev opened folder
if (prev_folder) {
@@ -172,6 +212,11 @@ export default {
}
})
}
+
+ // if prevs not found
+ if (!this.selectedFile) {
+ this.selectFirst()
+ }
})
// we have files
@@ -226,7 +271,7 @@ export default {
/* Tool-Bar */
NewFolderForm(event) {
- let folder_name = this.new_folder_name
+ let folder_name = this.newFolderName
if (!folder_name) {
return this.showNotif(this.trans('no_val'), 'warning')
@@ -240,19 +285,19 @@ export default {
axios.post(event.target.action, {
current_path: this.files.path,
- new_folder_name: folder_name
+ newFolderName: folder_name
}).then(({data}) => {
this.toggleLoading()
- this.resetInput('new_folder_name')
+ this.resetInput('newFolderName')
this.toggleModal()
if (!data.success) {
return this.showNotif(data.message, 'danger')
}
- this.showNotif(`${this.trans('create_success')} "${data.new_folder_name}" at "${data.full_path}"`)
- this.removeCachedResponse('../')
- this.getFiles(this.folders, data.new_folder_name)
+ this.showNotif(`${this.trans('create_success')} "${data.newFolderName}" at "${data.full_path}"`)
+ this.removeCachedResponse()
+ this.getFiles(this.folders, data.newFolderName)
}).catch((err) => {
console.error(err)
@@ -262,7 +307,7 @@ export default {
// rename
RenameFileForm(event) {
- let changed = this.new_filename
+ let changed = this.newFilename
if (!changed) {
return this.showNotif(this.trans('no_val'), 'warning')
@@ -276,12 +321,12 @@ export default {
let filename = this.selectedFile.name
let ext = this.getExtension(filename)
- let new_filename = ext == null ? changed : `${changed}.${ext}`
+ let newFilename = ext == null ? changed : `${changed}.${ext}`
axios.post(event.target.action, {
folder_location: this.files.path,
filename: filename,
- new_filename: new_filename
+ newFilename: newFilename
}).then(({data}) => {
this.toggleLoading()
this.toggleModal()
@@ -290,8 +335,8 @@ export default {
return this.showNotif(data.message, 'danger')
}
- this.showNotif(`${this.trans('rename_success')} "${filename}" to "${data.new_filename}"`)
- this.updateItemName(this.selectedFile, filename, data.new_filename)
+ this.showNotif(`${this.trans('rename_success')} "${filename}" to "${data.newFilename}"`)
+ this.updateItemName(this.selectedFile, filename, data.newFilename)
this.removeCachedResponse()
if (this.selectedFileIs('folder')) {
@@ -401,7 +446,7 @@ export default {
})
this.$refs['success-audio'].play()
- this.removeCachedResponse()
+ this.removeCachedResponse('../')
this.toggleModal()
this.isBulkSelecting() ? this.blkSlct() : this.selectFirst()
diff --git a/src/resources/assets/js/components/modules/selected.js b/src/resources/assets/js/components/modules/selected.js
index f4cd35e..2ca9120 100644
--- a/src/resources/assets/js/components/modules/selected.js
+++ b/src/resources/assets/js/components/modules/selected.js
@@ -131,7 +131,7 @@ export default {
// go to last item
if (keycode(e) == 'end') {
e.preventDefault()
- this.navDirection = 'next'
+ this.imageSlideDirection = 'next'
let last = this.filesList.length - 1
this.scrollToFile(this.$refs[`file_${last}`])
@@ -140,7 +140,7 @@ export default {
// go to first item
if (keycode(e) == 'home') {
e.preventDefault()
- this.navDirection = 'prev'
+ this.imageSlideDirection = 'prev'
this.scrollToFile(this.$refs.file_0)
}
@@ -160,7 +160,7 @@ export default {
let curSelectedIndex = this.currentFileIndex
if (curSelectedIndex !== 0) {
- this.navDirection = 'prev'
+ this.imageSlideDirection = 'prev'
let newSelected = curSelectedIndex - 1
this.scrollToFile(this.$refs[`file_${newSelected}`])
}
@@ -169,7 +169,7 @@ export default {
let curSelectedIndex = this.currentFileIndex
if (curSelectedIndex < this.allItemsCount - 1) {
- this.navDirection = 'next'
+ this.imageSlideDirection = 'next'
let newSelected = curSelectedIndex + 1
this.scrollToFile(this.$refs[`file_${newSelected}`])
}
diff --git a/src/resources/assets/js/components/modules/utils.js b/src/resources/assets/js/components/modules/utils.js
index f17e9b4..5f79d12 100644
--- a/src/resources/assets/js/components/modules/utils.js
+++ b/src/resources/assets/js/components/modules/utils.js
@@ -4,7 +4,7 @@ export default {
return item == list[list.length - 1]
},
isActiveModal(el) {
- return this.active_modal == el
+ return this.activeModal == el
},
showNotif(msg, s = 'success') {
if (msg) {
@@ -50,7 +50,7 @@ export default {
},
/* Buttons */
- mv_dl() {
+ item_ops() {
if (this.isBulkSelecting()) {
return this.bulkListFilter.length == 0
}
@@ -103,13 +103,19 @@ export default {
/* Toggle */
toggleModal(selector = null) {
if (!selector) {
+ // refresh if an image was edited
+ if (this.activeModal == 'imageEditor_modal' && this.imageWasEdited) {
+ this.imageWasEdited = false
+ this.refresh()
+ }
+
this.noScroll('remove')
- this.resetInput('active_modal')
+ this.resetInput('activeModal')
return EventHub.fire('modal-hide')
}
this.noScroll('add')
- this.active_modal = selector
+ this.activeModal = selector
EventHub.fire('modal-show')
},
toggleLoading() {
diff --git a/src/resources/assets/js/components/modules/watch.js b/src/resources/assets/js/components/modules/watch.js
index 3e29618..51a26c6 100644
--- a/src/resources/assets/js/components/modules/watch.js
+++ b/src/resources/assets/js/components/modules/watch.js
@@ -37,7 +37,7 @@ export default {
this.bulkSelectAll = true
}
},
- active_modal(val) {
+ activeModal(val) {
if (val == 'new_folder_modal') {
this.$nextTick(() => {
return this.$refs.new_folder_modal_input.focus()
@@ -61,6 +61,12 @@ export default {
return this.$refs.confirm_delete_modal_submit.focus()
})
}
+
+ if (val == 'save_link_modal') {
+ this.$nextTick(() => {
+ return this.$refs.save_link_modal_input.focus()
+ })
+ }
},
showProgress(val) {
if (val) {
diff --git a/src/resources/assets/js/icons.js b/src/resources/assets/js/icons.js
new file mode 100644
index 0000000..0368518
--- /dev/null
+++ b/src/resources/assets/js/icons.js
@@ -0,0 +1,60 @@
+import 'vue-awesome/icons/shopping-basket'
+import 'vue-awesome/icons/folder'
+import 'vue-awesome/icons/refresh'
+import 'vue-awesome/icons/share'
+import 'vue-awesome/icons/terminal'
+import 'vue-awesome/icons/trash'
+import 'vue-awesome/icons/minus'
+import 'vue-awesome/icons/plus'
+import 'vue-awesome/icons/puzzle-piece'
+import 'vue-awesome/icons/image'
+import 'vue-awesome/icons/video-camera'
+import 'vue-awesome/icons/music'
+import 'vue-awesome/icons/times'
+import 'vue-awesome/icons/bell-o'
+import 'vue-awesome/icons/search'
+import 'vue-awesome/icons/angle-double-right'
+import 'vue-awesome/icons/angle-double-left'
+import 'vue-awesome/icons/bars'
+import 'vue-awesome/icons/clone'
+import 'vue-awesome/icons/file-pdf-o'
+import 'vue-awesome/icons/power-off'
+import 'vue-awesome/icons/file-text-o'
+import 'vue-awesome/icons/download'
+import 'vue-awesome/icons/warning'
+import 'vue-awesome/icons/archive'
+import 'vue-awesome/icons/unlock'
+import 'vue-awesome/icons/lock'
+import 'vue-awesome/icons/eye'
+import 'vue-awesome/icons/film'
+import 'vue-awesome/icons/object-ungroup'
+import 'vue-awesome/icons/circle'
+import 'vue-awesome/icons/link'
+import 'vue-awesome/icons/spinner'
+// cropperjs
+import 'vue-awesome/icons/crop'
+import 'vue-awesome/icons/ban'
+import 'vue-awesome/icons/check'
+import 'vue-awesome/icons/arrows'
+import 'vue-awesome/icons/search-plus'
+import 'vue-awesome/icons/search-minus'
+import 'vue-awesome/icons/rotate-left'
+import 'vue-awesome/icons/rotate-right'
+import 'vue-awesome/icons/arrows-h'
+import 'vue-awesome/icons/arrows-v'
+// camanjs
+import 'vue-awesome/icons/sun-o'
+import 'vue-awesome/icons/adjust'
+import 'vue-awesome/icons/eye-slash'
+import 'vue-awesome/icons/flash'
+import 'vue-awesome/icons/thermometer-half'
+import 'vue-awesome/icons/eyedropper'
+import 'vue-awesome/icons/lemon-o'
+import 'vue-awesome/icons/flask'
+import 'vue-awesome/icons/dot-circle-o'
+import 'vue-awesome/icons/scissors'
+import 'vue-awesome/icons/diamond'
+import 'vue-awesome/icons/filter'
+import 'vue-awesome/icons/cube'
+import 'vue-awesome/icons/shield'
+Vue.component('icon', require('vue-awesome/components/Icon'))
diff --git a/src/resources/assets/js/manager.js b/src/resources/assets/js/manager.js
index 9af4b34..2bbfcbc 100644
--- a/src/resources/assets/js/manager.js
+++ b/src/resources/assets/js/manager.js
@@ -3,6 +3,9 @@ window.Vue = require('vue')
window.EventHub = require('vuemit')
window.keycode = require('keycode')
window.Dropzone = require('dropzone')
+window.Cropper = require('cropperjs')
+import 'cropperjs/dist/cropper.css'
+
Vue.use(require('vue2-filters'))
Vue.use(require('vue-clipboard2'))
Vue.use(require('vue-ls'))
@@ -58,36 +61,7 @@ localforage.config({
})
// vue-awesome
-import 'vue-awesome/icons/shopping-basket'
-import 'vue-awesome/icons/folder'
-import 'vue-awesome/icons/refresh'
-import 'vue-awesome/icons/share'
-import 'vue-awesome/icons/terminal'
-import 'vue-awesome/icons/trash'
-import 'vue-awesome/icons/minus'
-import 'vue-awesome/icons/plus'
-import 'vue-awesome/icons/puzzle-piece'
-import 'vue-awesome/icons/image'
-import 'vue-awesome/icons/video-camera'
-import 'vue-awesome/icons/music'
-import 'vue-awesome/icons/times'
-import 'vue-awesome/icons/bell-o'
-import 'vue-awesome/icons/search'
-import 'vue-awesome/icons/angle-double-right'
-import 'vue-awesome/icons/angle-double-left'
-import 'vue-awesome/icons/bars'
-import 'vue-awesome/icons/clone'
-import 'vue-awesome/icons/file-pdf-o'
-import 'vue-awesome/icons/power-off'
-import 'vue-awesome/icons/file-text-o'
-import 'vue-awesome/icons/download'
-import 'vue-awesome/icons/warning'
-import 'vue-awesome/icons/archive'
-import 'vue-awesome/icons/unlock'
-import 'vue-awesome/icons/lock'
-import 'vue-awesome/icons/eye'
-import 'vue-awesome/icons/film'
-Vue.component('icon', require('vue-awesome/components/Icon'))
+require('./icons')
/* Components */
Vue.component('MediaManager', require('./components/media.vue'))
diff --git a/src/resources/assets/sass/media.scss b/src/resources/assets/sass/media.scss
index 095f759..870dbb7 100644
--- a/src/resources/assets/sass/media.scss
+++ b/src/resources/assets/sass/media.scss
@@ -76,12 +76,45 @@
color: $white;
}
- .form-switcher {
+ .form-switcher,
+ .save_link {
position: absolute;
z-index: 1;
+ }
+
+ .form-switcher {
bottom: 0.5rem;
left: 0.5rem;
}
+
+ .save_link {
+ right: 0.5rem;
+ bottom: 0;
+ filter: drop-shadow(0 0 0.1rem rgba($black, 0.3));
+
+ .circle,
+ .anchor {
+ transition: all 0.3s;
+ }
+
+ .circle {
+ color: $active_theme;
+ }
+
+ .anchor {
+ color: darken($active_theme, 50%);
+ }
+
+ &:hover {
+ .anchor {
+ color: $active_theme;
+ }
+
+ .circle {
+ color: darken($active_theme, 50%);
+ }
+ }
+ }
}
@include media('max', 1023) {
@@ -343,8 +376,13 @@
right: 3px;
width: 0.5rem;
height: 0.5rem;
- cursor: default;
+ padding: 0;
+ border: none;
border-radius: 100vw;
+
+ &:disabled {
+ cursor: not-allowed;
+ }
}
.__box-copy-link {
@@ -464,7 +502,6 @@
background-color: $active_theme;
.__sidebar-preview {
- overflow: hidden;
flex: 0 0 auto;
text-align: center;
color: darken($active_theme, 50%);
@@ -587,8 +624,6 @@
/* modal */
.__modal-preview {
- padding: 0.7rem 0.7rem 0;
-
* {
color: $black;
}
@@ -633,6 +668,93 @@
margin-top: 15px;
}
+.__modal-content-wrapper {
+ align-self: center;
+}
+
+/* Editor */
+$editorBtns: rgba(darken($active_theme, 70%), 0.8);
+
+.__modal-editor {
+ .__cropper {
+ min-width: unset;
+ background: transparent;
+ box-shadow: none;
+
+ .__cropper-top-toolbar,
+ .__cropper-side-toolbar,
+ .__cropper-bottom-toolbar {
+ display: flex;
+ align-self: flex-start;
+ background-color: $editorBtns;
+ }
+
+ .top,
+ .mid,
+ .bottom {
+ display: flex;
+ }
+
+ .mid {
+ .__cropper-side-toolbar {
+ flex-flow: column nowrap;
+ }
+
+ .card-image {
+ margin: 1rem 0 1rem 1rem;
+
+ .image {
+ max-width: 50vw;
+ max-height: 50vh;
+ }
+
+ img {
+ max-width: 100%;
+ }
+ }
+ }
+
+ .bottom {
+ justify-content: flex-end;
+ }
+ }
+
+ .__caman {
+ position: relative;
+
+ .__caman-controls {
+ position: absolute;
+ z-index: 1;
+ top: -82px;
+ left: 0;
+ background-color: $editorBtns;
+ }
+ }
+
+ .btn-plain {
+ padding: 0.7rem;
+ transition: all 0.3s;
+ color: white;
+
+ &:hover,
+ &.is-active {
+ background-color: $blue;
+ }
+
+ &:disabled {
+ background-color: rgba(darken($active_theme, 80%), 0.8);
+ }
+ }
+}
+
+@include media('max', 1023) {
+ .__modal-editor {
+ .image {
+ max-width: 75vw !important;
+ }
+ }
+}
+
/* Options */
/* manager in modal */
@@ -730,8 +852,8 @@
}
}
- .count-enter,
- .count-leave-to {
+ .counter-enter,
+ .counter-leave-to {
transform: translateX(-0.7rem);
opacity: 0;
}
@@ -842,8 +964,13 @@ a[target='_blank'] {
background-color: $green;
}
+ &.is-black {
+ background-color: darken($active_theme, 70%);
+ }
+
&.is-danger,
&.is-link,
+ &.is-black,
&.is-success {
* {
color: $white;
@@ -861,7 +988,11 @@ a[target='_blank'] {
.modal-card {
margin: 0;
- padding: 1rem;
+ padding-top: 3rem;
+ }
+
+ .modal-card-title {
+ max-width: calc(100% - 20px);
}
.modal-content {
@@ -875,7 +1006,7 @@ a[target='_blank'] {
&.is-active {
margin-bottom: 2%;
- padding-top: 4%;
+ padding: 0.7rem 0.7rem 0;
}
}
diff --git a/src/resources/assets/sass/packages/switcher.scss b/src/resources/assets/sass/packages/switcher.scss
index 824c9ec..d747575 100755
--- a/src/resources/assets/sass/packages/switcher.scss
+++ b/src/resources/assets/sass/packages/switcher.scss
@@ -78,7 +78,7 @@
transform: translateX(0);
border-radius: 100vw;
background-color: $white;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
+ box-shadow: 0 1px 3px rgba($black, 0.4);
}
&:hover {
diff --git a/src/resources/lang/en/messages.php b/src/resources/lang/en/messages.php
index 1fc1c55..a740bf1 100644
--- a/src/resources/lang/en/messages.php
+++ b/src/resources/lang/en/messages.php
@@ -3,6 +3,7 @@
return array (
'add_folder' => 'Add Folder',
'add_new_folder' => 'Add New Folder',
+ 'add_url' => 'Url ...',
'ajax_error' => 'Oh NO! Something Went Wrong.',
'are_you_sure_delete' => 'Are you sure you want to delete ?',
'audio_support' => 'Your browser does not support the audio element.',
@@ -18,6 +19,15 @@
'copy_to_cp' => 'Copy Link To Clipboard',
'create_new_folder' => 'Create New Folder',
'create_success' => 'Successfully Created',
+ 'crop' => 'Crop',
+ 'crop_apply' => 'Apply Changes',
+ 'crop_flip_horizontal' => 'Flip Horizontal',
+ 'crop_flip_vertical' => 'Flip Vertical',
+ 'crop_reset' => 'Reset',
+ 'crop_rotate_left' => 'Rotate Left',
+ 'crop_rotate_right' => 'Rotate Right',
+ 'crop_zoom_in' => 'Zoom In',
+ 'crop_zoom_out' => 'Zoom Out',
'delete' => 'Delete',
'delete_confirm' => 'Yes, Delete it !',
'delete_folder' => 'Deleting a folder will remove all files and folders contained inside.',
@@ -26,6 +36,7 @@
'download_file' => 'Download File',
'download_folder' => 'Download Folder',
'downloaded' => 'Downloaded',
+ 'editor' => 'Editor',
'error_already_exists' => 'A File/Folder already exists with that name.',
'error_creating_dir' => 'Something gone wrong while creating the directory, please check your permissions,',
'error_deleting_file' => 'Something gone wrong while deleting this file / folder, please check your permissions,',
@@ -58,6 +69,7 @@
'rename' => 'Rename',
'rename_file_folder' => 'Rename File/Folder',
'rename_success' => 'Successfully Renamed',
+ 'save_link' => 'Upload Image From Url',
'select_all' => 'Select All',
'select_non' => 'Select Non',
'selected' => 'Selected',
@@ -66,6 +78,7 @@
'size' => 'Size',
'sort_by' => 'Sort By',
'stand_by' => 'Please Stand By ...',
+ 'stream_error' => 'Couldn\'t open ":attr" for streaming.',
'title' => 'Title',
'too_many_files' => 'Items Will Be Deleted !',
'total' => 'Total',
@@ -76,5 +89,5 @@
'upload_text' => 'Drag & Drop Files
Or
Click To Upload',
'use_random_names' => 'Use Random Names ?',
'video_support' => 'Your browser does not support the video tag.',
- 'stream_error' => 'Couldn\'t open ":attr" for streaming.',
+ 'save_success' => 'Successfully Saved As',
);
\ No newline at end of file
diff --git a/src/resources/views/_manager.blade.php b/src/resources/views/_manager.blade.php
index 4e4c3fb..27732de 100644
--- a/src/resources/views/_manager.blade.php
+++ b/src/resources/views/_manager.blade.php
@@ -24,6 +24,7 @@
'move_success' => trans('MediaManager::messages.move_success'),
'delete_success' => trans('MediaManager::messages.delete_success'),
'copy_success' => trans('MediaManager::messages.copy_success'),
+ 'save_success' => trans('MediaManager::messages.save_success'),
]) }}"
:upload-panel-img-list="{{ $patterns }}"
files-route="{{ route('media.files') }}"
@@ -52,7 +53,7 @@
:disabled="isLoading"
@click="toggleUploadPanel()"
v-tippy title="u">
-
+
{{ trans('MediaManager::messages.upload') }}
@@ -62,7 +63,7 @@
@@ -78,7 +79,7 @@
v-tippy title="r"
@tap="refresh()"
@hold="clearCache()">
-
+
@@ -91,33 +92,44 @@
{{-- rename --}}
-
+
+ {{-- editor --}}
+
+
+
+
{{-- delete --}}
@@ -132,7 +144,7 @@
:disabled="lock()"
v-tippy title="l"
@click="pushToLockedList()">
-
+
@@ -156,11 +168,11 @@ class="button"
:disabled="searchItemsCount == 0 || isLoading"
v-tippy title="a">
-
+
{{ trans('MediaManager::messages.select_non') }}
-
+
{{ trans('MediaManager::messages.select_all') }}
@@ -174,7 +186,7 @@ class="button"
:class="{'is-danger' : bulkSelect}"
:disabled="searchItemsCount == 0 || !allItemsCount || isLoading"
v-tippy title="b">
-
+
{{ trans('MediaManager::messages.bulk_select') }}
@@ -192,7 +204,7 @@ class="button"
class="button"
:class="{'is-link': filterNameIs('image')}"
:disabled="!btnFilter('image') || isLoading">
-
+
@@ -201,7 +213,7 @@ class="button"
class="button"
:class="{'is-link': filterNameIs('video')}"
:disabled="!btnFilter('video') || isLoading">
-
+
@@ -210,7 +222,7 @@ class="button"
class="button"
:class="{'is-link': filterNameIs('audio')}"
:disabled="!btnFilter('audio') || isLoading">
-
+
@@ -219,7 +231,7 @@ class="button"
class="button"
:class="{'is-link': filterNameIs('folder')}"
:disabled="!btnFilter('folder') || isLoading">
-
+
@@ -228,7 +240,7 @@ class="button"
class="button"
:class="{'is-link': filterNameIs('text')}"
:disabled="!btnFilter('text') || isLoading">
-
+
@@ -237,7 +249,7 @@ class="button"
class="button"
:class="{'is-danger': btnFilter('all')}"
:disabled="!btnFilter('all') || isLoading">
-
+
@@ -255,7 +267,7 @@ class="button"
-
@@ -272,7 +284,7 @@ class="button"
v-model="searchFor"
data-search
placeholder="{{ trans('MediaManager::messages.find') }}">
-
+
@@ -280,7 +292,7 @@ class="button"
@@ -296,15 +308,30 @@ class="button"