App-SlideServer

 view release on metacpan or  search on metacpan

lib/App/SlideServer.pm  view on Meta::CPAN

			push @slides, $node;
		}
		elsif ($self->_node_splits_slide($node, $tag)) {
			$cur_slide= undef;
		}
		else {
			# Ignore whitespace nodes when not in a current slide
			next if !defined $cur_slide && $node->type eq 'text' && $node->text !~ /\S/;
			push @slides, ($cur_slide= Mojo::DOM->new_tag('div', class => 'slide')->[0])
				if !defined $cur_slide
					|| $self->_node_starts_slide($node, $tag);
			# Add "auto-step" to any <UL> tags
			if (($tag eq 'ul' || $tag eq 'ol') && !$node->{class}) {
				$node->{class}= "auto-step";
				# Now apply auto-step recursively to <ol> and <ul>
				$node->find('ol')->map(sub{ $_->{class}= $_->{class}? "$_->{class} auto-step" : 'auto-step' });
				$node->find('ul')->map(sub{ $_->{class}= $_->{class}? "$_->{class} auto-step" : 'auto-step' });
			}
			$cur_slide->append_content($node);
		}
	}
	
	# Re-add things that belong in <head>
	($dom->at('html') || $dom)->prepend_content('<head></head>')
		unless $dom->at('head');
	for my $el (@move_to_head) {
		if ($el->tag eq 'head') {
			$el->child_nodes->each(sub{ $dom->at('head')->append_content($_) });
		} else {
			$dom->at('head')->append_content($el);
		}
	}
	return ($dom, @slides);
}	


sub merge_page_assets($self, $srcdom, %opts) {
	my $page= Mojo::DOM->new($self->share_dir->child('page_template.html')->slurp);
	if (my $srchead= $srcdom->at('head')) {
		my $pagehead= $page->at('head');
		# Prevent conflicting tags (TODO, more...)
		if (my $title= $srchead->at('title')) {
			$pagehead->at('title')->remove;
		}
		$pagehead->append_content($_) for $srchead->@*;
	}
	if (my $srcbody= $srcdom->at('body')) {
		if ($srcbody->child_nodes->size) {
			$page->at('body')->replace($srcbody);
			if (!$page->at('body div.slides')) {
				$page->at('body')->append_content('<div class="slides"></div>');
			}
		} else {
			$page->at('body')->%*= $srcbody->%*;
		}
	}
	return $page;
}


sub update_published_state($self, @new_attrs) {
	$self->published_state->%* = ( $self->published_state->%*, @new_attrs );
	$_->send({ json => { state => $self->published_state } })
		for values $self->viewers->%*;
}


sub startup($self) {
	$self->build_slides;
	$self->presenter_key;
	$self->static->paths([ $self->serve_dir->child('public'), $self->share_dir->child('public') ]);
	$self->routes->get('/' => sub($c){ $c->app->serve_page($c) });
	$self->routes->websocket('/slidelink.io' => sub($c){ $c->app->init_slidelink($c) });
}

sub serve_page($self, $c, %opts) {
	if (!defined $self->page_dom || $self->cache_token) {
		eval { $self->build_slides; 1 }
			or $self->log->error($@);
	}
	# Merge the empty page with all currently-visible slides,
	# which saves the client from needing a second request to fetch them.
	# TODO: implement slide-by-slide loading
	my $slide_max= $#{$self->slides_dom}; # $self->published_state->{slide_max} || 0;
	my @slides= $self->slides_dom->@[0..$slide_max];
	my $combined= Mojo::DOM->new($self->page_dom);
	$combined->at('div.slides')->append_content(join '', @slides);

	# If this is for the presenter, set the config variable for that
	if ($opts{presenter} || defined $c->req->param('presenter')) {
		$combined->at('head')->append_content(
			'<script>window.slides.config.mode="presenter";</script>'."\n"
		);
	}

	$c->render(text => ''.$combined);
}

sub init_slidelink($self, $c) {
	my $id= $c->req->request_id;
	$self->viewers->{$id}= $c;
	my $mode= $c->req->param('mode');
	my $key= $c->req->param('key');
	my %roles= ( follow => 1 );
	if ($mode eq 'presenter') {
		if (($key||'') eq $self->presenter_key) {
			$roles{lead}= 1;
			$roles{navigate}= 1;
			$self->update_published_state(viewer_count => scalar keys $self->viewers->%*);
		}
		elsif (defined $key) {
			$c->send({ json => { key_incorrect => 1 } });
		}
	}
	$c->stash('roles', join ',', keys %roles);
	$self->log->info(sprintf "%s (%s) connected as %s", $id, $c->tx->remote_address, $c->stash('roles'));
	$c->send({ json => { roles => [ keys %roles ] } });
	
	$c->on(json => sub($c, $msg, @) { $c->app->on_viewer_message($c, $msg) });
	$c->on(finish => sub($c, @) { $c->app->on_viewer_disconnect($c) });
	$c->inactivity_timeout(3600);



( run in 0.879 second using v1.01-cache-2.11-cpan-df04353d9ac )