From 017305cd5bf9ee802b1c7b11b93aa8f9bb4f0b32 Mon Sep 17 00:00:00 2001 From: philipp lang Date: Wed, 21 Oct 2020 00:02:20 +0200 Subject: [PATCH] Copy fileupload-widget from core --- Plugin.php | 7 + formwidgets/.Responsiveimage.php.swp | Bin 0 -> 32768 bytes formwidgets/Responsiveimage.php | 528 ++++++++++++++++++ .../responsiveimage/assets/css/fileupload.css | 157 ++++++ .../responsiveimage/assets/js/fileupload.js | 478 ++++++++++++++++ .../assets/less/fileupload.base.less | 413 ++++++++++++++ .../assets/less/fileupload.filemulti.less | 176 ++++++ .../assets/less/fileupload.filesingle.less | 119 ++++ .../assets/less/fileupload.imagemulti.less | 134 +++++ .../assets/less/fileupload.imagesingle.less | 78 +++ .../assets/less/fileupload.less | 15 + .../partials/._responsiveimage.htm.swp | Bin 0 -> 12288 bytes .../responsiveimage/partials/_config_form.htm | 47 ++ .../responsiveimage/partials/_file_multi.htm | 73 +++ .../responsiveimage/partials/_file_single.htm | 76 +++ .../responsiveimage/partials/_image_multi.htm | 72 +++ .../partials/_image_single.htm | 74 +++ .../partials/_responsiveimage.htm | 39 ++ traits/ResponsiveSaver.php | 120 ++++ traits/ResponsiveWidget.php | 101 ++++ 20 files changed, 2707 insertions(+) create mode 100644 formwidgets/.Responsiveimage.php.swp create mode 100644 formwidgets/Responsiveimage.php create mode 100644 formwidgets/responsiveimage/assets/css/fileupload.css create mode 100644 formwidgets/responsiveimage/assets/js/fileupload.js create mode 100644 formwidgets/responsiveimage/assets/less/fileupload.base.less create mode 100644 formwidgets/responsiveimage/assets/less/fileupload.filemulti.less create mode 100644 formwidgets/responsiveimage/assets/less/fileupload.filesingle.less create mode 100644 formwidgets/responsiveimage/assets/less/fileupload.imagemulti.less create mode 100644 formwidgets/responsiveimage/assets/less/fileupload.imagesingle.less create mode 100644 formwidgets/responsiveimage/assets/less/fileupload.less create mode 100644 formwidgets/responsiveimage/partials/._responsiveimage.htm.swp create mode 100644 formwidgets/responsiveimage/partials/_config_form.htm create mode 100644 formwidgets/responsiveimage/partials/_file_multi.htm create mode 100644 formwidgets/responsiveimage/partials/_file_single.htm create mode 100644 formwidgets/responsiveimage/partials/_image_multi.htm create mode 100644 formwidgets/responsiveimage/partials/_image_single.htm create mode 100644 formwidgets/responsiveimage/partials/_responsiveimage.htm create mode 100644 traits/ResponsiveSaver.php create mode 100644 traits/ResponsiveWidget.php diff --git a/Plugin.php b/Plugin.php index 9e4e19e..a4badbf 100644 --- a/Plugin.php +++ b/Plugin.php @@ -9,6 +9,7 @@ use Aweos\Resizer\Console\ClearOld; use Aweos\Resizer\Console\ResizeMake; use Illuminate\Support\Facades\Cache; use Aweos\Resizer\Console\ResizePurge; +use Aweos\Resizer\FormWidgets\Responsiveimage; /** * resizer Plugin Information File @@ -210,4 +211,10 @@ class Plugin extends PluginBase public function registerSchedule($schedule) { $schedule->command('resize:make')->dailyAt('01:00'); } + + public function registerFormWidgets() { + return [ + Responsiveimage::class => 'responsiveimage' + ]; + } } diff --git a/formwidgets/.Responsiveimage.php.swp b/formwidgets/.Responsiveimage.php.swp new file mode 100644 index 0000000000000000000000000000000000000000..14d77f959ffff9779ceedef3a758a62772253617 GIT binary patch literal 32768 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 'crop', + 'extension' => 'auto' + ]; + + /** + * @var boolean Allow the user to set a caption. + */ + public $useCaption = true; + + /** + * @var boolean Automatically attaches the uploaded file on upload if the parent record exists instead of using deferred binding to attach on save of the parent record. Defaults to false. + */ + public $attachOnUpload = false; + + // + // Object properties + // + + /** + * @inheritDoc + */ + protected $defaultAlias = 'fileupload'; + + /** + * @var Backend\Widgets\Form The embedded form for modifying the properties of the selected file + */ + protected $configFormWidget; + + /** + * @inheritDoc + */ + public function init() + { + $this->maxFilesize = $this->getUploadMaxFilesize(); + + $this->fillFromConfig([ + 'prompt', + 'imageWidth', + 'imageHeight', + 'fileTypes', + 'maxFilesize', + 'mimeTypes', + 'thumbOptions', + 'useCaption', + 'attachOnUpload', + ]); + + if ($this->formField->disabled) { + $this->previewMode = true; + } + + $this->getConfigFormWidget(); + } + + /** + * @inheritDoc + */ + public function render() + { + $this->prepareVars(); + return $this->makePartial('responsiveimage'); + } + + /** + * Prepares the view data + */ + protected function prepareVars() + { + if ($this->formField->disabled) { + $this->previewMode = true; + } + + if ($this->previewMode) { + $this->useCaption = false; + } + + if ($this->maxFilesize > $this->getUploadMaxFilesize()) { + throw new ApplicationException('Maximum allowed size for uploaded files: ' . $this->getUploadMaxFilesize()); + } + + $this->vars['fileList'] = $fileList = $this->getFileList(); + $this->vars['singleFile'] = $fileList->first(); + $this->vars['displayMode'] = $this->getDisplayMode(); + $this->vars['emptyIcon'] = $this->getConfig('emptyIcon', 'icon-upload'); + $this->vars['imageHeight'] = $this->imageHeight; + $this->vars['imageWidth'] = $this->imageWidth; + $this->vars['acceptedFileTypes'] = $this->getAcceptedFileTypes(true); + $this->vars['maxFilesize'] = $this->maxFilesize; + $this->vars['cssDimensions'] = $this->getCssDimensions(); + $this->vars['cssBlockDimensions'] = $this->getCssDimensions('block'); + $this->vars['useCaption'] = $this->useCaption; + $this->vars['prompt'] = $this->getPromptText(); + } + + /** + * Get the file record for this request, returns false if none available + * + * @return System\Models\File|false + */ + protected function getFileRecord() + { + $record = false; + + if (!empty(post('file_id'))) { + $record = $this->getRelationModel()::find(post('file_id')) ?: false; + } + + return $record; + } + + /** + * Get the instantiated config Form widget + * + * @return void + */ + public function getConfigFormWidget() + { + if ($this->configFormWidget) { + return $this->configFormWidget; + } + + $config = $this->makeConfig('~/modules/system/models/file/fields.yaml'); + $config->model = $this->getFileRecord() ?: $this->getRelationModel(); + $config->alias = $this->alias . $this->defaultAlias; + $config->arrayName = $this->getFieldName(); + + $widget = $this->makeWidget(Form::class, $config); + $widget->bindToController(); + + return $this->configFormWidget = $widget; + } + + protected function getFileList() + { + $list = $this + ->getRelationObject() + ->withDeferred($this->sessionKey) + ->orderBy('sort_order') + ->get() + ; + + /* + * Decorate each file with thumb and custom download path + */ + $list->each(function ($file) { + $this->decorateFileAttributes($file); + }); + + return $list; + } + + /** + * Returns the display mode for the file upload. Eg: file-multi, image-single, etc. + * @return string + */ + protected function getDisplayMode() + { + $mode = $this->getConfig('mode', 'image'); + + if (str_contains($mode, '-')) { + return $mode; + } + + $relationType = $this->getRelationType(); + $mode .= ($relationType == 'attachMany' || $relationType == 'morphMany') ? '-multi' : '-single'; + + return $mode; + } + + /** + * Returns the escaped and translated prompt text to display according to the type. + * @return string + */ + protected function getPromptText() + { + if ($this->prompt === null) { + $isMulti = ends_with($this->getDisplayMode(), 'multi'); + $this->prompt = $isMulti + ? 'backend::lang.fileupload.upload_file' + : 'backend::lang.fileupload.default_prompt'; + } + + return str_replace('%s', '', e(trans($this->prompt))); + } + + /** + * Returns the CSS dimensions for the uploaded image, + * uses auto where no dimension is provided. + * @param string $mode + * @return string + */ + protected function getCssDimensions($mode = null) + { + if (!$this->imageWidth && !$this->imageHeight) { + return ''; + } + + $cssDimensions = ''; + + if ($mode == 'block') { + $cssDimensions .= $this->imageWidth + ? 'width: '.$this->imageWidth.'px;' + : 'width: '.$this->imageHeight.'px;'; + + $cssDimensions .= ($this->imageHeight) + ? 'max-height: '.$this->imageHeight.'px;' + : 'height: auto;'; + } + else { + $cssDimensions .= $this->imageWidth + ? 'width: '.$this->imageWidth.'px;' + : 'width: auto;'; + + $cssDimensions .= ($this->imageHeight) + ? 'max-height: '.$this->imageHeight.'px;' + : 'height: auto;'; + } + + return $cssDimensions; + } + + /** + * Returns the specified accepted file types, or the default + * based on the mode. Image mode will return: + * - jpg,jpeg,bmp,png,gif,svg + * @return string + */ + public function getAcceptedFileTypes($includeDot = false) + { + $types = $this->fileTypes; + + if ($types === false) { + $isImage = starts_with($this->getDisplayMode(), 'image'); + $types = implode(',', FileDefinitions::get($isImage ? 'imageExtensions' : 'defaultExtensions')); + } + + if (!$types || $types == '*') { + return null; + } + + if (!is_array($types)) { + $types = explode(',', $types); + } + + $types = array_map(function ($value) use ($includeDot) { + $value = trim($value); + + if (substr($value, 0, 1) == '.') { + $value = substr($value, 1); + } + + if ($includeDot) { + $value = '.'.$value; + } + + return $value; + }, $types); + + return implode(',', $types); + } + + /** + * Removes a file attachment. + */ + public function onRemoveAttachment() + { + $fileModel = $this->getRelationModel(); + if (($fileId = post('file_id')) && ($file = $fileModel::find($fileId))) { + $this->getRelationObject()->remove($file, $this->sessionKey); + } + } + + /** + * Sorts file attachments. + */ + public function onSortAttachments() + { + if ($sortData = post('sortOrder')) { + $ids = array_keys($sortData); + $orders = array_values($sortData); + + $fileModel = $this->getRelationModel(); + $fileModel->setSortableOrder($ids, $orders); + } + } + + /** + * Loads the configuration form for an attachment, allowing title and description to be set. + */ + public function onLoadAttachmentConfig() + { + $fileModel = $this->getRelationModel(); + if ($file = $this->getFileRecord()) { + $file = $this->decorateFileAttributes($file); + + $this->vars['file'] = $file; + $this->vars['displayMode'] = $this->getDisplayMode(); + $this->vars['cssDimensions'] = $this->getCssDimensions(); + $this->vars['relationManageId'] = post('manage_id'); + $this->vars['relationField'] = post('_relation_field'); + + return $this->makePartial('config_form'); + } + + throw new ApplicationException('Unable to find file, it may no longer exist'); + } + + /** + * Commit the changes of the attachment configuration form. + */ + public function onSaveAttachmentConfig() + { + try { + $formWidget = $this->getConfigFormWidget(); + if ($file = $formWidget->model) { + $modelsToSave = $this->prepareModelsToSave($file, $formWidget->getSaveData()); + Db::transaction(function () use ($modelsToSave, $formWidget) { + foreach ($modelsToSave as $modelToSave) { + $modelToSave->save(null, $formWidget->getSessionKey()); + } + }); + + return ['displayName' => $file->title ?: $file->file_name]; + } + + throw new ApplicationException('Unable to find file, it may no longer exist'); + } + catch (Exception $ex) { + return json_encode(['error' => $ex->getMessage()]); + } + } + + /** + * @inheritDoc + */ + protected function loadAssets() + { + $this->addCss('css/fileupload.css', 'core'); + $this->addJs('js/fileupload.js', 'core'); + } + + /** + * @inheritDoc + */ + public function getSaveValue($value) + { + return FormField::NO_SAVE_DATA; + } + + /** + * Upload handler for the server-side processing of uploaded files + */ + public function onUpload() + { + try { + if (!Input::hasFile('file_data')) { + throw new ApplicationException('File missing from request'); + } + + $fileModel = $this->getRelationModel(); + $uploadedFile = Input::file('file_data'); + + $validationRules = ['max:'.$fileModel::getMaxFilesize()]; + if ($fileTypes = $this->getAcceptedFileTypes()) { + $validationRules[] = 'extensions:'.$fileTypes; + } + + if ($this->mimeTypes) { + $validationRules[] = 'mimes:'.$this->mimeTypes; + } + + $validation = Validator::make( + ['file_data' => $uploadedFile], + ['file_data' => $validationRules] + ); + + if ($validation->fails()) { + throw new ValidationException($validation); + } + + if (!$uploadedFile->isValid()) { + throw new ApplicationException('File is not valid'); + } + + $fileRelation = $this->getRelationObject(); + + $file = $fileModel; + $file->data = $uploadedFile; + $file->is_public = $fileRelation->isPublic(); + $file->save(); + + /** + * Attach directly to the parent model if it exists and attachOnUpload has been set to true + * else attach via deferred binding + */ + $parent = $fileRelation->getParent(); + if ($this->attachOnUpload && $parent && $parent->exists) { + $fileRelation->add($file); + } + else { + $fileRelation->add($file, $this->sessionKey); + } + + $file = $this->decorateFileAttributes($file); + + $result = [ + 'id' => $file->id, + 'thumb' => $file->thumbUrl, + 'path' => $file->pathUrl + ]; + + $response = Response::make($result, 200); + } + catch (Exception $ex) { + $response = Response::make($ex->getMessage(), 400); + } + + return $response; + } + + /** + * Adds the bespoke attributes used internally by this widget. + * - thumbUrl + * - pathUrl + * @return System\Models\File + */ + protected function decorateFileAttributes($file) + { + $path = $thumb = $file->getPath(); + + if ($this->imageWidth || $this->imageHeight) { + $thumb = $file->getThumb($this->imageWidth, $this->imageHeight, $this->thumbOptions); + } + + $file->pathUrl = $path; + $file->thumbUrl = $thumb; + + return $file; + } + + /** + * Return max upload filesize in Mb + * @return integer + */ + protected function getUploadMaxFilesize() + { + $size = ini_get('upload_max_filesize'); + if (preg_match('/^([\d\.]+)([KMG])$/i', $size, $match)) { + $pos = array_search($match[2], ['K', 'M', 'G']); + if ($pos !== false) { + $size = $match[1] * pow(1024, $pos + 1); + } + } + return floor($size / 1024 / 1024); + } +} diff --git a/formwidgets/responsiveimage/assets/css/fileupload.css b/formwidgets/responsiveimage/assets/css/fileupload.css new file mode 100644 index 0000000..b3d4ba1 --- /dev/null +++ b/formwidgets/responsiveimage/assets/css/fileupload.css @@ -0,0 +1,157 @@ +.field-fileupload .upload-object {-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;position:relative;outline:none;overflow:hidden;display:inline-block;vertical-align:top} +.field-fileupload .upload-object img {width:100%;height:100%} +.field-fileupload .upload-object .icon-container {display:table;opacity:.6} +.field-fileupload .upload-object .icon-container i {color:#95a5a6;display:inline-block} +.field-fileupload .upload-object .icon-container div {display:table-cell;text-align:center;vertical-align:middle} +.field-fileupload .upload-object .icon-container.image >div.icon-wrapper {display:none} +.field-fileupload .upload-object h4 {font-size:13px;color:#2A3E51;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:150%;margin:15px 0 5px 0;padding-right:0;-webkit-transition:padding 0.1s;transition:padding 0.1s;position:relative} +.field-fileupload .upload-object h4 a {position:absolute;right:0;top:0;display:none;font-weight:400} +.field-fileupload .upload-object p.size {font-size:12px;color:#95a5a6} +.field-fileupload .upload-object p.size strong {font-weight:400} +.field-fileupload .upload-object .meta .drag-handle {position:absolute;bottom:0;right:0;cursor:move;display:block} +.field-fileupload .upload-object .info h4 a, +.field-fileupload .upload-object .meta a.upload-remove-button, +.field-fileupload .upload-object .meta a.drag-handle {color:#2b3e50;display:none;font-size:13px;text-decoration:none} +.field-fileupload .upload-object .icon-container {position:relative} +.field-fileupload .upload-object .icon-container:after {background-image:url('../../../../../system/assets/ui/images/loader-transparent.svg');position:absolute;content:' ';width:40px;height:40px;left:50%;top:50%;margin-top:-20px;margin-left:-20px;display:block;background-size:40px 40px;background-position:50% 50%;-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite} +.field-fileupload .upload-object.is-success .icon-container {opacity:1} +.field-fileupload .upload-object.is-success .icon-container:after {opacity:0;-webkit-transition:opacity 0.3s ease;transition:opacity 0.3s ease} +.field-fileupload .upload-object.is-error .icon-container:after {content:"";background:none;font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;content:"\f071";-webkit-animation:none;animation:none;font-size:40px;color:#ab2a1c;margin-top:-20px;margin-left:-20px;text-shadow:2px 2px 0 #fff} +.field-fileupload .upload-object.is-loading .icon-container {opacity:.6} +.field-fileupload .upload-object.is-loading .icon-container:after {opacity:1;-webkit-transition:opacity 0.3s ease;transition:opacity 0.3s ease} +.field-fileupload .upload-object.is-success {cursor:pointer} +.field-fileupload .upload-object.is-success .progress-bar {opacity:0;-webkit-transition:opacity 0.3s ease;transition:opacity 0.3s ease} +.field-fileupload .upload-object.is-success:hover h4 a, +.field-fileupload .upload-object.is-success:hover .meta .upload-remove-button, +.field-fileupload .upload-object.is-success:hover .meta .drag-handle {display:block} +.field-fileupload .upload-object.is-error {cursor:pointer} +.field-fileupload .upload-object.is-error .icon-container {opacity:1} +.field-fileupload .upload-object.is-error .icon-container >img, +.field-fileupload .upload-object.is-error .icon-container >i {opacity:.5} +.field-fileupload .upload-object.is-error .info h4 {color:#ab2a1c} +.field-fileupload .upload-object.is-error .info h4 a {display:none} +.field-fileupload .upload-object.is-error .meta {display:none} +.field-fileupload.is-sortable {position:relative} +.field-fileupload.is-sortable .upload-placeholder {position:relative;border:1px dotted #e0e0e0 !important} +.field-fileupload.is-sortable .upload-object.dragged {position:absolute;opacity:0.5;filter:alpha(opacity=50);z-index:2000} +.field-fileupload.is-sortable .upload-object.dragged .uploader-toolbar {display:none} +.field-fileupload.is-preview .upload-button, +.field-fileupload.is-preview .upload-remove-button, +.field-fileupload.is-preview .meta a.drag-handle {display:none !important} +@media (max-width:1024px) {.field-fileupload .upload-object.is-success h4 a,.field-fileupload .upload-object.is-success .meta .upload-remove-button,.field-fileupload .upload-object.is-success .meta .drag-handle {display:block !important }} +.fileupload-config-form .fileupload-url-button {padding-left:0} +.fileupload-config-form .fileupload-url-button >i {color:#666} +.fileupload-config-form .file-upload-modal-image-header {background-color:#FEFEFE;background-image:-webkit-linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb),-webkit-linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb);background-image:-moz-linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb),-moz-linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb);background-image:-o-linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb),-o-linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb);background-image:-ms-linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb),-ms-linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb);background-image:linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb),linear-gradient(45deg,#cbcbcb 25%,transparent 25%,transparent 75%,#cbcbcb 75%,#cbcbcb);-webkit-background-size:20px 20px;-moz-background-size:20px 20px;background-size:20px 20px;background-position:0 0,10px 10px} +.fileupload-config-form .file-upload-modal-image-header, +.fileupload-config-form .file-upload-modal-image-header img {border-top-right-radius:2px;border-top-left-radius:2px} +.fileupload-config-form .file-upload-modal-image-header .close {position:absolute;top:20px;right:20px;background:#BDC3C7;opacity:.7;height:24px;width:22px} +.fileupload-config-form .file-upload-modal-image-header .close:hover, +.fileupload-config-form .file-upload-modal-image-header .close:focus {opacity:.9} +.fileupload-config-form .file-upload-modal-image-header + .modal-body {padding-top:20px} +.field-fileupload.style-image-multi .upload-button, +.field-fileupload.style-image-multi .upload-object {margin:0 10px 10px 0} +.field-fileupload.style-image-multi .upload-button {display:block;border:2px dashed #BDC3C7;background-clip:content-box;background-color:#F9F9F9;position:relative;outline:none;float:left;width:76px;height:76px} +.field-fileupload.style-image-multi .upload-button .upload-button-icon {position:absolute;width:22px;height:22px;top:50%;left:50%;margin-top:-11px;margin-left:-11px} +.field-fileupload.style-image-multi .upload-button .upload-button-icon:before {text-align:center;display:block;font-size:22px;height:22px;width:22px;line-height:22px;color:#BDC3C7} +.field-fileupload.style-image-multi .upload-button .upload-button-icon.large-icon {width:34px;height:34px;top:50%;left:50%;margin-top:-17px;margin-left:-17px} +.field-fileupload.style-image-multi .upload-button .upload-button-icon.large-icon:before {font-size:34px;height:24px;width:24px;line-height:24px} +.field-fileupload.style-image-multi .upload-button:hover {border:2px dashed #1F99DC} +.field-fileupload.style-image-multi .upload-button:hover .upload-button-icon:before {color:#1F99DC} +.field-fileupload.style-image-multi .upload-button:focus {border:2px dashed #1F99DC} +.field-fileupload.style-image-multi .upload-button:focus .upload-button-icon:before {color:#1F99DC} +.field-fileupload.style-image-multi .upload-files-container {margin-left:90px} +.field-fileupload.style-image-multi .upload-object {background:#fff;border:1px solid #ecf0f1;width:260px} +.field-fileupload.style-image-multi .upload-object .progress-bar {display:block;width:100%;overflow:hidden;height:5px;background-color:#f5f5f5;border-radius:3px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);position:absolute;bottom:10px;left:0} +.field-fileupload.style-image-multi .upload-object .progress-bar .upload-progress {float:left;width:0%;height:100%;line-height:5px;color:#fff;background-color:#5fb6f5;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:width 0.6s ease;transition:width 0.6s ease} +.field-fileupload.style-image-multi .upload-object .icon-container {border-right:1px solid #f6f8f9;float:left;display:inline-block;overflow:hidden;width:75px;height:75px} +.field-fileupload.style-image-multi .upload-object .icon-container i {font-size:35px} +.field-fileupload.style-image-multi .upload-object .icon-container.image img {border-bottom-left-radius:3px;border-top-left-radius:3px;width:auto} +.field-fileupload.style-image-multi .upload-object .info {margin-left:90px} +.field-fileupload.style-image-multi .upload-object .info h4 {padding-right:15px} +.field-fileupload.style-image-multi .upload-object .info h4 a {right:15px} +.field-fileupload.style-image-multi .upload-object .meta {position:absolute;bottom:0;left:0;right:0;margin:0 15px 0 90px} +.field-fileupload.style-image-multi .upload-object .meta a.drag-handle {bottom:15px} +.field-fileupload.style-image-multi .upload-object.upload-placeholder {height:75px;background-color:transparent} +.field-fileupload.style-image-multi .upload-object.upload-placeholder:after {opacity:0} +.field-fileupload.style-image-multi .upload-object:hover {background:#4da7e8 !important} +.field-fileupload.style-image-multi .upload-object:hover i, +.field-fileupload.style-image-multi .upload-object:hover p.size {color:#ecf0f1} +.field-fileupload.style-image-multi .upload-object:hover h4 {color:white} +.field-fileupload.style-image-multi .upload-object:hover .icon-container {border-right-color:#4da7e8 !important} +.field-fileupload.style-image-multi .upload-object:hover h4 {padding-right:35px} +.field-fileupload.style-image-multi.is-preview .upload-files-container {margin-left:0} +.form-sidebar .field-fileupload.style-image-multi .upload-files-container {margin-left:0} +.form-sidebar .field-fileupload.style-image-multi .upload-button {width:100%} +@media (max-width:1280px) {.field-fileupload.style-image-multi .upload-object {width:230px }} +@media (max-width:1024px) {.field-fileupload.style-image-multi .upload-button {width:100% }.field-fileupload.style-image-multi .upload-files-container {margin-left:0 }.field-fileupload.style-image-multi .upload-object {margin-right:0;display:block;width:auto }} +.field-fileupload.style-image-single.is-populated .upload-button {display:none} +.field-fileupload.style-image-single .upload-button {display:block;border:2px dashed #BDC3C7;background-clip:content-box;background-color:#F9F9F9;position:relative;outline:none;min-height:100px;min-width:100px} +.field-fileupload.style-image-single .upload-button .upload-button-icon {position:absolute;width:22px;height:22px;top:50%;left:50%;margin-top:-11px;margin-left:-11px} +.field-fileupload.style-image-single .upload-button .upload-button-icon:before {text-align:center;display:block;font-size:22px;height:22px;width:22px;line-height:22px;color:#BDC3C7} +.field-fileupload.style-image-single .upload-button .upload-button-icon.large-icon {width:34px;height:34px;top:50%;left:50%;margin-top:-17px;margin-left:-17px} +.field-fileupload.style-image-single .upload-button .upload-button-icon.large-icon:before {font-size:34px;height:24px;width:24px;line-height:24px} +.field-fileupload.style-image-single .upload-button:hover {border:2px dashed #1F99DC} +.field-fileupload.style-image-single .upload-button:hover .upload-button-icon:before {color:#1F99DC} +.field-fileupload.style-image-single .upload-button:focus {border:2px dashed #1F99DC} +.field-fileupload.style-image-single .upload-button:focus .upload-button-icon:before {color:#1F99DC} +.field-fileupload.style-image-single .upload-object {padding-bottom:66px} +.field-fileupload.style-image-single .upload-object .icon-container {border:1px solid #f6f8f9;background:rgba(255,255,255,0.5)} +.field-fileupload.style-image-single .upload-object .icon-container.image img {-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;display:block;max-width:100%;height:auto;min-height:100px;min-width:100px} +.field-fileupload.style-image-single .upload-object .progress-bar {display:block;width:100%;overflow:hidden;height:5px;background-color:#f5f5f5;border-radius:3px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);position:absolute;bottom:10px;left:0} +.field-fileupload.style-image-single .upload-object .progress-bar .upload-progress {float:left;width:0%;height:100%;line-height:5px;color:#fff;background-color:#5fb6f5;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:width 0.6s ease;transition:width 0.6s ease} +.field-fileupload.style-image-single .upload-object .info {position:absolute;left:0;right:0;bottom:0;height:66px} +.field-fileupload.style-image-single .upload-object .meta {position:absolute;bottom:65px;left:0;right:0;margin:0 15px} +.field-fileupload.style-image-single .upload-object:hover h4 {padding-right:20px} +@media (max-width:1024px) {.field-fileupload.style-image-single .upload-object h4 {padding-right:20px !important }} +.field-fileupload.style-file-multi .upload-button {margin-bottom:10px} +.field-fileupload.style-file-multi .upload-files-container {border:1px solid #eee;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;border-bottom:none;display:none} +.field-fileupload.style-file-multi.is-populated .upload-files-container {display:block} +.field-fileupload.style-file-multi .upload-object {display:block;width:100%;border-bottom:1px solid #eee;padding-left:10px} +.field-fileupload.style-file-multi .upload-object:nth-child(even) {background-color:#f5f5f5} +.field-fileupload.style-file-multi .upload-object .icon-container {position:absolute;top:0;left:10px;width:15px;padding:11px 7px} +.field-fileupload.style-file-multi .upload-object .icon-container i {line-height:150%;font-size:15px} +.field-fileupload.style-file-multi .upload-object .icon-container img {display:none} +.field-fileupload.style-file-multi .upload-object .info {margin-left:35px;margin-right:15%} +.field-fileupload.style-file-multi .upload-object .info h4, +.field-fileupload.style-file-multi .upload-object .info p {margin:0;padding:11px 0;font-size:12px;font-weight:normal;line-height:150%;color:#666} +.field-fileupload.style-file-multi .upload-object .info h4 {padding-right:15px} +.field-fileupload.style-file-multi .upload-object .info h4 a {padding:10px 0;right:15px} +.field-fileupload.style-file-multi .upload-object .info p.size {position:absolute;top:0;right:0;width:15%;display:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.field-fileupload.style-file-multi .upload-object .progress-bar {display:block;width:100%;overflow:hidden;height:5px;background-color:#f5f5f5;border-radius:3px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);position:absolute;top:18px;left:0} +.field-fileupload.style-file-multi .upload-object .progress-bar .upload-progress {float:left;width:0%;height:100%;line-height:5px;color:#fff;background-color:#5fb6f5;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:width 0.6s ease;transition:width 0.6s ease} +.field-fileupload.style-file-multi .upload-object .meta {position:absolute;top:0;right:0;margin-right:15px;width:15%} +.field-fileupload.style-file-multi .upload-object .meta a.drag-handle {top:-2px;bottom:auto;line-height:150%;padding:10px 0} +.field-fileupload.style-file-multi .upload-object .icon-container:after {width:20px;height:20px;margin-top:-10px;margin-left:-10px;background-size:20px 20px} +.field-fileupload.style-file-multi .upload-object.is-error .icon-container:after {font-size:20px} +.field-fileupload.style-file-multi .upload-object.is-success .info p.size {display:block} +.field-fileupload.style-file-multi .upload-object.upload-placeholder {height:35px;background-color:transparent} +.field-fileupload.style-file-multi .upload-object.upload-placeholder:after {opacity:0} +.field-fileupload.style-file-multi .upload-object:hover {background:#4da7e8 !important} +.field-fileupload.style-file-multi .upload-object:hover i, +.field-fileupload.style-file-multi .upload-object:hover p.size {color:#ecf0f1} +.field-fileupload.style-file-multi .upload-object:hover h4 {color:white} +.field-fileupload.style-file-multi .upload-object:hover .icon-container {border-right-color:#4da7e8 !important} +.field-fileupload.style-file-multi .upload-object:hover h4 {padding-right:35px} +@media (max-width:1199px) {.field-fileupload.style-file-multi .info {margin-right:20% !important }.field-fileupload.style-file-multi .info p.size {width:20% !important }.field-fileupload.style-file-multi .meta {width:20% !important }} +@media (max-width:991px) {.field-fileupload.style-file-multi .upload-object h4 {padding-right:35px !important }.field-fileupload.style-file-multi .info {margin-right:25% !important }.field-fileupload.style-file-multi .info p.size {width:25% !important;padding-right:35px !important }.field-fileupload.style-file-multi .meta {width:25% !important }} +.field-fileupload.style-file-single {background-color:#fff;border:1px solid #d1d6d9;overflow:hidden;position:relative;padding-right:30px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(209,214,217,0.25),0 1px 0 rgba(255,255,255,.5);box-shadow:inset 0 1px 0 rgba(209,214,217,0.25),0 1px 0 rgba(255,255,255,.5)} +.field-fileupload.style-file-single .upload-button {position:absolute;top:50%;margin-top:-44px;height:88px;background:transparent;right:-2px;color:#595959} +.field-fileupload.style-file-single .upload-button i {font-size:14px} +.field-fileupload.style-file-single .upload-button:hover {color:#333} +.field-fileupload.style-file-single .upload-empty-message {padding:8px 0 8px 11px;font-size:14px} +.field-fileupload.style-file-single.is-populated .upload-empty-message {display:none} +.field-fileupload.style-file-single .upload-object {display:block;width:100%;padding:7px 0 9px 0} +.field-fileupload.style-file-single .upload-object .icon-container {position:absolute;top:0;left:0;width:15px;padding:0 5px;margin:8px 0 0 7px;text-align:center} +.field-fileupload.style-file-single .upload-object .icon-container i {line-height:150%;font-size:15px} +.field-fileupload.style-file-single .upload-object .icon-container img {display:none} +.field-fileupload.style-file-single .upload-object .info {margin-left:34px;margin-right:15%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.field-fileupload.style-file-single .upload-object .info h4, +.field-fileupload.style-file-single .upload-object .info p {display:inline;margin:0;padding:0;font-size:13px;line-height:150%;color:#666} +.field-fileupload.style-file-single .upload-object .info p.size {font-weight:normal} +.field-fileupload.style-file-single .upload-object .info p.size:before {content:" - "} +.field-fileupload.style-file-single .upload-object .progress-bar {display:block;width:100%;overflow:hidden;height:5px;background-color:#f5f5f5;border-radius:3px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);position:absolute;top:50%;margin-top:-2px;right:5px} +.field-fileupload.style-file-single .upload-object .progress-bar .upload-progress {float:left;width:0%;height:100%;line-height:5px;color:#fff;background-color:#5fb6f5;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:width 0.6s ease;transition:width 0.6s ease} +.field-fileupload.style-file-single .upload-object .meta {position:absolute;top:50%;margin-top:-44px;height:88px;right:0;width:15%} +.field-fileupload.style-file-single .upload-object .meta .upload-remove-button {position:absolute;top:50%;right:0;height:20px;margin-top:-10px;margin-right:10px;z-index:100} +.field-fileupload.style-file-single .upload-object .icon-container:after {width:20px;height:20px;margin-top:-10px;margin-left:-10px;background-size:20px 20px} +.field-fileupload.style-file-single .upload-object.is-error .icon-container:after {font-size:20px} \ No newline at end of file diff --git a/formwidgets/responsiveimage/assets/js/fileupload.js b/formwidgets/responsiveimage/assets/js/fileupload.js new file mode 100644 index 0000000..88b4cd4 --- /dev/null +++ b/formwidgets/responsiveimage/assets/js/fileupload.js @@ -0,0 +1,478 @@ +/* + * File upload form field control + * + * Data attributes: + * - data-control="fileupload" - enables the file upload plugin + * - data-unique-id="XXX" - an optional identifier for multiple uploaders on the same page, this value will + * appear in the postback variable called X_OCTOBER_FILEUPLOAD + * - data-template - a Dropzone.js template to use for each item + * - data-error-template - a popover template used to show an error + * - data-sort-handler - AJAX handler for sorting postbacks + * - data-config-handler - AJAX handler for configuration popup + * + * JavaScript API: + * $('div').fileUploader() + * + * Dependancies: + * - Dropzone.js + */ ++function ($) { "use strict"; + + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + // FILEUPLOAD CLASS DEFINITION + // ============================ + + var FileUpload = function (element, options) { + this.$el = $(element) + this.options = options || {} + + $.oc.foundation.controlUtils.markDisposable(element) + Base.call(this) + this.init() + } + + FileUpload.prototype = Object.create(BaseProto) + FileUpload.prototype.constructor = FileUpload + + FileUpload.prototype.init = function() { + if (this.options.isMulti === null) { + this.options.isMulti = this.$el.hasClass('is-multi') + } + + if (this.options.isPreview === null) { + this.options.isPreview = this.$el.hasClass('is-preview') + } + + if (this.options.isSortable === null) { + this.options.isSortable = this.$el.hasClass('is-sortable') + } + + this.$el.one('dispose-control', this.proxy(this.dispose)) + this.$uploadButton = $('.upload-button', this.$el) + this.$filesContainer = $('.upload-files-container', this.$el) + this.uploaderOptions = {} + + this.$el.on('click', '.upload-object.is-success', this.proxy(this.onClickSuccessObject)) + this.$el.on('click', '.upload-object.is-error', this.proxy(this.onClickErrorObject)) + + // Stop here for preview mode + if (this.options.isPreview) + return + + this.$el.on('click', '.upload-remove-button', this.proxy(this.onRemoveObject)) + + this.bindUploader() + + if (this.options.isSortable) { + this.bindSortable() + } + + } + + FileUpload.prototype.dispose = function() { + + this.$el.off('click', '.upload-object.is-success', this.proxy(this.onClickSuccessObject)) + this.$el.off('click', '.upload-object.is-error', this.proxy(this.onClickErrorObject)) + this.$el.off('click', '.upload-remove-button', this.proxy(this.onRemoveObject)) + + this.$el.off('dispose-control', this.proxy(this.dispose)) + this.$el.removeData('oc.fileUpload') + + this.$el = null + this.$uploadButton = null + this.$filesContainer = null + this.uploaderOptions = null + + // In some cases options could contain callbacks, + // so it's better to clean them up too. + this.options = null + + BaseProto.dispose.call(this) + } + + // + // Uploading + // + + FileUpload.prototype.bindUploader = function() { + this.uploaderOptions = { + url: this.options.url, + paramName: this.options.paramName, + clickable: this.$uploadButton.get(0), + previewsContainer: this.$filesContainer.get(0), + maxFiles: !this.options.isMulti ? 1 : null, + maxFilesize: this.options.maxFilesize, + timeout: 0, + headers: {} + } + + if (this.options.fileTypes) { + this.uploaderOptions.acceptedFiles = this.options.fileTypes + } + + if (this.options.template) { + this.uploaderOptions.previewTemplate = $(this.options.template).html() + } + + this.uploaderOptions.thumbnailWidth = this.options.thumbnailWidth + ? this.options.thumbnailWidth : null + + this.uploaderOptions.thumbnailHeight = this.options.thumbnailHeight + ? this.options.thumbnailHeight : null + + this.uploaderOptions.resize = this.onResizeFileInfo + + /* + * Add CSRF token to headers + */ + var token = $('meta[name="csrf-token"]').attr('content') + if (token) { + this.uploaderOptions.headers['X-CSRF-TOKEN'] = token + } + + this.dropzone = new Dropzone(this.$el.get(0), this.uploaderOptions) + this.dropzone.on('addedfile', this.proxy(this.onUploadAddedFile)) + this.dropzone.on('sending', this.proxy(this.onUploadSending)) + this.dropzone.on('success', this.proxy(this.onUploadSuccess)) + this.dropzone.on('error', this.proxy(this.onUploadError)) + } + + FileUpload.prototype.onResizeFileInfo = function(file) { + var info, + targetWidth, + targetHeight + + if (!this.options.thumbnailWidth && !this.options.thumbnailWidth) { + targetWidth = targetHeight = 100 + } + else if (this.options.thumbnailWidth) { + targetWidth = this.options.thumbnailWidth + targetHeight = this.options.thumbnailWidth * file.height / file.width + } + else if (this.options.thumbnailHeight) { + targetWidth = this.options.thumbnailHeight * file.height / file.width + targetHeight = this.options.thumbnailHeight + } + + // drawImage(image, srcX, srcY, srcWidth, srcHeight, trgX, trgY, trgWidth, trgHeight) takes an image, clips it to + // the rectangle (srcX, srcY, srcWidth, srcHeight), scales it to dimensions (trgWidth, trgHeight), and draws it + // on the canvas at coordinates (trgX, trgY). + info = { + srcX: 0, + srcY: 0, + srcWidth: file.width, + srcHeight: file.height, + trgX: 0, + trgY: 0, + trgWidth: targetWidth, + trgHeight: targetHeight + } + + return info + } + + FileUpload.prototype.onUploadAddedFile = function(file) { + var $object = $(file.previewElement).data('dzFileObject', file), + filesize = this.getFilesize(file) + + // Change filesize format to match October\Rain\Filesystem\Filesystem::sizeToString() format + $(file.previewElement).find('[data-dz-size]').html('' + filesize.size + ' ' + filesize.units) + + // Remove any exisiting objects for single variety + if (!this.options.isMulti) { + this.removeFileFromElement($object.siblings()) + } + + this.evalIsPopulated() + } + + FileUpload.prototype.onUploadSending = function(file, xhr, formData) { + this.addExtraFormData(formData) + xhr.setRequestHeader('X-OCTOBER-REQUEST-HANDLER', this.options.uploadHandler) + } + + FileUpload.prototype.onUploadSuccess = function(file, response) { + var $preview = $(file.previewElement), + $img = $('.image img', $preview) + + $preview.addClass('is-success') + + if (response.id) { + $preview.data('id', response.id) + $preview.data('path', response.path) + $('.upload-remove-button', $preview).data('request-data', { file_id: response.id }) + $img.attr('src', response.thumb) + } + + this.triggerChange(); + } + + FileUpload.prototype.onUploadError = function(file, error) { + var $preview = $(file.previewElement) + $preview.addClass('is-error') + } + + /* + * Trigger change event (Compatibility with october.form.js) + */ + FileUpload.prototype.triggerChange = function() { + this.$el.closest('[data-field-name]').trigger('change.oc.formwidget') + } + + FileUpload.prototype.addExtraFormData = function(formData) { + if (this.options.extraData) { + $.each(this.options.extraData, function (name, value) { + formData.append(name, value) + }) + } + + var $form = this.$el.closest('form') + if ($form.length > 0) { + $.each($form.serializeArray(), function (index, field) { + formData.append(field.name, field.value) + }) + } + } + + FileUpload.prototype.removeFileFromElement = function($element) { + var self = this + + $element.each(function() { + var $el = $(this), + obj = $el.data('dzFileObject') + + if (obj) { + self.dropzone.removeFile(obj) + } + else { + $el.remove() + } + }) + } + + // + // Sorting + // + + FileUpload.prototype.bindSortable = function() { + var + self = this, + placeholderEl = $('
').css({ + width: this.options.imageWidth, + height: this.options.imageHeight + }) + + this.$filesContainer.sortable({ + itemSelector: 'div.upload-object.is-success', + nested: false, + tolerance: -100, + placeholder: placeholderEl, + handle: '.drag-handle', + onDrop: function ($item, container, _super) { + _super($item, container) + self.onSortAttachments() + }, + distance: 10 + }) + } + + FileUpload.prototype.onSortAttachments = function() { + if (this.options.sortHandler) { + + /* + * Build an object of ID:ORDER + */ + var orderData = {} + + this.$el.find('.upload-object.is-success') + .each(function(index){ + var id = $(this).data('id') + orderData[id] = index + 1 + }) + + this.$el.request(this.options.sortHandler, { + data: { sortOrder: orderData } + }) + } + } + + // + // User interaction + // + + FileUpload.prototype.onRemoveObject = function(ev) { + var self = this, + $object = $(ev.target).closest('.upload-object') + + $(ev.target) + .closest('.upload-remove-button') + .one('ajaxPromise', function(){ + $object.addClass('is-loading') + }) + .one('ajaxDone', function(){ + self.removeFileFromElement($object) + self.evalIsPopulated() + self.triggerChange() + }) + .request() + + ev.stopPropagation() + } + + FileUpload.prototype.onClickSuccessObject = function(ev) { + if ($(ev.target).closest('.meta').length) return + + var $target = $(ev.target).closest('.upload-object') + + if (!this.options.configHandler) { + window.open($target.data('path')) + return + } + + $target.popup({ + handler: this.options.configHandler, + extraData: { file_id: $target.data('id') } + }) + + $target.one('popupComplete', function(event, element, modal){ + + modal.one('ajaxDone', 'button[type=submit]', function(e, context, data) { + if (data.displayName) { + $('[data-dz-name]', $target).text(data.displayName) + } + }) + }) + } + + FileUpload.prototype.onClickErrorObject = function(ev) { + var + self = this, + $target = $(ev.target).closest('.upload-object'), + errorMsg = $('[data-dz-errormessage]', $target).text(), + $template = $(this.options.errorTemplate) + + // Remove any exisiting objects for single variety + if (!this.options.isMulti) { + this.removeFileFromElement($target.siblings()) + } + + $target.ocPopover({ + content: Mustache.render($template.html(), { errorMsg: errorMsg }), + modal: true, + highlightModalTarget: true, + placement: 'top', + fallbackPlacement: 'left', + containerClass: 'popover-danger' + }) + + var $container = $target.data('oc.popover').$container + $container.one('click', '[data-remove-file]', function() { + $target.data('oc.popover').hide() + self.removeFileFromElement($target) + self.evalIsPopulated() + }) + } + + // + // Helpers + // + + FileUpload.prototype.evalIsPopulated = function() { + var isPopulated = !!$('.upload-object', this.$filesContainer).length + this.$el.toggleClass('is-populated', isPopulated) + + // Reset maxFiles counter + if (!isPopulated) { + this.dropzone.removeAllFiles() + } + } + + /* + * Replicates the formatting of October\Rain\Filesystem\Filesystem::sizeToString(). This method will return + * an object with the file size amount and the unit used as `size` and `units` respectively. + */ + FileUpload.prototype.getFilesize = function (file) { + var formatter = new Intl.NumberFormat('en', { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }), + size = 0, + units = 'bytes' + + if (file.size >= 1073741824) { + size = formatter.format(file.size / 1073741824) + units = 'GB' + } else if (file.size >= 1048576) { + size = formatter.format(file.size / 1048576) + units = 'MB' + } else if (file.size >= 1024) { + size = formatter.format(file.size / 1024) + units = 'KB' + } else if (file.size > 1) { + size = file.size + units = 'bytes' + } else if (file.size == 1) { + size = 1 + units = 'byte' + } + + return { + size: size, + units: units + } + } + + FileUpload.DEFAULTS = { + url: window.location, + uploadHandler: null, + configHandler: null, + sortHandler: null, + uniqueId: null, + extraData: {}, + paramName: 'file_data', + fileTypes: null, + maxFilesize: 256, + template: null, + errorTemplate: null, + isMulti: null, + isPreview: null, + isSortable: null, + thumbnailWidth: 120, + thumbnailHeight: 120 + } + + // FILEUPLOAD PLUGIN DEFINITION + // ============================ + + var old = $.fn.fileUploader + + $.fn.fileUploader = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('oc.fileUpload') + var options = $.extend({}, FileUpload.DEFAULTS, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('oc.fileUpload', (data = new FileUpload(this, options))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.fileUploader.Constructor = FileUpload + + // FILEUPLOAD NO CONFLICT + // ================= + + $.fn.fileUploader.noConflict = function () { + $.fn.fileUpload = old + return this + } + + // FILEUPLOAD DATA-API + // =============== + $(document).render(function () { + $('[data-control="fileupload"]').fileUploader() + }) + +}(window.jQuery); diff --git a/formwidgets/responsiveimage/assets/less/fileupload.base.less b/formwidgets/responsiveimage/assets/less/fileupload.base.less new file mode 100644 index 0000000..d8804ca --- /dev/null +++ b/formwidgets/responsiveimage/assets/less/fileupload.base.less @@ -0,0 +1,413 @@ +.uploader-object-active() { + background: @fileupload-object-active-bg !important; + + i, p.size { + color: #ecf0f1; + } + + h4 { + color: white; + } + + .icon-container { + border-right-color: @fileupload-object-active-bg !important; + } +} + +.uploader-progress-bar() { + display: block; + width: 100%; + overflow: hidden; + height: @fileupload-progress-bar-height; + background-color: @fileupload-progress-bar-bg; + border-radius: @border-radius-base; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); + + .upload-progress { + float: left; + width: 0%; + height: 100%; + line-height: @fileupload-progress-bar-height; + color: @fileupload-progress-bar-color; + background-color: #5fb6f5; + .box-shadow(none); + .transition(width .6s ease); + } +} + +.uploader-block-button() { + display: block; + border: 2px dashed #BDC3C7; + background-clip: content-box; + background-color: #F9F9F9; + position: relative; + outline: none; + + .upload-button-icon { + position: absolute; + width: 22px; + height: 22px; + top: 50%; + left: 50%; + margin-top: -11px; + margin-left: -11px; + + &:before { + text-align: center; + display: block; + font-size: 22px; + height: 22px; + width: 22px; + line-height: 22px; + color: #BDC3C7; + } + + &.large-icon { + width: 34px; + height: 34px; + top: 50%; + left: 50%; + margin-top: -17px; + margin-left: -17px; + + &:before { + font-size: 34px; + height: 24px; + width: 24px; + line-height: 24px; + } + } + } + + &:hover { + border: 2px dashed #1F99DC; + + .upload-button-icon:before { + color: #1F99DC; + } + } + + &:focus { + border: 2px dashed #1F99DC; + + .upload-button-icon:before { + color: #1F99DC; + } + } +} + +.uploader-small-loader() { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + background-size: 20px 20px; +} + +.uploader-vertical-align() { + position: absolute; + top: 50%; + margin-top: -44px; + height: 88px; +} + +// +// Shared +// + +.field-fileupload { + + // + // Uploaded item + // + + .upload-object { + + .border-radius(3px); + position: relative; + outline: none; + overflow: hidden; + display: inline-block; + vertical-align: top; + + img { + width: 100%; + height: 100%; + } + + .icon-container { + display: table; + opacity: .6; + + i { + color: #95a5a6; + display: inline-block; + } + + div { + display: table-cell; + text-align: center; + vertical-align: middle; + } + } + + .icon-container.image { + > div.icon-wrapper { + display: none; + } + } + + h4 { + font-size: 13px; + color: #2A3E51; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 150%; + margin: 15px 0 5px 0; + padding-right: 0; + .transition(padding 0.1s); + + position: relative; + + a { + position: absolute; + right: 0; + top: 0; + display: none; + font-weight: 400; + } + } + + p.size { + font-size: 12px; + color: #95a5a6; + strong { font-weight: 400; } + } + + .meta { + .drag-handle { + position: absolute; + bottom: 0; + right: 0; + cursor: move; + display: block; + } + } + + .info h4 a, + .meta a.upload-remove-button, + .meta a.drag-handle { + color: #2b3e50; + display: none; + font-size: 13px; + text-decoration: none; + } + + } + + // + // Loading State + // + + .upload-object { + .icon-container { + position: relative; + } + + .icon-container:after { + background-image: url('../../../../../system/assets/ui/images/loader-transparent.svg'); + position: absolute; + content: ' '; + width: 40px; + height: 40px; + left: 50%; + top: 50%; + margin-top: -20px; + margin-left: -20px; + display: block; + background-size: 40px 40px; + background-position: 50% 50%; + .animation(spin 1s linear infinite); + } + + &.is-success { + .icon-container { + opacity: 1; + } + .icon-container:after { + opacity: 0; + .transition(opacity .3s ease); + } + } + + // Replaces the loader with an error symbol + &.is-error { + .icon-container:after { + content: ""; + background: none; + .icon(@exclamation-triangle); + .animation(none); + font-size: 40px; + color: #ab2a1c; + margin-top: -20px; + margin-left: -20px; + text-shadow: 2px 2px 0 #fff; + } + } + + &.is-loading { + .icon-container { + opacity: .6; + } + .icon-container:after { + opacity: 1; + .transition(opacity .3s ease); + } + } + } + + // + // Success state + // + + .upload-object.is-success { + cursor: pointer; + + .progress-bar { + opacity: 0; + .transition(opacity .3s ease); + } + + &:hover { + h4 a, + .meta .upload-remove-button, + .meta .drag-handle { display: block; } + } + } + + // + // Error State + // + + .upload-object.is-error { + cursor: pointer; + + .icon-container { + opacity: 1; + > img, > i { + opacity: .5; + } + } + + .info h4 { + color: #ab2a1c; + a { + display: none; + } + } + + .meta { + display: none; + } + } + + // + // Sortable + // + + &.is-sortable { + position: relative; + + .upload-placeholder { + position: relative; + border: 1px dotted #e0e0e0 !important; + } + + .upload-object.dragged { + position: absolute; + .opacity(.5); + z-index: 2000; + .uploader-toolbar { + display: none; + } + } + } + + // + // Preview mode + // + + &.is-preview { + .upload-button, + .upload-remove-button, + .meta a.drag-handle { + display: none !important; + } + } +} + +// +// Media +// + +@media (max-width: 1024px) { + .field-fileupload { + .upload-object.is-success { + h4 a, + .meta .upload-remove-button, + .meta .drag-handle { display: block !important; } + } + } +} + +// +// Config form +// + +.fileupload-config-form { + .fileupload-url-button{ + padding-left: 0; + > i { + color: #666; + } + } + + .file-upload-modal-image-header { + // Photoshop transparent background + // Based on: http://lea.verou.me/css3patterns/#checkerboard + background-color: #FEFEFE; + background-image: -webkit-linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB), -webkit-linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB); + background-image: -moz-linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB), -moz-linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB); + background-image: -o-linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB), -o-linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB); + background-image: -ms-linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB), -ms-linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB); + background-image: linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB), linear-gradient(45deg, #CBCBCB 25%, transparent 25%, transparent 75%, #CBCBCB 75%, #CBCBCB); + -webkit-background-size: 20px 20px; + -moz-background-size: 20px 20px; + background-size: 20px 20px; + background-position: 0 0, 10px 10px; + + + &, img { + .border-top-radius(2px); + } + + .close { + position: absolute; + top: 20px; + right: 20px; + background: #BDC3C7; + opacity: .7; + height: 24px; + width: 22px; + + &:hover, &:focus { + opacity: .9; + } + } + } + + .file-upload-modal-image-header + .modal-body { + padding-top: @padding-standard; + } +} diff --git a/formwidgets/responsiveimage/assets/less/fileupload.filemulti.less b/formwidgets/responsiveimage/assets/less/fileupload.filemulti.less new file mode 100644 index 0000000..5fa0106 --- /dev/null +++ b/formwidgets/responsiveimage/assets/less/fileupload.filemulti.less @@ -0,0 +1,176 @@ +// +// Multi File +// + +.field-fileupload.style-file-multi { + .upload-button { + margin-bottom: 10px; + } + + .upload-files-container { + border: 1px solid @fileupload-list-border-color; + .border-radius(3px); + border-bottom: none; + display: none; + } + + &.is-populated .upload-files-container { + display: block; + } + + .upload-object { + display: block; + width: 100%; + border-bottom: 1px solid @fileupload-list-border-color; + padding-left: 10px; + + &:nth-child(even) { + background-color: @fileupload-list-accent-bg; + } + + .icon-container { + position: absolute; + top: 0; + left: 10px; + width: 15px; + padding: 11px 7px; + + i { + line-height: 150%; + font-size: 15px; + } + + img { display: none; } + } + + .info { + margin-left: 35px; + margin-right: 15%; + + h4, p { + margin: 0; + padding: 11px 0; + font-size: 12px; + font-weight: normal; + line-height: 150%; + color: #666666; + } + + h4 { + padding-right: 15px; + + a { + padding: 10px 0; + right: 15px; + } + } + + p.size { + position: absolute; + top: 0; + right: 0; + width: 15%; + display: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .progress-bar { + .uploader-progress-bar(); + position: absolute; + top: 18px; + left: 0; + } + + .meta { + position: absolute; + top: 0; + right: 0; + margin-right: 15px; + width: 15%; + + a.drag-handle { + top: -2px; + bottom: auto; + line-height: 150%; + padding: 10px 0; + } + } + + .icon-container:after { + .uploader-small-loader(); + } + + &.is-error .icon-container:after { + font-size: 20px; + } + + // + // Success + // + + &.is-success { + .info p.size { display: block; } + } + + // + // Sorting + // + + &.upload-placeholder { + height: 35px; + background-color: transparent; + &:after { opacity: 0; } + } + + // + // Hover + // + + &:hover { + .uploader-object-active(); + h4 { padding-right: 35px; } + } + } +} + +// +// Media +// + +@media (max-width: @screen-md-max) { + .field-fileupload.style-file-multi { + .info { + margin-right: 20% !important; + p.size { + width: 20% !important; + } + } + + .meta { + width: 20% !important; + } + } +} + +@media (max-width: @screen-sm-max) { + .field-fileupload.style-file-multi { + .upload-object { + h4 { padding-right: 35px !important; } + } + + .info { + margin-right: 25% !important; + p.size { + width: 25% !important; + padding-right: 35px !important; + } + } + + .meta { + width: 25% !important; + } + } +} \ No newline at end of file diff --git a/formwidgets/responsiveimage/assets/less/fileupload.filesingle.less b/formwidgets/responsiveimage/assets/less/fileupload.filesingle.less new file mode 100644 index 0000000..e6305be --- /dev/null +++ b/formwidgets/responsiveimage/assets/less/fileupload.filesingle.less @@ -0,0 +1,119 @@ +// +// Single File +// + +.field-fileupload.style-file-single { + background-color: @color-form-field-bg; + border: 1px solid @color-form-field-border; + overflow: hidden; + position: relative; + padding-right: 30px; + border-radius: 3px; + .box-shadow(@input-box-shadow); + + .upload-button { + .uploader-vertical-align(); + background: transparent; + right: -2px; + + color: lighten(@color-form-field-recordfinder-btn, 15%); + + i { + font-size: 14px; + } + + &:hover { + color: @color-form-field-recordfinder-btn; + } + } + + .upload-empty-message { + padding: 8px 0 8px 11px; + font-size: 14px; + } + + &.is-populated { + .upload-empty-message { + display: none; + } + } + + .upload-object { + display: block; + width: 100%; + padding: 7px 0 9px 0; + + .icon-container { + position: absolute; + top: 0; + left: 0; + width: 15px; + padding: 0 5px; + margin: 8px 0 0 7px; + text-align: center; + + i { + line-height: 150%; + font-size: 15px; + } + + img { display: none; } + } + + .info { + margin-left: 34px; + margin-right: 15%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + h4, p { + display: inline; + margin: 0; + padding: 0; + font-size: 13px; + line-height: 150%; + color: #666666; + } + + p.size { + font-weight: normal; + &:before { + content: " - "; + } + } + } + + .progress-bar { + .uploader-progress-bar(); + position: absolute; + top: 50%; + margin-top: -2px; + right: 5px; + } + + .meta { + .uploader-vertical-align(); + right: 0; + width: 15%; + .upload-remove-button { + position: absolute; + top: 50%; + right: 0; + height: 20px; + margin-top: -10px; + margin-right: 10px; + z-index: 100; + } + } + + .icon-container:after { + .uploader-small-loader(); + } + + &.is-error .icon-container:after { + font-size: 20px; + } + } + +} \ No newline at end of file diff --git a/formwidgets/responsiveimage/assets/less/fileupload.imagemulti.less b/formwidgets/responsiveimage/assets/less/fileupload.imagemulti.less new file mode 100644 index 0000000..79c2eb3 --- /dev/null +++ b/formwidgets/responsiveimage/assets/less/fileupload.imagemulti.less @@ -0,0 +1,134 @@ +// +// Multi Image +// + +.field-fileupload.style-image-multi { + .upload-button, + .upload-object { + margin: 0 10px 10px 0; + } + + .upload-button { + .uploader-block-button(); + float: left; + width: 76px; + height: 76px; + } + + .upload-files-container { + margin-left: 90px; + } + + .upload-object { + background: #fff; + border: 1px solid #ecf0f1; + width: 260px; + + .progress-bar { + .uploader-progress-bar(); + position: absolute; + bottom: 10px; + left: 0; + } + + .icon-container { + border-right: 1px solid #f6f8f9; + float: left; + display: inline-block; + overflow: hidden; + width: 75px; + height: 75px; + + i { + font-size: 35px; + } + + &.image img { + .border-left-radius(3px); + width: auto; + } + } + + .info { + margin-left: 90px; + + h4 { + padding-right: 15px; + a { + right: 15px; + } + } + } + + .meta { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0 15px 0 90px; + + a.drag-handle { + bottom: 15px; + } + } + + &.upload-placeholder { + height: 75px; + background-color: transparent; + &:after { opacity: 0; } + } + + &:hover { + .uploader-object-active(); + h4 { padding-right: 35px; } + } + } + + &.is-preview { + .upload-files-container { + margin-left: 0; + } + } +} + +// +// On Sidebar +// + +.form-sidebar .field-fileupload.style-image-multi .upload-files-container { + margin-left: 0px; +} + +.form-sidebar .field-fileupload.style-image-multi .upload-button { + width: 100%; +} + +// +// Media +// + +@media (max-width: 1280px) { + .field-fileupload.style-image-multi { + .upload-object { + width: 230px; + } + } +} + +@media (max-width: 1024px) { + .field-fileupload.style-image-multi { + .upload-button { + width: 100%; + } + + .upload-files-container { + margin-left: 0; + } + + .upload-object { + margin-right: 0; + display: block; + width: auto; + } + } +} \ No newline at end of file diff --git a/formwidgets/responsiveimage/assets/less/fileupload.imagesingle.less b/formwidgets/responsiveimage/assets/less/fileupload.imagesingle.less new file mode 100644 index 0000000..a0b4982 --- /dev/null +++ b/formwidgets/responsiveimage/assets/less/fileupload.imagesingle.less @@ -0,0 +1,78 @@ +// +// Single Image +// + +.field-fileupload.style-image-single { + &.is-populated { + .upload-button { + display: none; + } + } + + .upload-button { + .uploader-block-button(); + min-height: 100px; + min-width: 100px; + } + + .upload-object { + padding-bottom: 66px; + + .icon-container { + border: 1px solid #f6f8f9; + background: rgba(255,255,255,.5); + + &.image img { + .border-radius(3px); + .img-responsive(); + + // This is needed when the image is very large and + // being processed by dropzone on the client-side + // the image has no height or width. + min-height: 100px; + min-width: 100px; + } + } + + .progress-bar { + .uploader-progress-bar(); + position: absolute; + bottom: 10px; + left: 0; + } + + .info { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 66px; + } + + .meta { + position: absolute; + bottom: 65px; + left: 0; + right: 0; + margin: 0 15px; + } + + &:hover { + h4 { padding-right: 20px; } + } + } + +} + + +// +// Media +// + +@media (max-width: 1024px) { + .field-fileupload.style-image-single { + .upload-object { + h4 { padding-right: 20px !important; } + } + } +} \ No newline at end of file diff --git a/formwidgets/responsiveimage/assets/less/fileupload.less b/formwidgets/responsiveimage/assets/less/fileupload.less new file mode 100644 index 0000000..3415f98 --- /dev/null +++ b/formwidgets/responsiveimage/assets/less/fileupload.less @@ -0,0 +1,15 @@ +@import "../../../../assets/less/core/boot.less"; + +@fileupload-progress-bar-height: 5px; +@fileupload-progress-bar-color: #fff; +@fileupload-progress-bar-bg: #f5f5f5; +@fileupload-inactive-icon: #808b93; +@fileupload-object-active-bg: #4da7e8; +@fileupload-list-accent-bg: #f5f5f5; +@fileupload-list-border-color: #eeeeee; + +@import "fileupload.base.less"; +@import "fileupload.imagemulti.less"; +@import "fileupload.imagesingle.less"; +@import "fileupload.filemulti.less"; +@import "fileupload.filesingle.less"; diff --git a/formwidgets/responsiveimage/partials/._responsiveimage.htm.swp b/formwidgets/responsiveimage/partials/._responsiveimage.htm.swp new file mode 100644 index 0000000000000000000000000000000000000000..46aa16e818de14d7b28f2cf245c0a55ff941d281 GIT binary patch literal 12288 zcmeI2zi$&U6vtgARA?!%F}X+8B*W$Su~a3!P7IYO6sbi*2nFZPNvzxt%XS({Ran^h z1NcXn5G)}60|pov*ccdC*m(BcH4%k^L>JU&>8sP(@BH5T_??yL+_`jTZJjPPuMlW+ zgnYmMtbOuAI5qe@O{7Q?Zu|exoSn~9)IZ$tu`>)tqfrn`-5caQ$pV=Sdm_^T8}VER zifi$RtDu{!bR^;)H?B|SnHJkzq^!pS$&?W+(Lo!mCr6rnlO7%cjv|3$N?>TZ{?ZJ& zdgbyZOW>k6PtTv*I;Jo%Z6tsMkN^@u0!RP}AOR$BJP8;%MV>(FC(86|<#p<(Yk7+o zB!C2v01`j~NB{{S0VIF~kN^@u0!ZLLB*0=q?oJbO_7nsU-~X%s|8LI_@&?2}4?wp; z8=wHx1kHlp&l2()^a}JG)CJuF-2^p2-)0E;2>Jke0eT8bK?!IJvrD*KZ1EEZk58p4lG zXstj;N=OsByP_u++v8i;)fpO-XEaKf)+=7eWEA9U9_3lg)Q(4EW|*&dns0NzD-s^I zR#zym8^tnRuXR{70JqD_3Cns-J3f?2&f;eAMBDb%8Vv}l6%?+<{#y`D2;eFt>&PPh=2p0hv+uL%DnC8^k_=U~#k@(Xh z>)E2446=AXCLESV|LoBn?*3DX&giIeEIj@y$s)bN*@65D0_C#S7vSZ9^4VF>W8PRM zq})Fe&BsJqTC)9(!5(e*VNWnBPrO5m9t@jO@om9J?JRd2fNsM(rr{KPRr+>jsJOx_ iY+3cPcFX5%RCIBX&M(*kx*@dj!zdqSrrv0{_ + + + + + + +
+ + +
+ + + + + + +
diff --git a/formwidgets/responsiveimage/partials/_file_multi.htm b/formwidgets/responsiveimage/partials/_file_multi.htm new file mode 100644 index 0000000..e478a7c --- /dev/null +++ b/formwidgets/responsiveimage/partials/_file_multi.htm @@ -0,0 +1,73 @@ +
data-config-handler="getEventHandler('onLoadAttachmentConfig') ?>" + data-file-types="" +> + + + + + +
+ +
+
+ +
+
+

+ title ?: $file->file_name) ?> + +

+

sizeToString()) ?>

+
+
+ +
+
+ +
+
+ + + diff --git a/formwidgets/responsiveimage/partials/_file_single.htm b/formwidgets/responsiveimage/partials/_file_single.htm new file mode 100644 index 0000000..fbf1530 --- /dev/null +++ b/formwidgets/responsiveimage/partials/_file_single.htm @@ -0,0 +1,76 @@ +
data-config-handler="getEventHandler('onLoadAttachmentConfig') ?>" + data-file-types="" +> + + + + + +
+ +
+
+ +
+
+

+ title ?: $singleFile->file_name) ?> +

+

sizeToString()) ?>

+
+
+ +
+
+ +
+ + +
+ +
+ +
+ + + diff --git a/formwidgets/responsiveimage/partials/_image_multi.htm b/formwidgets/responsiveimage/partials/_image_multi.htm new file mode 100644 index 0000000..1f43e6c --- /dev/null +++ b/formwidgets/responsiveimage/partials/_image_multi.htm @@ -0,0 +1,72 @@ +
data-config-handler="getEventHandler('onLoadAttachmentConfig') ?>" + data-file-types="" +> + + + + + + + +
+ +
+
+ +
+
+

+ title ?: $file->file_name) ?> + +

+

sizeToString()) ?>

+
+
+ +
+
+ +
+
+ + + diff --git a/formwidgets/responsiveimage/partials/_image_single.htm b/formwidgets/responsiveimage/partials/_image_single.htm new file mode 100644 index 0000000..069ff1a --- /dev/null +++ b/formwidgets/responsiveimage/partials/_image_single.htm @@ -0,0 +1,74 @@ +
data-config-handler="getEventHandler('onLoadAttachmentConfig') ?>" + data-file-types="" +> + + + + + + + +
+ +
+
+ +
+
+

+ title ?: $singleFile->file_name) ?> + +

+

sizeToString()) ?>

+
+
+
+ +
+ +
+ + + diff --git a/formwidgets/responsiveimage/partials/_responsiveimage.htm b/formwidgets/responsiveimage/partials/_responsiveimage.htm new file mode 100644 index 0000000..c8b0fde --- /dev/null +++ b/formwidgets/responsiveimage/partials/_responsiveimage.htm @@ -0,0 +1,39 @@ +previewMode && !$fileList->count()): ?> + + + + + + + makePartial('image_single') ?> + + + + makePartial('image_multi') ?> + + + + makePartial('file_single') ?> + + + + makePartial('file_multi') ?> + + + + + + + + diff --git a/traits/ResponsiveSaver.php b/traits/ResponsiveSaver.php new file mode 100644 index 0000000..bc0e1d8 --- /dev/null +++ b/traits/ResponsiveSaver.php @@ -0,0 +1,120 @@ +push()`. + * + * @package october\backend + * @author Alexey Bobkov, Samuel Georges + */ +trait ResponsiveSaver +{ + /** + * @var array List of prepared models that require saving. + */ + protected $modelsToSave = []; + + /** + * Takes a model and fills it with data from a multidimensional array. + * If an attribute is found to be a relationship, that relationship + * is also filled. + * + * $modelsToSave = $this->prepareModelsToSave($model, [...]); + * + * foreach ($modelsToSave as $modelToSave) { + * $modelToSave->save(); + * } + * + * @param \October\Rain\Database\Model $model Model to fill. + * @param array $saveData Attribute values to fill model. + * @return array The collection of models to save. + */ + protected function prepareModelsToSave($model, $saveData) + { + $this->modelsToSave = []; + $this->setModelAttributes($model, $saveData); + $this->modelsToSave = array_reverse($this->modelsToSave); + return $this->modelsToSave; + } + + /** + * Sets a data collection to a model attributes, relations are also set. + * + * @param \October\Rain\Database\Model $model Model to fill. + * @param array $saveData Attribute values to fill model. + * @return void + */ + protected function setModelAttributes($model, $saveData) + { + $this->modelsToSave[] = $model; + + if (!is_array($saveData)) { + return; + } + + if ($model instanceof HalcyonModel) { + $model->fill($saveData); + return; + } + + $attributesToPurge = []; + $singularTypes = ['belongsTo', 'hasOne', 'morphTo', 'morphOne']; + + foreach ($saveData as $attribute => $value) { + $isNested = $attribute == 'pivot' || ( + $model->hasRelation($attribute) && + in_array($model->getRelationType($attribute), $singularTypes) + ); + + if ($isNested && is_array($value)) { + $this->setModelAttributes($model->{$attribute}, $value); + } + elseif ($value !== FormField::NO_SAVE_DATA) { + if (Str::startsWith($attribute, '_')) { + $attributesToPurge[] = $attribute; + } + $model->{$attribute} = $value; + } + } + + if ($attributesToPurge) { + $this->deferPurgedSaveAttributes($model, $attributesToPurge); + } + } + + /** + * Removes an array of attributes from the model. If the model implements + * the Purgeable trait, this is preferred over the internal logic. + * + * @param \October\Rain\Database\Model $model Model to adjust. + * @param array $attributesToPurge Attribute values to remove from the model. + * @return void + */ + protected function deferPurgedSaveAttributes($model, $attributesToPurge) + { + if (!is_array($attributesToPurge)) { + return; + } + + /* + * Compatibility with Purgeable trait: + * This will give the ability to restore purged attributes + * and make them available again if necessary. + */ + if (method_exists($model, 'getPurgeableAttributes')) { + $model->addPurgeable($attributesToPurge); + } + else { + $model->bindEventOnce('model.saveInternal', function () use ($model, $attributesToPurge) { + foreach ($attributesToPurge as $attribute) { + unset($model->attributes[$attribute]); + } + }); + } + } +} diff --git a/traits/ResponsiveWidget.php b/traits/ResponsiveWidget.php new file mode 100644 index 0000000..cbc5cbb --- /dev/null +++ b/traits/ResponsiveWidget.php @@ -0,0 +1,101 @@ +resolveModelAttribute($this->valueFrom); + * @param string $attribute. + * @return array + */ + public function resolveModelAttribute($attribute) + { + try { + return $this->formField->resolveModelAttribute($this->model, $attribute); + } + catch (Exception $ex) { + throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', [ + 'class' => get_class($this->model), + 'relation' => $attribute + ])); + } + } + + /** + * Returns the model of a relation type, + * supports nesting via HTML array. + * @return Relation + */ + protected function getRelationModel() + { + list($model, $attribute) = $this->resolveModelAttribute($this->valueFrom); + + if (!$model) { + throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', [ + 'class' => get_class($this->model), + 'relation' => $this->valueFrom + ])); + } + + if (!$model->hasRelation($attribute)) { + throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', [ + 'class' => get_class($model), + 'relation' => $attribute + ])); + } + + return $model->makeRelation($attribute); + } + + /** + * Returns the value as a relation object from the model, + * supports nesting via HTML array. + * @return Relation + */ + protected function getRelationObject() + { + list($model, $attribute) = $this->resolveModelAttribute($this->valueFrom); + + if (!$model) { + throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', [ + 'class' => get_class($this->model), + 'relation' => $this->valueFrom + ])); + } + + if (!$model->hasRelation($attribute)) { + throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', [ + 'class' => get_class($model), + 'relation' => $attribute + ])); + } + + return $model->{$attribute}(); + } + + /** + * Returns the value as a relation type from the model, + * supports nesting via HTML array. + * @return Relation + */ + protected function getRelationType() + { + list($model, $attribute) = $this->resolveModelAttribute($this->valueFrom); + return $model->getRelationType($attribute); + } +}