wrapper script for using @other_checkouts (edit to taste)
[catagits/App-IdiotBox.git] / lib / App / IdiotBox.pm
1 package App::IdiotBox;
2
3 use Web::Simple __PACKAGE__;
4 use Method::Signatures::Simple;
5 use FindBin;
6 use HTML::Zoom;
7
8 {
9   package App::IdiotBox::Announcement;
10
11   sub made_at { shift->{made_at} } 
12   sub bucket { shift->{bucket} } 
13   sub video_count { shift->{video_count} } 
14
15   package App::IdiotBox::Bucket;
16
17   sub slug { shift->{slug} }
18   sub name { shift->{name} }
19   sub video_count {
20     exists $_[0]->{video_count}
21       ? $_[0]->{video_count}
22       : $_[0]->{videos}->count
23   }
24   sub videos { shift->{videos} }
25
26   package App::IdiotBox::Video;
27
28   sub slug { shift->{slug} }
29   sub name { shift->{name} }
30   sub author { shift->{author} }
31   sub details { shift->{details} }
32   sub bucket { shift->{bucket} }
33 }
34
35 default_config(
36   template_dir => 'share/html',
37   store => 'SQLite',
38   db_file => 'var/lib/idiotbox.db',
39 );
40
41 sub BUILD {
42   my $self = shift;
43   my $store;
44   ($store = $self->config->{store}) =~ /^(\w+)$/
45     or die "Store config should be just a name, got ${store} instead";
46   my $store_class = "App::IdiotBox::Store::${store}";
47   eval "require ${store_class}; 1"
48     or die "Couldn't load ${store} store: $@";
49   $store_class->bind($self);
50 }
51   
52 dispatch {
53   sub (/) { $self->show_front_page },
54   subdispatch sub (/*/...) {
55     my $bucket = $self->buckets->get({ slug => $_[1] });
56     [
57       sub (/) {
58         $self->show_bucket($bucket)
59       },
60       sub (/*/) {
61         $self->show_video($bucket->videos->get({ slug => $_[1] }));
62       }
63     ]
64   }
65 };
66
67 method recent_announcements { $self->{recent_announcements} }
68
69 method buckets { $self->{buckets} }
70
71 method show_front_page {
72   my $ann = $self->recent_announcements;
73   $self->html_response(
74     front_page => sub {
75       $_->select('#announcement-list')
76         ->repeat_content($ann->map(sub {
77             my $obj = $_;
78             sub {
79               $_->select('.bucket-name')->replace_content($obj->bucket->name)
80                 ->select('.bucket-link')->set_attribute({
81                     name => 'href', value => $obj->bucket->slug.'/'
82                   })
83                 ->select('.new-videos')->replace_content($obj->video_count)
84                 ->select('.total-videos')->replace_content(
85                     $obj->bucket->video_count
86                   )
87             }
88           }))
89     }
90   );
91 }
92
93 method show_bucket ($bucket) {
94   $self->html_response(bucket => sub {
95     $_->select('.bucket-name')->replace_content($bucket->name)
96       ->select('#video-list')->repeat_content($bucket->videos->map(sub {
97           my $video = $_;
98           sub {
99             $_->select('.video-name')->replace_content($video->name)
100               ->select('.video-author')->replace_content($video->author)
101               ->select('.video-link')->set_attribute(
102                   { name => 'href', value => $video->slug.'/' }
103                 )
104           }
105         }))
106   });
107 }
108
109 method show_video ($video) {
110   $self->html_response(video => sub {
111     $_->select('.video-name')->replace_content($video->name)
112       ->select('.author-name')->replace_content($video->author)
113       ->select('.bucket-link')->set_attribute(
114           { name => 'href', value => '../' }
115         )
116       ->select('.bucket-name')->replace_content($video->bucket->name)
117       ->select('.video-details')->replace_content($video->details)
118   });
119 }
120
121 method html_response ($template_name, $selectors) {
122   my $io = $self->_zoom_for($template_name => $selectors)->to_fh;
123   return [ 200, [ 'Content-Type' => 'text/html' ], $io ]
124 }
125
126 method _template_filename_for ($name) {
127   $self->{config}{template_dir}.'/'.$name.'.html';
128 }
129
130 method _layout_zoom {
131   $self->{layout_zoom} ||= HTML::Zoom->from_file(
132     $self->_template_filename_for('layout')
133   )
134 }
135
136 method _zoom_for ($template_name, $selectors) {
137   ($self->{zoom_for_template}{$template_name} ||= do {
138     my @body;
139     HTML::Zoom->from_file(
140                   $self->_template_filename_for($template_name)
141                 )
142               ->select('#main-content')->collect_content({ into => \@body })
143               ->run;
144     $self->_layout_zoom
145          ->select('#main-content')->replace_content(\@body)
146          ->memoize;
147   })->apply($selectors);
148 }
149
150 1;