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 0000000..14d77f9 Binary files /dev/null and b/formwidgets/.Responsiveimage.php.swp differ diff --git a/formwidgets/Responsiveimage.php b/formwidgets/Responsiveimage.php new file mode 100644 index 0000000..bc7a738 --- /dev/null +++ b/formwidgets/Responsiveimage.php @@ -0,0 +1,528 @@ + '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 0000000..46aa16e Binary files /dev/null and b/formwidgets/responsiveimage/partials/._responsiveimage.htm.swp differ diff --git a/formwidgets/responsiveimage/partials/_config_form.htm b/formwidgets/responsiveimage/partials/_config_form.htm new file mode 100644 index 0000000..91302a4 --- /dev/null +++ b/formwidgets/responsiveimage/partials/_config_form.htm @@ -0,0 +1,47 @@ += e(trans('backend::lang.fileupload.help')) ?>
+ + = $this->getConfigFormWidget()->render(); ?> += e($singleFile->sizeToString()) ?>
+