Manipulace s obrázky [Nový ImageStorage]
- Martk
- Člen | 661
Mám tu pro Vás nový Image Storage, který byl inspirován touto komponentou. Bohužel byl pro mě postupem času nedostačující, takže jsem vytvořil nový s více možnostmi.
Editoval Antik (16. 1. 2016 21:57)
- Martk
- Člen | 661
Vydána verze 1.1
Co je nového?
- Kompletně předělaný kód.
- Změna velikosti noimage podle neexistujícího obrázku.
- Možnost nastavení noimage v makru (4 parameter).
- Vytvoření vlastního image storage s namespace.
- UploadControl: Obrázek se nahrává jenom při úspěšném odeslání formuláře.
- UploadControl: Obrázek se odstraňuje jenom při úspěšném odeslání formuláře.
- Odstraněno responsive makro, nyní je možnost vytvoření pomocí img makra.
- Při přidání // do absolutního jména obrázku se obrázek vygeneruje s $baseUri místo $basePath
Editoval Antik (8. 2. 2015 17:36)
- Pavel Janda
- Člen | 977
Vytvořil jsem si jednoduchou formFactory, ve které volám
$form->addImageUpload('image', 'Image')
(Nebo addUploadImage,
jsem trošku zmatený, v docu. se objevují obě metody).
Není však třeba ještě nějaká konfigurace?
Při opisování příkladu z Githubu mi Nette kříčí,
viz: Call to undefined method Nette\Application\UI\Form::addUploadImage()
Neměl bych někde napsat něco jako:
<?php
// Bootstrap.php
Nette\Object::extensionMethod(
'Nette\Forms\Container::addImageUpload', function ($form, $name, $label = NULL, $multiple = FALSE) {
return $form[$name] = new WebChemistry\Images\Addons\UploadControl($label, FALSE);
}
);
?>
?
Editoval Beton (9. 2. 2015 13:14)
- David Kudera
- Člen | 455
@echo: myslím, že @Beton se jen ptal, jestli to tam nemá být už od základu. Chyba v jeho zápisu by být neměla, ba dokonce by měl být i lepší. Viz link. Jen teda netuším, od které verze to takto funguje
edit: a taky formulářové prvky se mají přidávat na kontejner formu a ne na form samotný, jinak nebudou v kontejnerech dostupné
Editoval David Kudera (9. 2. 2015 14:06)
- Pavel Janda
- Člen | 977
David Kudera napsal(a):
@echo: myslím, že @Beton se jen ptal, jestli to tam nemá být už od základu. Chyba v jeho zápisu by být neměla, ba dokonce by měl být i lepší. Viz link. Jen teda netuším, od které verze to takto funguje
S tou verzí si také nejsem jist.
Jojo, šlo mi jen o to, jestli se to dělá „někde“ magicky a já píšu něco blbě a jenom ze sebe dělám vola, nebo jestli autorovi utekl kousek dokumentace.
- Pavel Janda
- Člen | 977
Mám další dotaz. :)
Možná spíš report, ale dám to radši sem k diskuzi.
Pokud si nastavím namespace uploadu například na ‚avatars/‘ . uniqid(), tak extension vygeneruje například tuto cestu: avatars/54d8ae0e53b7d/avatar.png. Nicméně, macro rozšíření si s tím jen tak neporadí.
Avšak, pokud upravím cestu (odeberu 54d8ae0e53b7d/) a přesunu adresář „original“ o úroveň výš, začne extension fungovat.
- Martk
- Člen | 661
@Beton Jestli se tam objevují dvě metody, tak se omlouvám, přepsal jsem se. Správně je jenom $form->addImageUpload().
Registraci je možno provést buď jak jsi napsal nebo zavoláním:
WebChemistry\Images\Addons\UploadControl::register();
Ovšem jsem zapomněl upravit registrační metodu, takže v další verzi to bude vypadat (Doporučuji zapsat takhle, než provádět automatickou registraci):
Nette\Object::extensionMethod('Nette\Forms\Container::addImageUpload', function ($form, $name, $label = NULL, $namespace = NULL, $defaultValue = NULL) {
return $form[$name] = new WebChemistry\Images\Addons\UploadControl($label, $namespace, $defaultValue);
});
Edit: Lomítko / by se nemělo používat, protože to je značení pro namespace (Asi jsem to zapomněl ošetřit, alespoň vím co opravit), doporučil bych spíše pomlčku než lomítko.
Editoval Antik (9. 2. 2015 14:42)
- Pavel Janda
- Člen | 977
Tedy ony ty „force vícenásobne“ namespaces nefungují ani ze začátku. Nevygeneruje se unikátní jméno uploadu. (Nevygeneruje se například lnsdzmdj0s_._).
- Pavel Janda
- Člen | 977
Lomítko / by se nemělo používat, protože to je značení pro namespace (Asi jsem to zapomněl ošetřit, alespoň vím co opravit), doporučil bych spíše pomlčku než lomítko.
Rozumím, že používáš / jako delimiter pro namespace, ale co když chci
mít „namespaces“ více zanořené, tedy co když chci sázet obrázky do
adresářové strukturyavatars/substr(uniqid(), -2, 2)
? Tedy
nějak takto:
<?php
images/
avatars/
ab/
bc/
df/
?>
Nebylo by lepší nahradit 87. -X. řádek PropertyAccess něčím takovým:
<?php
preg_match('/(.*)\/([^\/].*)/', $name, $matches);
$this->name = $matches ? end($matches) : $name;
$this->namespace = $matches ? $matches[1] : NULL;
?>
?
A potom ještě poupravit upload, aby se opravil i ten vstupní krok?
- Pavel Janda
- Člen | 977
Antik napsal(a):
Uvažoval jsem u zanořených složkách (Zatím jsem neměl důvod to používat), problém vytvořit to určitě není a nejspíše to udělám.
Bylo by to perfektní. :)
Uvedu jeden z důvodů:
Pokud by web s velmi velkou návštěvností ukládal všechny obrázky do
jednoho adresáře, tak přesto, že filesystemu by to bylo jedno,
programátorovi by po zapnutí FileZilly nebo Commandru (Ne že bych používal
Win) pokaždé spadl počítač. To je jednoduché – občas prostě na to FTP
musím vlézt a něco vzít/smazat/upravit. Nechtěl bych otvírat adresář
(„složku“), kde jsou miliony obrázků. Pokud to rozdistribuujeme
například pomocí posledních pár znaků uniqid(), tak se nám to hezky
rozprostře.
Mimochodem, tvoje extension funguje na zanořené adresáře, pokud uvedu namespace ‚blabla/bubu‘, ale jsou tam zatím ty dva výše zmínění brouci. (Negenerování unikátního prefixu a to druhé, o čem jsem již mluvil a opravil 3 řádkami kódu).
- Pavel Janda
- Člen | 977
Mimochodem, tvoje extension funguje na zanořené adresáře, pokud uvedu namespace ‚blabla/bubu‘, ale jsou tam zatím ty dva výše zmínění brouci. (Negenerování unikátního prefixu a to druhé, o čem jsem již mluvil a opravil 3 řádkami kódu).
Moment, já to blbě pochopil. Ten unikátní prefix se generuje jen pokud již existuje daný soubor, že? Tím je to jasné, funguje to.
- Martk
- Člen | 661
Verze 1.2
- Doplněny podsložky pro namespaces.
- Zabezpečeno odstraňovaní pro podsložky namespaces (Budou odstraněny obrázky patřící pro danou namespace).
- Exception pro obrázek obsahující / ve jméně.
- Upravena registrace UploadControl.
- Opravena chyba pro změnu obrázku s %.
- Možnost zakázání generování změny velikosti obrázku (Hodící hlavně pro generování z Presenteru).
- Dokumentace opravena.
- Další drobné úpravy.
Plány pro další verzi (Budu rád pro každý návrh):
- Multiupload pro UploadControl
Děkuji hlavně uživateli @Beton
Editoval Antik (9. 2. 2015 17:02)
- akadlec
- Člen | 1326
@enumag zatím u mě na disku, na gh snad během týdne. Principielně vychází z uvedené ext. od barbijana a implementuje micropresenter, protože stejně jako u assetsloaderu chci aby obrazky byly na subdoméně kvůli paralelnímu načítání a zároveň bych rád přidal podporu pro jiná než lokální úložiště, např. S3 od amazonu.
- iguana007
- Člen | 970
@Antik
Antik napsal(a):
Plány pro další verzi (Budu rád pro každý návrh):
- Multiupload pro UploadControl
TIP: Pokud by si se rozhodl pro implementaci některého z MultiUploaderu,
který využívá v případě nedostupné podpory HTML5 flashovou instanci,
tak je třeba myslet na to, že flashové uploady nefungují bez hacku pro
aplikace, kde je nutnost přihlášeného uživatele, protože flash pak ruší
session – více o workaroundu zde:
https://github.com/…leUpload.php#L92
+ plno vláken tady na fóru, kde se to již nespočetně krát řešilo…
- flexroad
- Člen | 117
Moc rad bych podekoval za skvele rozsireni!!!
Rad bych se zeptal, jestli a jak jde nektere velikosti obrazku generovat ihned po uploadu.
V handle jsem zkousel
$this->imageStorage->create($filename, "1200x");
ale bohuzel se nic nestane.
Jde mi o to, ze soubory uploaduju ajaxem a pote co se mi soubor upne na server,
potrebuju neco jako:
<img class="img-responsive" src="'+ data.result.files.medium +'" srcset="'+data.result.files.big+' 1280w, '+data.result.files.medium+' 640w, '+data.result.files.small+' 320w" sizes="100%"/>
Soubor se sice ulozi jak ma do „original“, ale pokud vracim cestu na jeho thumbnail, tak z nejakeho duvodu neexistuje, dokud nerefreshnu celou stranku.
Diky za cokoliv!!!
- Martk
- Člen | 661
@flexroad Nyní jsem vytvořil commit (Je potřeba stáhnout dev verzi), který umožňuje co potřebuješ.
UploadControl to zatím neumí, ale píšu si to na seznam.
Příklad:
// Pro upload
$upload = $imageStorage->fromUpload($values->upload, 'namespace');
// Pro string
$upload = $imageStorage->fromContent($string, 'namespace');
// Namespace
$upload->setNamespace('namespace');
// Velikosti
$upload->setSize('100x250');
$upload->setHeight(100);
$upload->setWidth(250);
// Nastavení kvality
$upload->setQuality(80);
// Nastavení flagu
$upload->setFlag('exact');
$upload->setCallback(function (Nette\Utils\Image $image) {
// Zpracování
return $image;
});
// Uložení
$upload->save();
Editoval Antik (10. 2. 2015 17:36)
- akadlec
- Člen | 1326
@Antik tak to bych si dovolil tvrdit že to pomocí traitu neděláš. Ty přece jen upravíš cestu k obrázku tak aby byla relativní a odkazovala tak na umístě v document rootu. Takže obrázek bude vždy na stejné doméně jako celé stránky. U tohoto řešení jsem to měl předěláno tak že jsem si mohl zadefinovat baseUrl takže jsem obrázky mohl poskytovat ze subdomény. A úplně nej je to mít onDemand, a generovat je až na základě požadavku, takže to ještě zrychlí procesování požadavku.
- flexroad
- Člen | 117
@Antik … kdyz pouziju soucasny dev-master, aplikace prestane fungovat.
V aplikaci pouzivam nejake controls, ktere registruju a pouzivam standardnim zpusobem. Po update na dev-master mi aplikace vrati chybu:
Component name must be integer or string, NULL given … pro nasledujici radek
$service = new App\Controls\UserPanelControl($this->container->getService('images.presenter'));
Extenzi mam privesenou takto
extensions:
images: WebChemistry\Images\DI\Extension
Nejaky napad?
@flexroad
- flexroad
- Člen | 117
@akadlec… poradis mi s tim trochu?
V base presenteru registruju komponentu:
/**
* @var \App\Controls\IUserPanelControlFactory
* @inject
*/
public $userPanelFactory;
protected function createComponentUserPanel()
{
$control = $this->userPanelFactory->create();
return $control;
}
Samotna komponenta pak vypada takto:
namespace App\Controls;
use Nette\Application\UI\Form;
class UserPanelControl extends \Nette\Application\UI\Control{
public function render()
{
$this->template->setFile(__DIR__ . '/default.latte');
$this->template->render();
}
}
interface IUserPanelControlFactory
{
/**
* @return UserPanelControl
*/
function create();
}
A v config.neon mam pak:
services:
userPanel:
implement: App\Controls\IUserPanelControlFactory
PS: Hlavne ale nechapu, proc komponenta funguje, dokud neupdatuju ma
webchemistry/images:dev-master.
S webchemistry/images:1.2.1 jede uplne v pohode.
Diky,
@flexroad
Editoval flexroad (11. 2. 2015 11:11)
- akadlec
- Člen | 1326
tebou vypsaná chyba nekoresponduje s uvedeným kódem. Dle chyby to předává jako parametr konstruktoru té komponenty službu z té images ext, ale tvoje komponenta nemá konstruktor.
Nejjednodušší je si to do té komponenty injectovat a nebo pokud to chceš přes kontrusktor tak aby splňoval pak volání parent konstruktoru.
- flexroad
- Člen | 117
@Antik : Diky moc za upravu.
Je super, ze muzu nastavit velikost obrazku uz pri uploadu!!! O to mi ale v puvodnim dotazu neslo. Spise resim, ze kdyz obrazek uploadnu, tak se ulozi POUZE jeho original do slozky „original“.
Potreboval bych ale, aby se zaroven ulozili nahledy v ruznych velikostech (napriklad … 320×, 768×, 1200x)
Pokud tyto velikosti pouziju v makru:
<img class="img-responsive" src="{img $picture->name, '768x'}" srcset="{img $picture->name, '1200x'} 1280w, {img $picture->name, '768x'} 640w, {img $picture->name, '320x'} 320w" sizes="100%"/>
…a refreshnu stranku, pozadovane velikosti se vygeneruji presne jak potrebuji.
Pokud ale stranku nerefreshnu, pozadovane velikosti se nevygeneruji, cehoz bych prave potreboval dosahnout…
Muj handle muze vypadat nejak takto:
public function handleUpload()
{
$file = $_FILES['files'];
$filename = $file['name'][0];
$type = $file['type'][0];
$tmp_name = $file['tmp_name'][0];
$size = (int) $file['size'][0];
$error = empty($file) ? UPLOAD_ERR_NO_FILE : UPLOAD_ERR_OK;
$picture = new Picture();
$picture->name = $filename;
$filename = $this->pictures->save($picture) . "-" . $filename;
$fileUpload = new FileUpload([
'name' => $filename,
'type' => $type,
'tmp_name' => $tmp_name,
'size' => $size,
'error' => $error,
]);
$upload = $this->imageStorage->fromUpload($fileUpload);
$info = $upload->save();
}
pro priklad… ajaxem zavolam vyse uvedeny handle (ktery ulozi pozadovany
soubor do slozky „original“) a hned pote v novem okne otevru:
http://server.com/…age.jpg/320x, tak vidim jen puvodni
velikost obrazku.
Pokud ale pouziju vyse uvedene makro, obrazky se vygeneruji do pozadovanych
velikosti a stejna adresa:
http://server.com/…age.jpg/320x vrati zmensenou velikost,
coz je kyzeny vysledek.
Neda se nejak predgenerovat po nahrani k vyse uvedenemu obrazek s sirkou 320× (do slozky 320x), stejne jako se tomu deje pokud pouziju macro primo v prezenteru(handle)? Nebo nemuze volani uvedene routy primo generovat pozadovane velikosti???
Jeste jednou srdecne DIKY,
@flexroad
- Martk
- Člen | 661
@flexroad Ano uloží se jenom originální velikost obrázku, zmenšená verze se udělá, až když je potřeba (Při prvním požadavku na obrázek). Pokud by jsi chtěl i přesto vytvořit zmenšené verze, tak nyní to lze udělat takhle:
$upload = $this->imageStorage->fromUpload($fileUpload);
$info = $upload->save();
$name = (string) $info; // Absolutní jméno
$this->imageStorage->create($name, '200x300', 'flag')
->setNoImage(NULL) // Nebude se generovat no image
->createLink(); // Generace zmenšeného obrázku a vrácení cesty
$this->imageStorage->create($name, '200x300', 'flag')
->setNoImage(NULL) // Nebude se generovat no image
->createInfoLink(); // Generace zmenšeného a vrácení objektů s WebChemistry\Images\Image\Info
- Pavel Janda
- Člen | 977
Zdravím,
už hodinu nemůžu přijít na to, proč se obrázky neuploadují při ajaxovém požadavku. Pokud do formuláře přidám:
<?php
$form->getElementPrototype()->class = 'ajax';
?>
upload obrázků přestane fungovat, pokud to umažu, formulář chodí.
Nějaké tipy? Díky.
Editoval Beton (11. 2. 2015 16:13)
- Pavel Janda
- Člen | 977
Ano..
Všechny ostatní formulářové prvky se do ajaxového requestu propíšou, jenom ten upload ne.
- Pavel Janda
- Člen | 977
AJAX OFF:
Nette\Utils\ArrayHash #bfc8
image_auto_uid_cut ⇒
„images/82/Screen-Shot-2015–02–09-at-13.17.19.png“ (48)
AJAX ON:
Nette\Utils\ArrayHash #5ab9
image_auto_uid_cut ⇒ NULL
Fakt měním jenom tu třídu formuláře. :D
- Pavel Janda
- Člen | 977
Stejně se chová i nativní ->addUpload()
. Takže se
omlouvám, nesouvisí to s tímto rozšířením.
Nicméně, kdybyste někdo přišel, proč nefunguje při ajax requestu file upload, budu rád.
- flexroad
- Člen | 117
Antik napsal(a):
@flexroad Ano uloží se jenom originální velikost obrázku, zmenšená verze se udělá, až když je potřeba (Při prvním požadavku na obrázek). Pokud by jsi chtěl i přesto vytvořit zmenšené verze, tak nyní to lze udělat takhle:
$upload = $this->imageStorage->fromUpload($fileUpload); $info = $upload->save(); $name = (string) $info; // Absolutní jméno $this->imageStorage->create($name, '200x300', 'flag') ->setNoImage(NULL) // Nebude se generovat no image ->createLink(); // Generace zmenšeného obrázku a vrácení cesty $this->imageStorage->create($name, '200x300', 'flag') ->setNoImage(NULL) // Nebude se generovat no image ->createInfoLink(); // Generace zmenšeného a vrácení objektů s WebChemistry\Images\Image\Info
@Antik: Skvela prace!!! Toto funguje presne podle mych predstav!
- akadlec
- Člen | 1326
@Beton jakou verzi Nette.ajax používáš? Nejnovější nebo starší? Stačí si dohledat něco o problematice posílání souborů v ajaxovém requestu a budeš doma. Starší verze nette.ajax to neuměla, muselo se to hackovat (je to i zde na foru) no a v nové verzi to pak @vojtech.dobes už opravil.
EDIT: ne nebyla to chyba nette.ajax.js, je to prostě vlastnost AJAXových požadavků.
Editoval akadlec (11. 2. 2015 17:09)
- Pavel Janda
- Člen | 977
@akadlec No chyba to byla, nefungovalo to. Ale chápu, nemůžu to dávat za vinu autorovi nette.ajax.js.
- akadlec
- Člen | 1326
no chyba to nebyla, byla to prostě vlastnost. Aby se ajaxovým requestem poslaly i soubory, musel se poslat úplně jinak. Otevři si firebug a podívej se jak vypadá request se starou a novou verzi a uvidíš.
- Jan Mikeš
- Člen | 771
Rad bych nahlasil chybu
Komponenty, ktere vyuzivaji defaultni konstruktor:
public function __construct($parent = NULL, $name = NULL)
{
parent::__construct($parent, $name);
}
Nebo zadny nemaji (dedi od UI\Control) tak prestaly fungovat:
Nette\InvalidArgumentException
Component name must be integer or string, NULL given.
Vygenerovana tovarnicka:
public function create()
{
$service = new App\Components\GA\Control($this->container->getService('images.presenter')); // Zde vyskakuje exception
$service->injectComponentFactories($this->container);
$service->injectTemplateFactory($this->container->getService('157_App_Services_TemplateFactory'));
return $service;
}
Pokud v komponente, kde zadny konstruktor nemam, pridam tento kod:
public function __construct($parent = NULL, $name = NULL)
{
// Zde zamerne nevolam predka!!!
}
Tak vse zda se normalne funguje.
Jedna se o verzi b38acf3d30bfae97d692532d91a0778df4b0ee5d
nette 2.2.7, PHP 5.6.5
S verzi 1.2.1 je vse bez problemu!