Enabled system-storage of drawings made via draw.io
This commit is contained in:
		
							parent
							
								
									0dc1f0b07f
								
							
						
					
					
						commit
						920964a561
					
				|  | @ -96,6 +96,7 @@ class ImageController extends Controller | ||||||
|      * @param string $type |      * @param string $type | ||||||
|      * @param Request $request |      * @param Request $request | ||||||
|      * @return \Illuminate\Http\JsonResponse |      * @return \Illuminate\Http\JsonResponse | ||||||
|  |      * @throws \Exception | ||||||
|      */ |      */ | ||||||
|     public function uploadByType($type, Request $request) |     public function uploadByType($type, Request $request) | ||||||
|     { |     { | ||||||
|  | @ -103,11 +104,12 @@ class ImageController extends Controller | ||||||
|         $this->validate($request, [ |         $this->validate($request, [ | ||||||
|             'file' => 'is_image' |             'file' => 'is_image' | ||||||
|         ]); |         ]); | ||||||
|  |         // TODO - Restrict & validate types
 | ||||||
| 
 | 
 | ||||||
|         $imageUpload = $request->file('file'); |         $imageUpload = $request->file('file'); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             $uploadedTo = $request->filled('uploaded_to') ? $request->get('uploaded_to') : 0; |             $uploadedTo = $request->get('uploaded_to', 0); | ||||||
|             $image = $this->imageRepo->saveNew($imageUpload, $type, $uploadedTo); |             $image = $this->imageRepo->saveNew($imageUpload, $type, $uploadedTo); | ||||||
|         } catch (ImageUploadException $e) { |         } catch (ImageUploadException $e) { | ||||||
|             return response($e->getMessage(), 500); |             return response($e->getMessage(), 500); | ||||||
|  | @ -116,6 +118,47 @@ class ImageController extends Controller | ||||||
|         return response()->json($image); |         return response()->json($image); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Upload a drawing to the system. | ||||||
|  |      * @param Request $request | ||||||
|  |      * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response | ||||||
|  |      */ | ||||||
|  |     public function uploadDrawing(Request $request) | ||||||
|  |     { | ||||||
|  |         $this->validate($request, [ | ||||||
|  |             'image' => 'required|string', | ||||||
|  |             'uploaded_to' => 'required|integer' | ||||||
|  |         ]); | ||||||
|  |         $this->checkPermission('image-create-all'); | ||||||
|  |         $imageBase64Data = $request->get('image'); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             $uploadedTo = $request->get('uploaded_to', 0); | ||||||
|  |             $image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo); | ||||||
|  |         } catch (ImageUploadException $e) { | ||||||
|  |             return response($e->getMessage(), 500); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return response()->json($image); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the content of an image based64 encoded. | ||||||
|  |      * @param $id | ||||||
|  |      * @return \Illuminate\Http\JsonResponse|mixed | ||||||
|  |      */ | ||||||
|  |     public function getBase64Image($id) | ||||||
|  |     { | ||||||
|  |         $image = $this->imageRepo->getById($id); | ||||||
|  |         $imageData = $this->imageRepo->getImageData($image); | ||||||
|  |         if ($imageData === null) { | ||||||
|  |             return $this->jsonError("Image data could not be found"); | ||||||
|  |         } | ||||||
|  |         return response()->json([ | ||||||
|  |             'content' => base64_encode($imageData) | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Generate a sized thumbnail for an image. |      * Generate a sized thumbnail for an image. | ||||||
|      * @param $id |      * @param $id | ||||||
|  | @ -123,6 +166,8 @@ class ImageController extends Controller | ||||||
|      * @param $height |      * @param $height | ||||||
|      * @param $crop |      * @param $crop | ||||||
|      * @return \Illuminate\Http\JsonResponse |      * @return \Illuminate\Http\JsonResponse | ||||||
|  |      * @throws ImageUploadException | ||||||
|  |      * @throws \Exception | ||||||
|      */ |      */ | ||||||
|     public function getThumbnail($id, $width, $height, $crop) |     public function getThumbnail($id, $width, $height, $crop) | ||||||
|     { |     { | ||||||
|  | @ -137,6 +182,8 @@ class ImageController extends Controller | ||||||
|      * @param integer $imageId |      * @param integer $imageId | ||||||
|      * @param Request $request |      * @param Request $request | ||||||
|      * @return \Illuminate\Http\JsonResponse |      * @return \Illuminate\Http\JsonResponse | ||||||
|  |      * @throws ImageUploadException | ||||||
|  |      * @throws \Exception | ||||||
|      */ |      */ | ||||||
|     public function update($imageId, Request $request) |     public function update($imageId, Request $request) | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -132,6 +132,8 @@ class ImageRepo | ||||||
|      * @param  string $type |      * @param  string $type | ||||||
|      * @param int $uploadedTo |      * @param int $uploadedTo | ||||||
|      * @return Image |      * @return Image | ||||||
|  |      * @throws \BookStack\Exceptions\ImageUploadException | ||||||
|  |      * @throws \Exception | ||||||
|      */ |      */ | ||||||
|     public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0) |     public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0) | ||||||
|     { |     { | ||||||
|  | @ -140,11 +142,27 @@ class ImageRepo | ||||||
|         return $image; |         return $image; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Save a drawing the the database; | ||||||
|  |      * @param string $base64Uri | ||||||
|  |      * @param int $uploadedTo | ||||||
|  |      * @return Image | ||||||
|  |      * @throws \BookStack\Exceptions\ImageUploadException | ||||||
|  |      */ | ||||||
|  |     public function saveDrawing(string $base64Uri, int $uploadedTo) | ||||||
|  |     { | ||||||
|  |         $name = 'Drawing-' . user()->getShortName(40) . '-' . strval(time()) . '.png'; | ||||||
|  |         $image = $this->imageService->saveNewFromBase64Uri($base64Uri, $name, 'drawing', $uploadedTo); | ||||||
|  |         return $image; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Update the details of an image via an array of properties. |      * Update the details of an image via an array of properties. | ||||||
|      * @param Image $image |      * @param Image $image | ||||||
|      * @param array $updateDetails |      * @param array $updateDetails | ||||||
|      * @return Image |      * @return Image | ||||||
|  |      * @throws \BookStack\Exceptions\ImageUploadException | ||||||
|  |      * @throws \Exception | ||||||
|      */ |      */ | ||||||
|     public function updateImageDetails(Image $image, $updateDetails) |     public function updateImageDetails(Image $image, $updateDetails) | ||||||
|     { |     { | ||||||
|  | @ -170,6 +188,8 @@ class ImageRepo | ||||||
|     /** |     /** | ||||||
|      * Load thumbnails onto an image object. |      * Load thumbnails onto an image object. | ||||||
|      * @param Image $image |      * @param Image $image | ||||||
|  |      * @throws \BookStack\Exceptions\ImageUploadException | ||||||
|  |      * @throws \Exception | ||||||
|      */ |      */ | ||||||
|     private function loadThumbs(Image $image) |     private function loadThumbs(Image $image) | ||||||
|     { |     { | ||||||
|  | @ -189,6 +209,8 @@ class ImageRepo | ||||||
|      * @param int $height |      * @param int $height | ||||||
|      * @param bool $keepRatio |      * @param bool $keepRatio | ||||||
|      * @return string |      * @return string | ||||||
|  |      * @throws \BookStack\Exceptions\ImageUploadException | ||||||
|  |      * @throws \Exception | ||||||
|      */ |      */ | ||||||
|     public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false) |     public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false) | ||||||
|     { |     { | ||||||
|  | @ -200,5 +222,19 @@ class ImageRepo | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the raw image data from an Image. | ||||||
|  |      * @param Image $image | ||||||
|  |      * @return null|string | ||||||
|  |      */ | ||||||
|  |     public function getImageData(Image $image) | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             return $this->imageService->getImageData($image); | ||||||
|  |         } catch (\Exception $exception) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -46,6 +46,24 @@ class ImageService extends UploadService | ||||||
|         return $this->saveNew($imageName, $imageData, $type, $uploadedTo); |         return $this->saveNew($imageName, $imageData, $type, $uploadedTo); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Save a new image from a uri-encoded base64 string of data. | ||||||
|  |      * @param string $base64Uri | ||||||
|  |      * @param string $name | ||||||
|  |      * @param string $type | ||||||
|  |      * @param int $uploadedTo | ||||||
|  |      * @return Image | ||||||
|  |      * @throws ImageUploadException | ||||||
|  |      */ | ||||||
|  |     public function saveNewFromBase64Uri(string $base64Uri, string $name, string $type, $uploadedTo = 0) | ||||||
|  |     { | ||||||
|  |         $splitData = explode(';base64,', $base64Uri); | ||||||
|  |         if (count($splitData) < 2) { | ||||||
|  |             throw new ImageUploadException("Invalid base64 image data provided"); | ||||||
|  |         } | ||||||
|  |         $data = base64_decode($splitData[1]); | ||||||
|  |         return $this->saveNew($name, $data, $type, $uploadedTo); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Gets an image from url and saves it to the database. |      * Gets an image from url and saves it to the database. | ||||||
|  | @ -183,6 +201,19 @@ class ImageService extends UploadService | ||||||
|         return $this->getPublicUrl($thumbFilePath); |         return $this->getPublicUrl($thumbFilePath); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the raw data content from an image. | ||||||
|  |      * @param Image $image | ||||||
|  |      * @return string | ||||||
|  |      * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException | ||||||
|  |      */ | ||||||
|  |     public function getImageData(Image $image) | ||||||
|  |     { | ||||||
|  |         $imagePath = $this->getPath($image); | ||||||
|  |         $storage = $this->getStorage(); | ||||||
|  |         return $storage->get($imagePath); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Destroys an Image object along with its files and thumbnails. |      * Destroys an Image object along with its files and thumbnails. | ||||||
|      * @param Image $image |      * @param Image $image | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ function uploadImageFile(file) { | ||||||
|     let formData = new FormData(); |     let formData = new FormData(); | ||||||
|     formData.append('file', file, remoteFilename); |     formData.append('file', file, remoteFilename); | ||||||
| 
 | 
 | ||||||
|     return window.$http.post('/images/gallery/upload', formData).then(resp => (resp.data)); |     return window.$http.post(window.baseUrl('/images/gallery/upload'), formData).then(resp => (resp.data)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function registerEditorShortcuts(editor) { | function registerEditorShortcuts(editor) { | ||||||
|  | @ -225,25 +225,27 @@ function drawIoPlugin() { | ||||||
|     const drawIoUrl = 'https://www.draw.io/?embed=1&ui=atlas&spin=1&proto=json'; |     const drawIoUrl = 'https://www.draw.io/?embed=1&ui=atlas&spin=1&proto=json'; | ||||||
|     let iframe = null; |     let iframe = null; | ||||||
|     let pageEditor = null; |     let pageEditor = null; | ||||||
|  |     let currentNode = null; | ||||||
| 
 | 
 | ||||||
|     function isDrawing(node) { |     function isDrawing(node) { | ||||||
|         return node.hasAttribute('drawio-diagram'); |         return node.hasAttribute('drawio-diagram'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function showDrawingEditor(mceEditor) { |     function showDrawingEditor(mceEditor, selectedNode = null) { | ||||||
|         pageEditor = mceEditor; |         pageEditor = mceEditor; | ||||||
|  |         currentNode = selectedNode; | ||||||
|         iframe = document.createElement('iframe'); |         iframe = document.createElement('iframe'); | ||||||
|         iframe.setAttribute('frameborder', '0'); |         iframe.setAttribute('frameborder', '0'); | ||||||
|         window.addEventListener('message', drawReceive); |         window.addEventListener('message', drawReceive); | ||||||
|         iframe.setAttribute('src', drawIoUrl); |         iframe.setAttribute('src', drawIoUrl); | ||||||
|         iframe.setAttribute('class', 'fullscreen'); |         iframe.setAttribute('class', 'fullscreen'); | ||||||
|  |         iframe.style.backgroundColor = '#FFFFFF'; | ||||||
|         document.body.appendChild(iframe); |         document.body.appendChild(iframe); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function drawReceive(event) { |     function drawReceive(event) { | ||||||
|         if (!event.data || event.data.length < 1) return; |         if (!event.data || event.data.length < 1) return; | ||||||
|         let message = JSON.parse(event.data); |         let message = JSON.parse(event.data); | ||||||
|         console.log(message); |  | ||||||
|         if (message.event === 'init') { |         if (message.event === 'init') { | ||||||
|             drawEventInit(); |             drawEventInit(); | ||||||
|         } else if (message.event === 'exit') { |         } else if (message.event === 'exit') { | ||||||
|  | @ -255,19 +257,28 @@ function drawIoPlugin() { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function updateContent(svg) { |     function updateContent(pngData) { | ||||||
|         let svgWrap = document.createElement('div'); |         let id = "image-" + Math.random().toString(16).slice(2); | ||||||
|         svgWrap.setAttribute('drawio-diagram', svg); |         let loadingImage = window.baseUrl('/loading.gif'); | ||||||
|         svgWrap.setAttribute('contenteditable', 'false'); |         let data = { | ||||||
|         pageEditor.insertContent(svgWrap.outerHTML); |             image: pngData, | ||||||
|     } |             uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id')) | ||||||
|  |         }; | ||||||
| 
 | 
 | ||||||
|     function b64DecodeUnicode(str) { |         // TODO - Handle updating an existing image
 | ||||||
|         str = str.split(';base64,')[1]; | 
 | ||||||
|         // Going backwards: from bytestream, to percent-encoding, to original string.
 |         setTimeout(() => { | ||||||
|         return decodeURIComponent(atob(str).split('').map(function(c) { |             pageEditor.insertContent(`<div drawio-diagram contenteditable="false"><img src="${loadingImage}" id="${id}"></div>`); | ||||||
|             return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); |             drawEventClose(); | ||||||
|         }).join('')); |             window.$http.post(window.baseUrl('/images/drawing/upload'), data).then(resp => { | ||||||
|  |                 pageEditor.dom.setAttrib(id, 'src', resp.data.url); | ||||||
|  |                 pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', resp.data.id); | ||||||
|  |             }).catch(err => { | ||||||
|  |                 pageEditor.dom.remove(id); | ||||||
|  |                 window.$events.emit('error', trans('errors.image_upload_error')); | ||||||
|  |                 console.log(err); | ||||||
|  |             }); | ||||||
|  |         }, 5); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function drawEventExport(message) { |     function drawEventExport(message) { | ||||||
|  | @ -275,11 +286,21 @@ function drawIoPlugin() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function drawEventSave(message) { |     function drawEventSave(message) { | ||||||
|         drawPostMessage({action: 'export', format: 'svg', xml: message.xml, spin: 'Updating drawing'}); |         drawPostMessage({action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing'}); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function drawEventInit() { |     function drawEventInit() { | ||||||
|         drawPostMessage({action: 'load', autosave: 1, xml: ''}); |         if (!currentNode) { | ||||||
|  |             drawPostMessage({action: 'load', autosave: 1, xml: ''}); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let imgElem = currentNode.querySelector('img'); | ||||||
|  |         let drawingId = currentNode.getAttribute('drawio-diagram'); | ||||||
|  |         $http.get(window.baseUrl(`/images/base64/${drawingId}`)).then(resp => { | ||||||
|  |             let xml = `data:image/png;base64,${resp.data.content}`; | ||||||
|  |             drawPostMessage({action: 'load', autosave: 1, xml}); | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function drawEventClose() { |     function drawEventClose() { | ||||||
|  | @ -308,28 +329,16 @@ function drawIoPlugin() { | ||||||
|         editor.on('dblclick', event => { |         editor.on('dblclick', event => { | ||||||
|             let selectedNode = editor.selection.getNode(); |             let selectedNode = editor.selection.getNode(); | ||||||
|             if (!isDrawing(selectedNode)) return; |             if (!isDrawing(selectedNode)) return; | ||||||
|             showDrawingEditor(editor); |             showDrawingEditor(editor, selectedNode); | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         editor.on('PreProcess', function (e) { |  | ||||||
|             $('div[drawio-diagram]', e.node). |  | ||||||
|             each((index, elem) => { |  | ||||||
|                 let $elem = $(elem); |  | ||||||
|                 let svgData = b64DecodeUnicode($elem.attr('drawio-diagram')); |  | ||||||
|                 $elem.html(svgData); |  | ||||||
|             }); |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         editor.on('SetContent', function () { |         editor.on('SetContent', function () { | ||||||
| 
 |  | ||||||
|             let drawings = $('body > div[drawio-diagram]'); |             let drawings = $('body > div[drawio-diagram]'); | ||||||
| 
 |  | ||||||
|             if (!drawings.length) return; |             if (!drawings.length) return; | ||||||
|  | 
 | ||||||
|             editor.undoManager.transact(function () { |             editor.undoManager.transact(function () { | ||||||
|                 drawings.each((index, elem) => { |                 drawings.each((index, elem) => { | ||||||
|                     let svgContent =  b64DecodeUnicode(elem.getAttribute('drawio-diagram')); |  | ||||||
|                     elem.setAttribute('contenteditable', 'false'); |                     elem.setAttribute('contenteditable', 'false'); | ||||||
|                     elem.innerHTML = svgContent; |  | ||||||
|                 }); |                 }); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|  | @ -379,7 +388,7 @@ module.exports = { | ||||||
|     paste_data_images: false, |     paste_data_images: false, | ||||||
|     extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram]', |     extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram]', | ||||||
|     automatic_uploads: false, |     automatic_uploads: false, | ||||||
|     valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre],+div[svg],+svg", |     valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre],+div[img]", | ||||||
|     plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor drawio", |     plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor drawio", | ||||||
|     imagetools_toolbar: 'imageoptions', |     imagetools_toolbar: 'imageoptions', | ||||||
|     toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen drawio", |     toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen drawio", | ||||||
|  |  | ||||||
|  | @ -86,13 +86,15 @@ Route::group(['middleware' => 'auth'], function () { | ||||||
|         Route::get('/user/all/{page}', 'ImageController@getAllForUserType'); |         Route::get('/user/all/{page}', 'ImageController@getAllForUserType'); | ||||||
|         // Standard get, update and deletion for all types
 |         // Standard get, update and deletion for all types
 | ||||||
|         Route::get('/thumb/{id}/{width}/{height}/{crop}', 'ImageController@getThumbnail'); |         Route::get('/thumb/{id}/{width}/{height}/{crop}', 'ImageController@getThumbnail'); | ||||||
|  |         Route::get('/base64/{id}', 'ImageController@getBase64Image'); | ||||||
|         Route::put('/update/{imageId}', 'ImageController@update'); |         Route::put('/update/{imageId}', 'ImageController@update'); | ||||||
|  |         Route::post('/drawing/upload', 'ImageController@uploadDrawing'); | ||||||
|         Route::post('/{type}/upload', 'ImageController@uploadByType'); |         Route::post('/{type}/upload', 'ImageController@uploadByType'); | ||||||
|         Route::get('/{type}/all', 'ImageController@getAllByType'); |         Route::get('/{type}/all', 'ImageController@getAllByType'); | ||||||
|         Route::get('/{type}/all/{page}', 'ImageController@getAllByType'); |         Route::get('/{type}/all/{page}', 'ImageController@getAllByType'); | ||||||
|         Route::get('/{type}/search/{page}', 'ImageController@searchByType'); |         Route::get('/{type}/search/{page}', 'ImageController@searchByType'); | ||||||
|         Route::get('/gallery/{filter}/{page}', 'ImageController@getGalleryFiltered'); |         Route::get('/gallery/{filter}/{page}', 'ImageController@getGalleryFiltered'); | ||||||
|         Route::delete('/{imageId}', 'ImageController@destroy'); |         Route::delete('/{id}', 'ImageController@destroy'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Attachments routes
 |     // Attachments routes
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue