]> sipb.mit.edu Git - ikiwiki.git/blob - doc/users/smcv/gallery.mdwn
40e9f627916395ba564f9ccfa3bd9f4e5ece8248
[ikiwiki.git] / doc / users / smcv / gallery.mdwn
1 [[!template id=plugin name=smcvgallery author="[[Simon_McVittie|smcv]]"]]
2 [[!tag type/chrome]]
3
4 This plugin has not yet been written; this page is an experiment in
5 design-by-documentation :-)
6
7 ## Requirements
8
9 This plugin formats a collection of images into a photo gallery,
10 in the same way as many websites: good examples include the
11 PHP application [Gallery](http://gallery.menalto.com/), Flickr,
12 and Facebook's Photos "application".
13
14 The web UI I'm trying to achieve consists of one
15 [HTML page of thumbnails](http://www.pseudorandom.co.uk/2008/2008-03-08-panic-cell-gig/)
16 as an entry point to the gallery, where each thumbnail
17 links to
18 [a "viewer" HTML page](http://www.pseudorandom.co.uk/2008/2008-03-08-panic-cell-gig/img_0068/)
19 with a full size image, next/previous thumbnail links, and [[plugins/comments]].
20
21 (The Summer of Code [[plugins/contrib/gallery]] plugin does the
22 next/previous UI in Javascript using Lightbox, which means that
23 individual photos can't be bookmarked in a meaningful way, and
24 the best it can do as a fallback for non-Javascript browsers
25 is to provide a direct link to the image.)
26
27 Other features that would be good to have:
28
29 * minimizing the number of separate operations needed to make a gallery -
30   editing one source file per gallery is acceptable, editing one
31   source file per photo is not
32
33 * keeping photos outside source code control, for instance in an
34   underlay
35
36 * assigning [[tags|ikiwiki/directive/tag]] to photos, providing a
37   superset of Facebook's "show tagged photos of this person" functionality
38
39 * constructing galleries entirely via the web by uploading attachments
40
41 * inserting grouping (section headings) within a gallery; as in the example
42   linked above, I'd like this to split up the thumbnails but not the
43   next/previous trail
44
45 * rendering an `<object>/<embed>` arrangement to display videos, and possibly
46   thumbnailing them in the same way as totem-video-thumbnailer
47   (my camera can record short videos, so some of my web photo galleries contain
48   them)
49
50 My plan is to have these directives:
51
52 * \[[!gallery]] registers the page it's on as a gallery, and displays all photos
53   that are part of this gallery but not part of a \[[!gallerysection]] (below).
54
55   All images (i.e. `*.png *.jpg *.gif`) that are attachments to the gallery page
56   or its subpages are considered to be part of the gallery.
57
58   Optional arguments:
59
60   * filter="[[ikiwiki/PageSpec]]": only consider images to be part of the
61     gallery if they also match this filter
62
63   * sort="date|filename": order in which to sort the images
64
65 * \[[!gallerysection filter="[[ikiwiki/PageSpec]]"]] displays all photos in the
66   gallery that match the filter
67
68 So, [the gallery I'm using as an example](http://www.pseudorandom.co.uk/2008/2008-03-08-panic-cell-gig/)
69 could look something like this:
70
71     \[[!gallery]]
72     <!-- replaced with one uncategorized photo -->
73
74     # Gamarra
75
76     \[[!gallerysection filter="link(sometag)"]]
77     <!-- all the Gamarra photos -->
78
79     # Smokescreen
80
81     \[[!gallerysection filter="link(someothertag)"]]
82     <!-- all the Smokescreen photos -->
83
84     <!-- ... -->
85
86 ## Implementation ideas
87
88 The photo galleries I have at the moment, like the Panic Cell example above,
89 are made by using an external script to parse XML gallery descriptions (lists
90 of image filenames, with metadata such as titles), and using this to write IkiWiki
91 markup into a directory which is then used as an underlay. This is a hack, but it
92 works. The use of XML is left over from a previous attempt at solving the same
93 problem using Django.
94
95 The next/previous part this plugin overlaps with [[todo/wikitrails]].
96
97 A \[[!galleryimg]] directive to assign metadata to images is probably necessary, so
98 the gallery page can contain something like:
99
100     \[[!galleryimg p1010001.jpg title="..." caption="..." tags="foo"]]
101     \[[!galleryimg p1010002.jpg title="..." caption="..." tags="foo bar"]]
102
103 Making the viewer pages could be rather tricky.
104
105 One possibility is to write out the viewer pages as a side-effect of preprocessing
106 the \[[!gallery]] directive. The proof-of-concept implementation below does this.
107 However, this does mean the viewer pages can't have tags or metadata of their own
108 and can't be matched by [[pagespecs|ikiwiki/pagespec]] or
109 [[wikilinks|ikiwiki/wikilink]]. It might be possible to implement tagging by
110 using \[[!galleryimg]] to assign the metadata to the *images* instead of their
111 viewers, 
112
113 Another is to synthesize source pages for the viewers. This means they can have
114 tags and metadata, but trying to arrange for them to be scanned etc. correctly
115 without needing another refresh run is somewhat terrifying.
116 [[plugins/autoindex]] can safely create source pages because it runs in
117 the refresh hook, but I don't really like the idea of a refresh hook that scans
118 all source pages to see if they contain \[[!gallery]]...
119
120 Making the image be the source page (and generate HTML itself) would be possible,
121 but I wouldn't want to generate a HTML viewer for every `.jpg` on a site, so
122 either the images would have to have a special extension (awkward for uploads from
123 Windows users) or the plugin would have to be able to change whether HTML was
124 generated in some way (not currently possible).
125
126 ## Proof-of-concept
127
128     #!/usr/bin/perl
129     package IkiWiki::Plugin::gallery;
130     
131     use warnings;
132     use strict;
133     use IkiWiki 2.00;
134     
135     sub import {
136         hook(type => "getsetup", id => "gallery",  call => \&getsetup);
137         hook(type => "checkconfig", id => "gallery", call => \&checkconfig);
138         hook(type => "preprocess", id => "gallery",
139                 call => \&preprocess_gallery, scan => 1);
140         hook(type => "preprocess", id => "gallerysection",
141                 call => \&preprocess_gallerysection, scan => 1);
142         hook(type => "preprocess", id => "galleryimg",
143                 call => \&preprocess_galleryimg, scan => 1);
144     }
145     
146     sub getsetup () {
147         return
148                 plugin => {
149                         safe => 1,
150                         rebuild => undef,
151                 },
152     }
153     
154     sub checkconfig () {
155     }
156     
157     # page that is a gallery => array of images
158     my %galleries;
159     # page that is a gallery => array of filters
160     my %sections;
161     # page that is an image => page name of generated "viewer"
162     my %viewers;
163     
164     sub preprocess_gallery {
165         # \[[!gallery filter="!*/cover.jpg"]]
166         my %params=@_;
167     
168         my $subpage = qr/^\Q$params{page}\E\//;
169     
170         my @images;
171     
172         foreach my $page (keys %pagesources) {
173                 # Reject anything not a subpage or attachment of this page
174                 next unless $page =~ $subpage;
175     
176                 # Reject non-images
177                 # FIXME: hard-coded list of extensions
178                 next unless $page =~ /\.(jpg|gif|png|mov)$/;
179     
180                 # Reject according to the filter, if any
181                 next if (exists $params{filter} &&
182                         !pagespec_match($page, $params{filter},
183                                 location => $params{page}));
184     
185                 # OK, we'll have that one
186                 push @images, $page;
187     
188                 my $viewername = $page;
189                 $viewername =~ s/\.[^.]+$//;
190                 $viewers{$page} = $viewername;
191     
192                 my $filename = htmlpage($viewername);
193                 will_render($params{page}, $filename);
194         }
195     
196         $galleries{$params{page}} = \@images;
197     
198         # If we're just scanning, don't bother producing output
199         return unless defined wantarray;
200     
201         # actually render the viewers
202         foreach my $img (@images) {
203                 my $filename = htmlpage($viewers{$img});
204                 debug("rendering image viewer $filename for $img");
205                 writefile($filename, $config{destdir}, "# placeholder");
206         }
207     
208         # display a list of "loose" images (those that are in no section);
209         # this works because we collected the sections' filters during the
210         # scan stage
211     
212         my @loose = @images;
213     
214         foreach my $filter (@{$sections{$params{page}}}) {
215                 my $_;
216                 @loose = grep { !pagespec_match($_, $filter,
217                                 location => $params{page}) } @loose;
218         }
219     
220         my $_;
221         my $ret = "<ul>\n";
222         foreach my $img (@loose) {
223                 $ret .= "<li>";
224                 $ret .= "<a href=\"" . urlto($viewers{$img}, $params{page});
225                 $ret .= "\">$img</a></li>\n"
226         }
227         return "$ret</ul>\n";
228     }
229     
230     sub preprocess_gallerysection {
231         # \[[!gallerysection filter="friday/*"]]
232         my %params=@_;
233     
234         # remember the filter for this section so the "loose images" section
235         # won't include these images
236         push @{$sections{$params{page}}}, $params{filter};
237     
238         # If we're just scanning, don't bother producing output
239         return unless defined wantarray;
240     
241         # this relies on the fact that we ran preprocess_gallery once
242         # already, during the scan stage
243         my @images = @{$galleries{$params{page}}};
244         @images = grep { pagespec_match($_, $params{filter},
245                         location => $params{page}) } @images;
246     
247         my $_;
248         my $ret = "<ul>\n";
249         foreach my $img (@images) {
250                 $ret .= "<li>";
251                 $ret .= htmllink($params{page}, $params{destpage},
252                         $viewers{$img});
253                 $ret .= "</li>";
254         }
255         return "$ret</ul>\n";
256     }
257     
258     sub preprocess_galleryimg {
259         # \[[!galleryimg p1010001.jpg title="" caption="" tags=""]]
260         my $file = $_[0];
261         my %params=@_;
262     
263         return "";
264     }
265     
266     1