Skip to content
Browse files

Initial commit of flicks-2012 snippet.

  • Loading branch information...
1 parent 277a334 commit 12ffb4315b7c70d9364c825d1383078e4d5df358 @Osmose Osmose committed
View
7 video-snippets/flicks-2012-video/.gitignore
@@ -0,0 +1,7 @@
+build/*
+!build/.gitignore
+*~
+*.pyc
+.snippetconfig
+*.pyc
+build.html
View
7 video-snippets/flicks-2012-video/LICENSE
@@ -0,0 +1,7 @@
+Copyright (c) Michael Kelly 2012
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
65 video-snippets/flicks-2012-video/README.md
@@ -0,0 +1,65 @@
+# about:home Snippet Template
+
+A template project layout to help jump-start Firefox about:home snippet
+development.
+
+## Prerequisites
+
+* Tested on OS X Snow Leopard
+* Python 2.7 (may work with older versions)
+* (Recommended) virtualenv + pip
+* (Recommended) A local install of [home-snippets-server][]
+
+[home-snippets-server]: https://github.com/lmorchard/home-snippets-server
+
+## How-to
+
+1. `git clone git://github.com/Osmose/snippet-dev-template.git`
+2. `cd snippet-dev-template`
+ * (Optional) If you don't have Pystache or Fabric installed, or are running
+ inside of a virtualenv, run `pip install -r requirements.txt`
+3. Create your snippet.
+ * HTML goes in `content.html`
+ * CSS in `styles.css`
+ * Javascript in `script.js`
+ * Images in `images/` (See 'Using Images' below)
+4. `fab build`
+ * Compiled snippet will be in `build.html`
+
+## Auto-update Database
+
+`fabfile.py` can create and update a snippet in your database directly.
+
+1. `fab db_setup`
+2. Enter an absolute path to the sqlite3 database file for your
+ home-snippets-server.
+3. Enter a name for the snippet. This will be used both for the snippet name
+ and as the Product Name in the client match rule for the snippet to make it
+ easier to preview the snippet.
+3. `fab push`
+
+**Note:** You can set the environment variable `HOMESNIPPETS_DATABASE` to the
+absolute path to your database to bypass entering the path in every time you
+create a new snippet and hook up your database.
+
+## Auto-Build and Push
+
+`fabfile.py` can also monitor your project for changes and automatically build your snippet and even push to the database without user input.
+
+`fab monitor` will monitor for changes and execute a `build` and `push` when it detects changes.
+
+## Using Images
+
+Any images placed in the `images/` directory can be automatically embedded using data URIs into your content.html file:
+
+```
+content.html:
+
+<img src="{{#base64img}}some_image.png{{/base64img}}" />
+```
+
+The result of the code above embeds `images/some_image.png` into an `<img>` tag in the snippet using base64 data URI encoding.
+
+## Future Improvement Ideas
+
+* Replace raw queries with a better alternative
View
21 video-snippets/flicks-2012-video/content.html
@@ -0,0 +1,21 @@
+<div id="flicks-2012-snippet" class="snippet">
+ <style>{{& css}}</style>
+
+ <video class="hidden" preload="none">
+ <source src="http://videos.mozilla.org/serv/flux/snippets/flicks_snippet_falling_480p.webm" type="video/webm"/>
+ </video>
+
+ <div class="message">
+ <img class="icon" src="{{#base64img}}flicks.png{{/base64img}}" />
+ <p>
+ Firefox Flicks wants Firefox fans and filmmakers to create and submit your
+ short videos now! <span class="show-video">Watch the Flicks video</span>
+ </p>
+ </div>
+
+ <script>
+ //<![CDATA[
+{{& js}}
+ //]]>
+ </script>
+</div>
View
240 video-snippets/flicks-2012-video/fabfile.py
@@ -0,0 +1,240 @@
+# TODO: Figure out a better way to edit the DB
+
+import base64
+import ConfigParser
+import os
+import time
+from os.path import isfile
+from subprocess import CalledProcessError, check_output
+
+import pystache
+import sqlite3
+from fabric.decorators import task
+from watchdog.events import PatternMatchingEventHandler
+from watchdog.observers import Observer
+
+
+BUILD_OUT_FILE = 'build.html'
+IMAGES_DIR = 'images'
+SNIPPET_CONTENT_FILE = 'content.html'
+JS_FILE = 'script.js'
+CSS_FILE = 'styles.css'
+LESS_FILE = 'styles.less'
+
+LESS_BIN = os.environ.get('LESS_BIN', 'lessc')
+
+IGNORE_PATTERNS = (BUILD_OUT_FILE, '.gitignore', 'fabfile.py', 'README.md',
+ 'requirements.txt', 'LICENSE')
+IGNORE_PATTERNS = ['*{0}'.format(filename) for filename in IGNORE_PATTERNS]
+
+config = ConfigParser.ConfigParser()
+config.read('.snippetconfig')
+database_present = config.has_section('Database')
+
+
+class MonitorBuildPushEventHandler(PatternMatchingEventHandler):
+ def on_any_event(self, event):
+ """Runs the build_push_all task when any filesystem event occurs."""
+ print "Files have changed, pushing..."
+ build()
+ push()
+
+
+@task
+def monitor():
+ """
+ Monitors the current directory for changes and pushes when they happen.
+ """
+ observer = Observer()
+ handler = MonitorBuildPushEventHandler(ignore_patterns=IGNORE_PATTERNS)
+ observer.schedule(handler, '.', recursive=True)
+ print "Monitoring for changes..."
+ observer.start()
+ try:
+ while True:
+ time.sleep(1)
+ except KeyboardInterrupt:
+ observer.stop()
+ observer.join()
+
+
+@task
+def push():
+ """
+ Checks for database configuration info and pushes the last built snippet
+ to the specified database
+ """
+
+ if (database_present):
+ with open(BUILD_OUT_FILE, 'r') as snippet:
+ conn = sqlite3.connect(config.get('Database', 'db_path'))
+ conn.execute("""
+ UPDATE homesnippets_snippet
+ SET body=?
+ WHERE id=?
+ """, (snippet.read(), config.get('Database', 'snippet_id')))
+ conn.commit()
+ conn.close()
+
+
+@task
+def db_setup():
+ """Setup database details and create snippet to push updates to."""
+
+ if not config.has_section('Database'):
+ config.add_section('Database')
+
+ if 'HOMESNIPPETS_DATABASE' in os.environ:
+ print ('Using path from HOMESNIPPETS_DATABASE: {0}'
+ .format(os.environ['HOMESNIPPETS_DATABASE']))
+ db_path = os.environ['HOMESNIPPETS_DATABASE']
+ else:
+ db_path = raw_input('Enter the absolute path to the sqlite '
+ 'database file: ')
+
+ while not _test_sqlite3_db(db_path):
+ db_path = raw_input('Error validating database. Enter absolute path'
+ ' to database file (blank to quit setup): ')
+ if not db_path:
+ return
+
+ config.set('Database', 'db_path', db_path)
+
+ # Get name to use for snippet.
+ name = raw_input('Please enter the product name you will use to view this '
+ 'snippet (i.e. flicks_video):')
+
+ # Set up snippet to push to
+ conn = sqlite3.connect(db_path)
+
+ # Create client rule
+ conn.execute("""
+ INSERT INTO homesnippets_clientmatchrule (description, name, exclude,
+ created, modified)
+ VALUES (?, ?, 0, datetime('now'), datetime('now'))
+ """, ['Name: {0}'.format(name), name])
+ client_rule_id = _get_last_insert_rowid(conn)
+
+ # Create snippet
+ conn.execute("""
+ INSERT INTO homesnippets_snippet (name, body, disabled, preview,
+ created, modified)
+ VALUES (?, '', 0, 0, datetime('now'), datetime('now'))
+ """, (name,))
+ snippet_id = _get_last_insert_rowid(conn)
+
+ # Associate snippet with rule
+ conn.execute("""
+ INSERT INTO homesnippets_snippet_client_match_rules
+ (snippet_id, clientmatchrule_id)
+ VALUES (?, ?)
+ """, (snippet_id, client_rule_id))
+
+ conn.commit()
+ conn.close()
+
+ config.set('Database', 'snippet_id', snippet_id)
+ config.set('Database', 'client_rule_id', client_rule_id)
+
+ # TODO: Handle failure better
+ with open('.snippetconfig', 'w') as f:
+ config.write(f)
+ print ''
+
+
+def _get_last_insert_rowid(conn):
+ cursor = conn.execute('SELECT last_insert_rowid()')
+ result = cursor.fetchone()
+ if result is not None:
+ return result[0]
+ else:
+ return None
+
+
+def _test_sqlite3_db(db_path):
+ """Very basic test for validity of database."""
+
+ # Check for file existance and if it's a sqlite3 db
+ if isfile(db_path):
+ try:
+ conn = sqlite3.connect(db_path)
+
+ # TODO: If we really care, do a more thorough check
+ # If it has the homesnippets_snippet table we're content
+ tables = [r[0] for r in conn.execute("""
+ SELECT name FROM sqlite_master
+ WHERE type='table'
+ """).fetchall()]
+ conn.close()
+
+ if 'homesnippets_snippet' in tables:
+ return True
+ except sqlite3.DatabaseError:
+ pass
+
+ return False
+
+
+@task
+def build():
+ """Builds the snippet."""
+ # Use the LESS file over the CSS file if it exists.
+ css = CSS_FILE
+ if os.path.isfile(LESS_FILE):
+ css = LESS_FILE
+
+ template = SnippetView({
+ 'css': css,
+ 'js': JS_FILE,
+ 'content': SNIPPET_CONTENT_FILE
+ })
+
+ with open(BUILD_OUT_FILE, 'w') as output:
+ output.write(template.render())
+
+
+class SnippetView(pystache.View):
+ template_file = SNIPPET_CONTENT_FILE
+
+ def __init__(self, filenames):
+ super(SnippetView, self).__init__()
+ self._filenames = filenames
+
+ def __getattr__(self, item):
+ try:
+ return self._loadfile(item)
+ except KeyError:
+ raise AttributeError
+
+ def _loadfile(self, key):
+ filename = self._filenames[key]
+
+ # Handle files that need preprocessing.
+ if filename.endswith('less'):
+ args = [LESS_BIN, '-x', filename]
+ try:
+ return check_output(args)
+ except CalledProcessError, e:
+ print 'Error compiling %s with command `%s`:' % (filename, args)
+ print e.output
+ print 'File will be ignored.'
+ else:
+ with open(filename, 'r') as f:
+ return f.read()
+
+ def base64img(self, text=None):
+ return self._base64img
+
+ def _base64img(self, text=''):
+ filename = text.strip()
+ if filename.endswith('png'):
+ mimetype = 'image/png'
+ elif filename.endswith(('jpg', 'jpeg')):
+ mimetype = 'image/jpeg'
+ else:
+ return ''
+
+ with open(IMAGES_DIR + '/' + filename, 'r') as f:
+ data = base64.encodestring(f.read())
+
+ return 'data:%s;base64,%s' % (mimetype, data)
View
0 video-snippets/flicks-2012-video/images/.gitkeep
No changes.
View
BIN video-snippets/flicks-2012-video/images/flicks.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
3 video-snippets/flicks-2012-video/requirements.txt
@@ -0,0 +1,3 @@
+Fabric>=1.1.1
+pystache>=0.3.1
+watchdog
View
57 video-snippets/flicks-2012-video/script.js
@@ -0,0 +1,57 @@
+(function() {
+ 'use strict';
+
+ var snippet = document.querySelector('#flicks-2012-snippet');
+ var video = snippet.querySelector('video');
+ var showVideoLink = snippet.querySelector('.show-video');
+
+ var shareControls = new ShareVideoControls(video, {
+ overlayClass: 'hidden'
+ });
+
+ // Bind click event only after snippet has loaded.
+ snippet.addEventListener('show_snippet', function() {
+ // Trigger animation when show video link is clicked.
+ showVideoLink.addEventListener('click', function(e) {
+ var newClass = (shareControls.videoOverlay.className
+ .replace('hidden', ''));
+ shareControls.videoOverlay.className = newClass;
+ e.stopPropagation();
+ }, false);
+ }, false);
+
+ function ShareVideoControls(video, options) {
+ this.video = video;
+
+ // Building HTML via strings; ugly, but good enough!
+ var controls = [];
+ controls.push('<div class="controls">');
+ controls.push('<button class="play"></button>');
+ controls.push('<div class="seek-bar"><i class="seek-thumb"></i></div>');
+ controls.push('<button class="volume"></button>');
+ controls.push('<button class="share"></button>');
+ controls.push('<button class="fullscreen"></button>');
+ controls.push('</div>');
+
+ var videoOverlayContents = [];
+ videoOverlayContents.push('<i class="close"></i>');
+ videoOverlayContents.push(controls.join(''));
+
+ this.videoOverlay = document.createElement('div');
+ this.videoOverlay.className = 'video-overlay';
+ this.videoOverlay.innerHTML = videoOverlayContents.join('');
+
+ if (options.overlayClass) {
+ this.videoOverlay.className += ' ' + options.overlayClass;
+ }
+
+ // Replace the video with the overlay and insert it into the overlay.
+ video.parentNode.insertBefore(this.videoOverlay, video);
+ video.parentNode.removeChild(video);
+ this.videoOverlay.appendChild(video);
+ }
+
+ ShareVideoControls.prototype = {
+
+ };
+})();
View
56 video-snippets/flicks-2012-video/styles.less
@@ -0,0 +1,56 @@
+#flicks-2012-snippet {
+ position: relative;
+
+ video {
+ box-shadow: 0 1px 0 rgba(255,255,255,.8) inset,
+ 0 -2px 0 rgba(0,0,0,.1) inset,
+ 0 0 10px rgba(255,255,255,.5) inset,
+ 0 0 0 1px rgba(0,0,0,.1),
+ 0 2px 4px rgba(0,0,0,.2);
+ margin: 8px auto;
+ opacity: 1;
+ width: 100%;
+ }
+
+ .message {
+ .icon {
+ float: left;
+ padding-left: 28px;
+ position: relative;
+ right: auto;
+ top: auto;
+ }
+
+ p {
+ display: block;
+ padding: 0;
+ margin-left: 80px;
+ }
+ }
+
+ .show-video {
+ color: -moz-nativehyperlinktext;
+ cursor: pointer;
+ }
+
+ .video-overlay {
+ position: relative;
+
+ &.hidden {
+ height: 1px;
+ margin: 0;
+ -moz-transition: 400ms ease-in-out;
+ opacity: 0;
+ width: 1px;
+ }
+
+ .controls {
+ background: rgba(60, 60, 60, 0.5);
+ bottom: 0;
+ height: 25px;
+ left: 0;
+ position: absolute;
+ width: 100%;
+ }
+ }
+}

0 comments on commit 12ffb43

Please sign in to comment.
Something went wrong with that request. Please try again.