Посты / Гибкая загрузка изображений в Laravel 5.3/5.4 Пакет UploadImage.

13.02.2017 22:22
Привет, друзья. Хочу сегодня рассказать о том, как можно легко загружать/получать/удалять свои изображения в Laravel для любого типа контента. Специально для удобства работы с изображениями я написал свой пакет UploadImage. Но обо всем по порядку.

Итак, как вам известно, не один сайт не может обойтись без использования изображений. Это аксиома. Поэтому при разработке веб проекта, программист обязательно столкнется с необходимостью сохранять изображения на диск и получать изображения с диска.

По большому счету, Ларавел дает очень удобный инструментарий для этих целей. Но вот каким образом сделать автоматизированную систему сохранения изображений, в зависимости от разного типа контента, вот это уже большой вопрос. Конечно же в природе есть ряд решений для Ларавел, которые помогают в вопросе сохранения изображений, но по ряду причин они меня не устроили. Поэтому я решил написать свой "велосипед" - пакет UploadImage. Установить его можно через composer. Вот ссылка на пакет: https://github.com/kirill-dan/uploadimage

Я обожаю решения из коробки, не люблю полуфабрикатов, которые нужно потом допиливать напильником. Поэтому я и стараюсь делать максимально коробочное решение.

Для работы с изображениями я выдвинул ряд обязательных требований.

Во первых, я решил в базе данных сохранять только название файла, без пути к этому файлу. Это дает возможность в любой момент перенести ваши, уже сохраненные изображения, куда угодно. Хоть в облако, хоть на другой сервер. Прописать путь к базовому хранилищу можно в едином файле настроек. Такой подход никогда не побьет ваши ссылки, а шаблоны всегда будут знать от куда подгружать изображения.

Следующий момент - это возможность располагать изображения автоматически в разные папки, в зависимости от типа контента. Для этого я применил простой прием. В своем контроллере вы можете вызывать метод сохранения/загрузки изображения, передавая в него, помимо самого файла изображения, еще и название модели. Например,
// Upload and save image.
try {
// Upload and save image.
$input['image'] = UploadImage::upload($file, 'post', $watermark)->getImageName();
} catch (UploadImageException $e) {

return back()->withInput()->withErrors(['image', $e->getMessage()]);
}
В этом случае из файла настроек будет получен путь, куда нужно сохранять изображение. Изображение будет проверено и сохранено в папку 'posts' В конец текстовой строки всегда дописывается символ s. Как это, например, происходит с миграциями и таблицами в базе данных. При чем, произойдет ряд проверок изображения, согласно настройкам. Например, чтобы файл был шириной не менее 500px. В папке 'posts' будет создана папка для оригинального изображения: 'original'. Именно в ней и будет сохранено изображение под случайным именем. И это имя будет возвращено методом одним из значений массива. Как это видно из кода.

Таким образом, изображения для разного контента будут хранится в разных директориях. Также можно в настройках включить функцию миниатюр. И указать какого размера нужно делать превьюшки. В этом случае будут созданы миниатюры и помещены рядом с папкой 'original' в свои каталоги, которые будут названы согласно размеру превьюшки с префиксом 'w', например 'w300'.

Еще можно в настройках включить использование водяных знаков и прописать к ним свои пути. Тогда водяной знак будет автоматически ставиться на оригинальные изображения. Для нанесения водфного знака есть третий параметр $watermark (по умолчанию false). В нем можно указать true, для наклеивания водяного знака на ваше изображение. Если эту переменную вообще не передавать, то на фото не будет наклеен водяной знак.
Я специально изменил систему наклеивания ватермарка, чтобы сделать этот процесс более гибким. Может в одном типе контента нужны ватермарки, а в другом не нужны.

А если вы еще видеоролики храните на сайте, например из Ютуб, то наверняка вы захотите на картинку в тизере наложить изображение видеоплеера:

Для этого вам нужно передать в метод четвертый параметр для наложения видео, который равен true.

// Upload and save image.
try {
// Upload and save image.
$input['image'] = UploadImage::upload($file, 'post', false, $video)->getImageName();
} catch (UploadImageException $e) {

return back()->withInput()->withErrors(['image', $e->getMessage()]);
}
Получить же изображение с диска очень просто. Нужно вызвать метод загрузки и передать в него строку своей модели:
$path = UploadImage::load('post');
Таким образом вы получите путь к папке, где хранится изображение, а само имя файла у вас хранится в базе. Если нужна миниатюра, то передайте в метод вторым параметром ширину миниатюры, просто цифрой.

Если же вам необходимо удалить из базы вашу запись, для которой создано изображение, то это тоже очень легко сделать:
UploadImage::delete($post->image, 'post');
Нужно передать в метод имя файла и название модели. Тогда алгоритм удалит и оригинальное изображение и все миниатюры, которые были для него сделаны.

По мимо всего прочего, очень хочется, чтобы при начальном заполнении формы была возможность увидеть загруженное изображение. Например, на вашем сайте есть возможность пользователям добавлять свои новости. Пользователь начинает заполнять форму добавления новости, выбирает фотографию, она загружается на диск. Пользователю сразу же показывается превьюшка, еще до того, как он закончил заполнение формы.

Для того, чтобы пользователю отобразить превьюшку, достаточно в шаблоне обернуть ваше поле загрузки изображения следующим образом:
<div class="image-preview-block">
<div class="image-preview-image">div>
{!! Form::file('image', ['class' => 'image-preview-input']) !!}
div>
Больше ни о чем думать не нужно все произойдет автоматически.

Но есть один момент, достаточно любопытный. Представим, что по каким-то причинам, пользователь решил не создавать новость и бросит эту затею. Просто закрыл страницу и все. При этом изображение уже было загружено на диск. Что же делать? Теперь это изображение будет похоронено в виде мусора.

Я решил эту проблему достаточно просто. Когда пользователь загружает изображение, то мой функционал его получает от пользователя, сохраняет на диск, потом получает из диска и перекодирует в текстовый формат Base64 и отдает его в виде превьюшки назад пользователю. А оригинальное изображение удаляет с диска. Вот и все. Если пользователь не засабмитит форму, то ничего страшного не произойдет. А если засабмитит, то изображение опять загрузится на диск.

В новом релизе заменен алгоритм. Теперь превью изображения загружается средствами браузера. Единственное что будет получено от сервера, то это только настройка для ширины превьюшки.

Но и это еще не все! Самое вкусное я оставил на закуску. Как нельзя представить современный сайт без изображений, так и нельзя, на сегодняшний день, представить сайт без WYSIWYG редактора. Никто, в здравом уме, не пишет контент в простом текстовом поле. И просто огромное количество пользователей знает лично, какая головная боль вставлять изображения в текст в самом редакторе! Для разных редакторов придуманы десятки всевозможных загрузчиков изображений, как платных, так и бесплатных. Я видел их все, или почти все. И мое личное мнение - это просто КОШМАР!!!

Я всегда раньше мечтал о том, чтобы просто нажать на кнопку вставить изображение прямо в редакторе, выбрал файл на компьютере, и изображение просто вставилось в мой текст, без вообще каких-либо дополнительных телодвижений. И еще мечтал о том, чтобы при удалении статьи, куда вставилось много изображений из редактора, ненужно ничего было бы делать. Все фотографии автоматически бы удалились с диска вместе со статьей. Ах мечты-мечты. Но хватит мечтать. Я это взял, да и сделал!

В технической документации к пакету вы найдете пример, как это можно сделать. Но суть в чем. В настройках в вашем редакторе вы указываете, какая JS функция должна загружать изображения, а какая удалять. После этого, нажав на кнопку загрузки изображения, оно будет загружено в специальную папку, которую можно настроить в файле конфигурации. Если включены водяные знаки, то на изображение он будет нанесен, а изображение будет масштабировано и сжато, согласно настроек в конфиге. 

И когда вы передаете изображение из текстового редактора, то вы можете передать сразу массив изображений. Все они автоматически будут загружены и возвращены в редактор.

Когда вы находитесь в редакторе текста и нажмете на кнопку удаления изображения, то обработчик в финале вызовет метод, который удалит изображение с диска.

Все изображения в вашей статье будут содержать только относительные ссылки, поэтому переезд с сервера на сервер не побьет их. Если же вы захотите удалить статью и все изображения в ней, то вам нужно всего-навсего вызвать следующий метод:
UploadImage::deleteBody($post->body);
Вы просто передаете в метод текст, где содержаться ссылки на ваши фотографии или изображения. Метод распарсит весь текст, найдет только относительные изображения и удалит их с вашего диска. Легко и просто :)

Да, еще момент. Вылетело из головы. В метод сохранения изображений, вы можете передать не только само изображение, но и ссылку на изображение, которое хранится на каком-то сайте. Метод автоматически распознает что вы передаете. Тогда изображение будет закачено и превращено в обычное изображение на вашем диске.

Вот, в принципе, и все. Кому поможет мое решение, я буду очень рад. Лично мне оно теперь очень сильно сохраняет и время и нервы. Если есть предложения по улучшению и исправлению, то пишите в комментариях. До скорой встречи.
5

Комментарии (39):

pakeg писал(а):
Удаляется из original, а миниатюра остается, пробовал UploadImage::delete($user->image, 'user', '250'); , так же не принесло результата. Что я делаю не так ?
Для того, чтобы удалялись и превьюшки, необходимо в файле настроек
config/UploadImage.php
Значение

// Use thumbnails or not.
'thumbnail_status' => false,
изменить на true

При удалении файлов происходит проверка данного значения в конфиге. И если оно истинно, то удаляются и превьюшки. Если ложно, то превьюшки игнорируются при удалении.

Уже и не помню, почему я сделал именно так. Видать была причина ))

Спасибо, решил с версией php, потом не хотело обновлять  spatie/laravel-glide и тд. Решилось) 

Если же вам необходимо удалить из базы вашу запись, для которой создано изображение, то это тоже очень легко сделать:
UploadImage::delete($post->image, 'post');
Нужно передать в метод имя файла и название модели. Тогда алгоритм удалит и оригинальное изображение и все миниатюры, которые были для него сделаны.  

Удаляется  из original, а миниатюра остается, пробовал UploadImage::delete($user->image, 'user', '250'); , так же не принесло результата. Что я делаю не так ?
pakeg писал(а):
Добрый вечер, подскажите пожалуйста как избавиться от данной ошибки?
Здравствуйте. Эта ошибка говорит о том, что ваша версия PHP 5.6 не совместима с пакетом, так как пакету нужна версия PHP 7.x

Вам нужно установить PHP 7 версии. Честно говоря странно, как у вас вообще ларка запустилась. Она тоже требует 7 версию.
Добрый вечер, подскажите пожалуйста как избавиться от данной ошибки?
Мультизагруку сделал так:
в форме:
{!! Form::file('image[]', ['multiple' => 'multiple']) !!}

в контроллере:

foreach ($request->image as $img) {
$images[] = UploadImage::upload($img, 'post', $watermark, $video, $thumbnail)->getImageName();
}


pyro338 писал(а):
это я протупил - не то скопировал. ну в общем после обновления node и npm rebuild node-sass --force - скомпилировалось нормально. спасибо за помощь
Отлично :) Поздравляю.
это я протупил - не то скопировал.
ну в общем после обновления node и npm rebuild node-sass --force - скомпилировалось нормально. спасибо за помощь

Я не совсем понимаю, откуда html разметка взялась?! Что у вас внутри upload_image_preview.js? И что внутри app.js?
обновил ноду. таже самая ошибка


не, ну это перевести то я смог))

ну буду обновляться тогда
pyro338 писал(а):
Make sure you have the latest version of node.js and npm installed
Меня эта надпись настораживает. Перевод таков: Убедитесь, что вы используете последнюю версию ноды и npm. Думаю, что нужно начать с установки последних версий.
там vue.js подключается.

npm-debug.log:

0 info it worked if it ends with ok
1 verbose cli [ '/usr/bin/node', '/usr/bin/npm', 'run', 'dev' ]
2 info using npm@3.10.10
3 info using node@v6.11.4
4 verbose run-script [ 'predev', 'dev', 'postdev' ]
5 info lifecycle @~predev: @
6 silly lifecycle @~predev: no script for predev, continuing
7 info lifecycle @~dev: @
8 verbose lifecycle @~dev: unsafe-perm in lifecycle true
9 verbose lifecycle @~dev: PATH: /usr/lib/node_modules/npm/bin/node-gyp-bin:/home/vagrant/code/blog/node_modules/.bin:/home/vagrant/.composer/vendor/bin:/home/vagrant/bin:/home/vagrant/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
10 verbose lifecycle @~dev: CWD: /home/vagrant/code/blog
11 silly lifecycle @~dev: Args: [ '-c', 'npm run development' ]
12 silly lifecycle @~dev: Returned: code: 1 signal: null
13 info lifecycle @~dev: Failed to exec dev script
14 verbose stack Error: @ dev: `npm run development`
14 verbose stack Exit status 1
14 verbose stack at EventEmitter. (/usr/lib/node_modules/npm/lib/utils/lifecycle.js:255:16)
14 verbose stack at emitTwo (events.js:106:13)
14 verbose stack at EventEmitter.emit (events.js:191:7)
14 verbose stack at ChildProcess. (/usr/lib/node_modules/npm/lib/utils/spawn.js:40:14)
14 verbose stack at emitTwo (events.js:106:13)
14 verbose stack at ChildProcess.emit (events.js:191:7)
14 verbose stack at maybeClose (internal/child_process.js:920:16)
14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:230:5)
15 verbose pkgid @
16 verbose cwd /home/vagrant/code/blog
17 error Linux 4.4.0-96-generic
18 error argv "/usr/bin/node" "/usr/bin/npm" "run" "dev"
19 error node v6.11.4
20 error npm v3.10.10
21 error code ELIFECYCLE
22 error @ dev: `npm run development`
22 error Exit status 1
23 error Failed at the @ dev script 'npm run development'.
23 error Make sure you have the latest version of node.js and npm installed.
23 error If you do, this is most likely a problem with the package,
23 error not with npm itself.
23 error Tell the author that this fails on your system:
23 error npm run development
23 error You can get information on how to open an issue for this project with:
23 error npm bugs
23 error Or if that isn't available, you can get their info via:
23 error npm owner ls
23 error There is likely additional logging output above.
24 verbose exit [ 1, true ]
Еще вопрос, а что у вас прописано здесь resources/assets/js/app.js ? Попробуйте убрать из сборки этот файл.
Кстати, когда нода сыпется с ошибкой, то она должна создать лог в корне проекта. Неплохо было бы увидеть его, что ему именно не нравится. Скорее всего он ругается на какой-то старый пакет.
pyro338 писал(а):
ясно. спасибо за помощь
Не за что. Если не получится, то пишите. Попробую помочь.
pyro338 писал(а):
то есть ошибка из-за несвежих node.js и npm? в интернетах пишут что восьмая нода нестабильная. стоит ли?
У меня стоит, в том числе и на проде. Все пока нормально. А для Лары 5.3 у меня стояла седьмая версия. Но там были elixir и gulp. Тоже без проблем все собирали. Для теста попробуйте из конфига убрать resources/assets/js/upload_image_preview.js, а вместо него создайте свой маленький скриптик, и пропишите его в конфиг. Просто для проверки. Я помню, что ноду тоже с каким-то гемором собирал. При инсталляции ей не хватало каких-то пакетов, некоторые пакеты просило обновить. После танцев с бубном и гугления у меня все стало собирать как надо.
то есть ошибка из-за несвежих node.js и npm?

в интернетах пишут что восьмая нода нестабильная. стоит ли?
Для Ubuntu попробуйте ноду так поставить:
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
Сейчас такие версии используют:
node: v8.3.0
npm: 5.4.2

Конфиг правильный. Для прода еще нужно прописать версионность в конце файла:
if (mix.inProduction()) {
mix.version();
}
Файл тут существует resources/assets/js/upload_image_preview.js ?

Если все ок, то обновляйте ноду и npm. Ставьте свежие версии (скачайте с их сайта deb пакет). Для теста собирайте командой:
npm run dev

npm 3.10.10 
node v6.11.4

let mix = require('laravel-mix');

/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/

mix.sass('resources/assets/sass/app.scss', 'public/css/app.css').version();

mix.js(['resources/assets/js/app.js', 'resources/assets/js/upload_image_preview.js'],
'public/js/all.js').version();
Да, описка, только что поправил. Какая версия ноды и npm? Если выполнить команду npm run dev то тоже выдает ошибку? Киньте конфиг по сборке js в mix.
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">

так прописано
laravel 5.5, webpack

ну я делаю пошагово как на гитхабе написано. дохожу до строчки

  After execute command in terminal (for production):

npn run production 

и потом такая ошибка как я написал (и да. наверное здесь опечатка: не npnm а npm).  
ps в ларавеле новичок, так что может я туплю где
Кстати, а в главном шаблоне токен прописан?
{{-- CSRF Token --}}
<meta name="csrf-token" content="{!! csrf_token() !!}">
pyro338 писал(а):
не устанавливается. npm пишет 95% emitting ERROR Failed to compile with 1 errors 12:31:21 PM error in ./resources/assets/js/upload_image_preview.js Syntax Error: Unexpected token (1:0) > 1 | | ^ 2 | ну и так далее
А можете рассказать подробнее. Какая версия Ларавел, что используется, elixir или mix. Чем собираете скрипты, gulp или webpack. Что именно не устанавливается? Сам пакет установился? Настройки прописали?
не устанавливается. npm пишет


95% emitting

ERROR Failed to compile with 1 errors 12:31:21 PM

error in ./resources/assets/js/upload_image_preview.js

Syntax Error: Unexpected token (1:0)

> 1 | <!DOCTYPE html>
| ^ <html lang="en">
2 | <head>


ну и так далее
artem писал(а):
А нельзя перед началом чтения поинтересоваться, чем вас не устроили решения на подобии dropzonejs ? Просто совсем не пойму нужно читать мне или нет статью.
Здравствуйте. Я в одном пакете собрал готовое решение и для загрузки изображений с вюсов и для загрузки через WYSIWYG. Один пакет, единое решение. Делал для себя, именно как мне удобно. Уже потом сделал опен сорс. Что касается любых других решений, то я не претендую на истину последней инстанции. Они тоже хороши. Но я не хочу выходить из зоны своего комфорта в угоду чужим решениям. Если мне нужно усложнить решение, то мне, либо ишью в гит постить, либо самому допиливать чужое решение. А значит, это будет уже не один мобильный пакет, а размазывание кода по проекту. Поэтому мне проще было сделать решение под себя, где я хорошо знаю код и могу быстро его доработать, в случае чего.
А нельзя перед началом чтения поинтересоваться, чем вас не устроили решения на подобии dropzonejs ? Просто совсем не пойму нужно читать мне или нет статью.
an_zim писал(а):
Пардоньте... {!! Form::open([ 'url' => 'uploads', 'class' => 'ui form', 'enctype' => 'multipart/form-data'] ) !!} Файл загрузился. Остается вопрос мультизагрузки, да, возможна ли она? И как быть если текущий способ именования файлов нужно изменить? Например необходимо дать им более понятные имена, чтобы использовать далее не только на сайте? Можно ли как-то использовать собственный generateNewName() ? Может как-то через настройки, мол new_name_generator => 'default' или new_name_generator => 'superpuperSvoyNameGenerator'.
В форме должна быть включена поддержка файлов, например:
{!! Form::open(['route' => 'post.store', 'files' => TRUE]) !!}
Старайтесь использовать не пути, а имена роутов. Это лучше тем, что если вам нужно будет поменять путь, вы поменяете только у роута, а везде он подхватится уже автоматом.

Мультизагрузку через
// Upload and save image.
try {
// Upload and save image.
$input['image'] = UploadImage::upload($file, 'post', $watermark)->getImageName();
} catch (UploadImageException $e) {

return back()->withInput()->withErrors(['image', $e->getMessage()]);
}
я не делал. Я руководствовался другой логикой и принципом. Одна фотка для тизера, как ознакомительная, и все. А мультизагрузка через редактор изображений WYSIWYG. Например, на вход контроллера
// Save image from WYSIWYG editor.
ajax/uploader/upload
можно подать массив с изображениями из JS. Все изображения будут загружены и будет возвращен массив с уже загруженными изображениями. Здесь есть пример: https://cleverman.org/post/38

Своего генератора в настройках нет, но могу попробовать сделать.

Почему я фотки переименовываю. Во первых, чтобы вам не загрузили какой-то вредоносный код, который маскируется под изображение (есть горький опыт). Потом, чтобы точно не было совпадений по имени. Иначе будет перезаписан файл изображения.


an_zim wrote:
Что я не так сделал?
Пардоньте...
{!! Form::open([ 'url' => 'uploads', 'class' => 'ui form', 'enctype' => 'multipart/form-data'] ) !!}
Файл загрузился.

Остается вопрос мультизагрузки, да, возможна ли она?

И как быть если текущий способ именования файлов нужно изменить? Например необходимо дать им более понятные имена, чтобы использовать далее не только на сайте?
Можно ли как-то использовать собственный generateNewName() ?
Может как-то через настройки, мол new_name_generator => 'default' или  new_name_generator => 'superpuperSvoyNameGenerator'.
Route::resource('uploads', 'UploadController');

***  UploadController ***
<?php
namespace App\Http\Controllers;

use Dan\UploadImage\Exceptions\UploadImageException;
use Illuminate\Http\Request;
use UploadImage;

class UploadController extends Controller
{
public function create() { return view('uploads.create'); }

public function store(Request $request)
{
$file = $request->file('image');
$video = false;
$watermark = false;

// Upload and save image.
try {
// Upload and save image.
$input['image'] = UploadImage::upload($file, 'post', $watermark, $video)->getImageName();
} catch (UploadImageException $e) {

return back()->withInput()->withErrors(['image', $e->getMessage()]);
}
}
}
***  /UploadController *** 
 
***  uploads.create ***
@extends('app')

@section('content')
<h2 class="text-center">Загрузить</h2>

{!! Form::open([ 'url' => 'uploads', 'class' => 'ui form'] ) !!}
<div class="image-preview-block">
<div class="image-preview-image"></div>
{!! Form::file('image', ['class' => 'image-preview-input']) !!}
</div>
{!! Form::submit('Загрузить', ['class' => 'fluid ui blue button']) !!}
{!! Form::close() !!}
@include ('errors.list')
@stop

***  /uploads.create ***

В итоге Can't upload image!
dd( $file ) говорит null

Что я не так сделал? 
Очень уж хочется, чтобы это вертолет у меня полетел ))

EGORR писал(а):
Я как-то подумал:"а почему бы не сохранять изображения в серверный /tmp?" Да и сделал. Когда юзер грузит фото на страницу, они проходят необходимую обработку(resize, ещё что-то если нужно) кладутся как я уже сказал в /tmp Юзеру показываем Base64. Имя папки с изображениями в /tmp кладём в input type=hidden По сабмиту, в контроллере забираем из тайника фото и перекладываем куда нужно. В трёх проектах работает нормально.
Я в итоге переделал именно этот кусок. Я делаю показ превью методом браузера, через JS, без непосредственной загрузки фото на сервер (мне подсказали это на сайте ларавел.ру). Если пользователь отказался от сабмита формы, то и не страшно. Ничего и не было на сервер загружено. И работает быстрее без необходимости дергать сервер.

П.С. Чтобы сделать цитату, нужно выделить кусок теста в комментарии и нажать на кнопку. При наведении мышки выводится подсказка.
 <цитата>Когда пользователь загружает изображение, то мой функционал его получает от пользователя, сохраняет на диск, потом получает из диска и перекодирует в текстовый формат Base64 и отдает его в виде превьюшки назад пользователю.</цитата>
Я как-то подумал:"а почему бы не сохранять изображения в серверный /tmp?" Да и сделал. Когда юзер грузит фото на страницу, они проходят необходимую обработку(resize, ещё что-то если нужно) кладутся как я уже сказал в /tmp Юзеру показываем Base64. Имя папки с изображениями в /tmp кладём в input type=hidden По сабмиту, в контроллере забираем из тайника фото и перекладываем куда нужно. 
В трёх проектах работает нормально.
Пакет полностью переделал. Баги пофиксил, заменил работу с массивами на объекты. Текущая версия 1.0.4
an_zim писал(а):
Пишет Illegal string offset 'name'.
Нашел причину. Сегодня постараюсь пофиксить.
an_zim писал(а):
У меня, к сожалению, «этот вертолет не полетел» (( Пишет Illegal string offset 'name'. Очень бы хотелось рабочее демо с исходниками. Лучше конечно с примером мультизагрузки. И да, я не программист, скорее интересующийся. Еще, превью показывалось полноразмерным, вылечилось добавлением .image-preview-image { img { width: 100%; } }
Спасибо за коммент. Сегодня попробую поправить. А подскажите, кода ошибка появилась, на каком этапе?
У меня, к сожалению, «этот вертолет не полетел» ((
Пишет  Illegal string offset 'name'.
Очень бы хотелось рабочее демо с исходниками. Лучше конечно с примером мультизагрузки.
И да, я не программист, скорее интересующийся.

Еще, превью показывалось полноразмерным, вылечилось добавлением .image-preview-image { img { width: 100%; } }