32 Commits

Author SHA1 Message Date
Sam Lu
053570e55d Add dismiss Searchbar with Esc key 2018-08-06 11:53:23 -05:00
Sam Lu
ab98261889 Add ability to dismiss search bar by clicking outside search field 2018-08-06 11:23:09 -05:00
Sam Lu
97bf76f9ba Merge branch 'wip-front-end' of git.blender.org:blender-cloud into wip-front-end 2018-08-06 09:05:38 -05:00
Sam Lu
f876961e1c Style Searchbar. Add basic fade animations for searchbar and navigation. 2018-08-06 00:14:10 -05:00
1bf90a6f6e WIP on landing page 2018-08-05 14:20:06 +02:00
aa40e8903b WIP on landing page 2018-08-04 19:55:29 +02:00
Sam Lu
0f5c409c04 Update Search Bar. Fix dropdown menus. 2018-08-04 01:40:27 -05:00
Sam Lu
bd2c8893dd Add Logo Homepage Link 2018-08-03 22:19:00 -05:00
Sam Lu
73730a81c8 Add footer links 2018-08-03 11:25:43 -05:00
Sam Lu
a9a267df2a Add new search bar functionality 2018-08-03 00:08:10 -05:00
Sam Lu
09ae6712bb Polish nav for all pages. Fix hover dropdowns, notification, profile menu 2018-08-02 00:20:05 -05:00
Sam Lu
78702cc5e0 Add base mixin to override old base.css 2018-07-31 23:59:22 -05:00
Sam Lu
515ef5e1b5 Fix sass to override old base and main on all pages and mobile views 2018-07-31 23:57:25 -05:00
Sam Lu
19f9734349 Add super css, fix nav and other components. 2018-07-31 17:33:07 -05:00
Sam Lu
bb3adc37d3 Add larger iframe video embed. 2018-07-28 23:07:39 -05:00
Sam Lu
0e11c2b923 Add placeholder images. 2018-07-27 14:42:00 -05:00
Sam Lu
929edb9f90 Fix cropping in image gallery grid. Fix responsive issues on landing page. 2018-07-27 14:36:45 -05:00
Sam Lu
131bb26e9e Create mixins for reusuable landing page elements like iframe and gallery 2018-07-25 23:53:40 -05:00
Sam Lu
9023e32c47 Make jumbotron responsive. Fix navbar responsive spacing. 2018-07-25 23:45:02 -05:00
Sam Lu
c34d2c3a41 Add navbar dropshadow. Fix spacing on landing page sections. 2018-07-25 19:30:29 -05:00
Sam Lu
27ea3a6c1b Add landing page cta buttons, fix card images to crop, fix spacing issues 2018-07-25 17:36:11 -05:00
Sam Lu
b91aeadb45 Add new project landing with jumbotron, responsive iframe, gallery and updates. 2018-07-25 00:08:57 -05:00
Sam Lu
a4bbff1ad0 Add secondary navbar. Add jinja if for jumbotron bg and secondary navbar 2018-07-20 22:55:04 -05:00
Sam Lu
ecd09a3f02 Add pillar icons 2018-07-19 18:43:21 -05:00
Sam Lu
dbdd32bf0d Remove jinja test 2018-07-18 23:16:13 -05:00
Sam Lu
94b6ee3020 Add sample training courses and update page description. 2018-07-18 23:12:00 -05:00
Sam Lu
1f9b0eda42 Add back dropdown menu links for Libraries and Training. 2018-07-18 18:42:07 -05:00
Sam Lu
97c74f3267 Fix user dropdown and mobile nav 2018-07-18 16:22:36 -05:00
Sam Lu
1342ed2601 Fix gulpfile.js destinations. 2018-07-17 10:17:28 -05:00
Sam Lu
3dcf8ba93a Add responsiveness for footer. Update gulpjs, package.json and bootstrap sass 2018-07-16 21:49:23 -05:00
Sam Lu
5b14d78037 Add new navbar, jumbotron, cards, and footer components. 2018-07-16 15:54:53 -05:00
Sam Lu
c8f6ba207b Initial Commit 2018-07-16 11:54:09 -05:00
280 changed files with 11895 additions and 7905 deletions

5
.gitignore vendored
View File

@@ -3,11 +3,9 @@
.coverage
*.pyc
__pycache__
*.js.map
*.css.map
/cloud/templates/
/cloud/static/assets/
/cloud/static/assets/css/
node_modules/
/config_local.py
@@ -21,7 +19,6 @@ node_modules/
/docker/2_buildpy/python/
/docker/4_run/wheelhouse/
/docker/4_run/deploy/
/docker/4_run/staging/
/celerybeat-schedule.bak
/celerybeat-schedule.dat
/celerybeat-schedule.dir

View File

@@ -27,19 +27,14 @@ git clone git://git.blender.org/blender-cloud.git
### Initial setup and configuration
Create a virtualenv for the project and install the requirements. Dependencies are managed via
[Poetry](https://poetry.eustace.io/). Install it using `pip install -U --user poetry`.
Create a virtualenv for the project and install the requirements:
```
cd blender-cloud
pip install --user -U poetry
poetry install
mkvirtualenv blender-cloud -p python3.6
pip install -r requirements-dev.txt
```
NOTE: After a dependency changed its own dependencies (say a new library was added as dependency of
Pillar), you need to run `poetry update`. This will take the new dependencies into account and write
them to the `poetry.lock` file.
Build assets and templates for all Blender Cloud dependencies using Gulp.
```
@@ -56,7 +51,7 @@ cp config_local.example.py config_local.py
Setup the database with the initial collections and the admin user.
```
poetry run ./manage.py setup setup_db your_email
./manage.py setup setup_db your_email
```
The command will return the following message:
@@ -70,7 +65,7 @@ Copy the value of `<project_id>` and assign it as value for `MAIN_PROJECT_ID`.
Run the application:
```
poetry run ./manage.py runserver
./manage.py runserver
```
@@ -107,7 +102,7 @@ git stash # if you still have local stuff.
# pull from master, run unittests, push your changes to master.
git pull
poetry run py.test
py.test
git push
# Switch to production branch, and investigate the situation.
@@ -117,7 +112,7 @@ git prod
git ff master
# Run tests again
poetry run py.test
py.test
# Push the production branch.
git push
@@ -125,4 +120,13 @@ git push
## Deploying to production server
See [deploy/README.md](deploy/README.md).
```
workon blender-cloud # activate your virtualenv
cd $projectdir/deploy
./2docker.sh
./build-all.sh # or ./build-quick.sh
./2server.sh servername
```
To deploy another branch than `production`, do `export DEPLOY_BRANCH=otherbranch` before starting
the above commands.

View File

@@ -3,8 +3,6 @@ import logging
import flask
from werkzeug.local import LocalProxy
import pillarsdk
import pillar.auth
from pillar.api.utils import authorization
from pillar.extension import PillarExtension
@@ -43,9 +41,6 @@ class CloudExtension(PillarExtension):
'EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER': 'https://store.blender.org/api/',
'EXTERNAL_SUBSCRIPTIONS_TIMEOUT_SECS': 10,
'BLENDER_ID_WEBHOOK_USER_CHANGED_SECRET': 'oos9wah1Zoa0Yau6ahThohleiChephoi',
'NODE_TAGS': ['animation', 'modeling', 'rigging', 'sculpting', 'shading', 'texturing', 'lighting',
'character-pipeline', 'effects', 'video-editing', 'digital-painting', 'production-design',
'walk-through'],
}
def eve_settings(self):
@@ -89,54 +84,6 @@ class CloudExtension(PillarExtension):
'current_user_is_subscriber': authorization.user_has_cap('subscriber')
}
def is_cloud_project(self, project):
"""Returns whether the project is set up for Blender Cloud.
Requires the presence of the 'cloud' key in extension_props
"""
if project.extension_props is None:
# There are no extension_props on this project
return False
try:
pprops = project.extension_props[EXTENSION_NAME]
except AttributeError:
self._log.warning("is_cloud_project: Project url=%r doesn't have any "
"extension properties.", project['url'])
if self._log.isEnabledFor(logging.DEBUG):
import pprint
self._log.debug('Project: %s', pprint.pformat(project.to_dict()))
return False
except KeyError:
# Not set up for Blender Cloud
return False
if pprops is None:
self._log.debug("is_cloud_project: Project url=%r doesn't have Blender Cloud"
" extension properties.", project['url'])
return False
return True
@property
def has_project_settings(self) -> bool:
# Only available for admins
return pillar.auth.current_user.has_cap('admin')
def project_settings(self, project: pillarsdk.Project, **template_args: dict) -> flask.Response:
"""Renders the project settings page for this extension.
Set YourExtension.has_project_settings = True and Pillar will call this function.
:param project: the project for which to render the settings.
:param template_args: additional template arguments.
:returns: a Flask HTTP response
"""
from cloud.routes import project_settings
return project_settings(project, **template_args)
def setup_app(self, app):
from . import routes, webhooks, eve_hooks, email

View File

@@ -9,8 +9,6 @@ import requests
from pillar.cli import manager
from pillar.api import service
from pillar.api.utils import authentication
import cloud.setup
log = logging.getLogger(__name__)
@@ -128,12 +126,4 @@ def reconcile_subscribers():
log.info(' skipped : %d', count_skipped)
@manager_cloud.command
def setup_for_film(project_url):
"""Adds Blender Cloud film custom properties to a project."""
authentication.force_cli_user()
cloud.setup.setup_for_film(project_url)
manager.add_command("cloud", manager_cloud)

View File

@@ -1,15 +0,0 @@
from flask_wtf import FlaskForm
from wtforms import BooleanField, StringField
from wtforms.fields.html5 import URLField
from wtforms.validators import URL
from pillar.web.utils.forms import FileSelectField
class FilmProjectForm(FlaskForm):
video_url = URLField(validators=[URL()])
poster = FileSelectField('Poster Image', file_format='image')
logo = FileSelectField('Logo', file_format='image')
is_in_production = BooleanField('In Production')
is_featured = BooleanField('Featured')
theme_color = StringField('Theme Color')

View File

@@ -3,32 +3,23 @@ import json
import logging
import typing
import bson
from flask_login import login_required
from flask_login import current_user, login_required
import flask
import werkzeug.exceptions as wz_exceptions
from flask import Blueprint, render_template, redirect, session, url_for, abort, flash, request
from flask import Blueprint, render_template, redirect, session, url_for, abort, flash
from pillarsdk import Node, Project, User, exceptions as sdk_exceptions, Group
from pillarsdk.exceptions import ResourceNotFound
import pillar
import pillarsdk
from pillar import current_app
from pillar.api.utils import authorization
from pillar.auth import current_user
import pillar.api
from pillar.web.users import forms
from pillar.web.utils import system_util, get_file, current_user_is_authenticated
from pillar.web.utils import attach_project_pictures
from pillar.web.settings import blueprint as blueprint_settings
from pillar.web.nodes.routes import url_for_node
from pillar.web.nodes.custom.comments import render_comments_for_node
from pillar.web.projects.routes import render_project
from pillar.web.projects.routes import find_project_or_404
from pillar.web.projects.routes import project_view
from pillar.web.projects.routes import project_navigation_links
from cloud import current_cloud
from cloud.forms import FilmProjectForm
from . import EXTENSION_NAME
blueprint = Blueprint('cloud', __name__)
log = logging.getLogger(__name__)
@@ -51,6 +42,35 @@ def _homepage_context() -> dict:
# Get latest blog posts
api = system_util.pillar_api()
latest_posts = Node.all({
'projection': {
'name': 1,
'project': 1,
'node_type': 1,
'picture': 1,
'properties.url': 1,
'properties.content': 1,
'properties.attachments': 1
},
'where': {'node_type': 'post', 'properties.status': 'published'},
'embedded': {'project': 1},
'sort': '-_created',
'max_results': '3'
}, api=api)
# Append picture Files to last_posts
for post in latest_posts._items:
post.picture = get_file(post.picture, api=api)
post.url = url_for_node(node=post)
# Get latest assets added to any project
latest_assets = Node.latest('assets', api=api)
# Append picture Files to latest_assets
for asset in latest_assets._items:
asset.picture = get_file(asset.picture, api=api)
asset.url = url_for_node(node=asset)
# Get latest comments to any node
latest_comments = Node.latest('comments', api=api)
@@ -69,7 +89,6 @@ def _homepage_context() -> dict:
'name': 1,
'node_type': 1,
'project': 1,
'parent': 1,
'properties.url': 1,
}},
api=api)
@@ -96,24 +115,23 @@ def _homepage_context() -> dict:
main_project = Project.find(current_app.config['MAIN_PROJECT_ID'], api=api)
main_project.picture_header = get_file(main_project.picture_header, api=api)
# Merge latest assets and comments into one activity stream.
def sort_key(item):
return item._created
activity_stream = sorted(latest_assets._items, key=sort_key, reverse=True)
for node in activity_stream:
node.url = url_for_node(node=node)
return dict(
main_project=main_project,
latest_posts=latest_posts._items,
latest_comments=latest_comments._items,
activity_stream=activity_stream,
random_featured=random_featured)
@blueprint.route('/design-system')
def design_system():
"""Display the design system page.
This endpoing is intended for development only, and returns a
rendered template only if the app is running in debug mode.
"""
if not current_app.config['DEBUG']:
abort(404)
return render_template('design_system.html')
@blueprint.route('/login')
def login():
from flask import request
@@ -151,16 +169,6 @@ def services():
return render_template('services.html')
@blueprint.route('/learn')
def learn():
return render_template('learn.html')
@blueprint.route('/libraries')
def libraries():
return render_template('libraries.html')
@blueprint.route('/stats')
def stats():
return render_template('stats.html')
@@ -211,35 +219,10 @@ def courses():
def open_projects():
@current_app.cache.cached(timeout=3600, unless=current_user_is_authenticated)
def render_page():
api = system_util.pillar_api()
projects = Project.all({
'where': {
'category': 'film',
'is_private': False
},
'sort': '-_created',
}, api=api)
for project in projects._items:
# Attach poster file (ensure the extension_props.cloud.poster
# attributes exists)
try:
# If the attribute exists, but is None, continue
if not project['extension_props'][EXTENSION_NAME]['poster']:
continue
# Fetch the file and embed it in the document
project.extension_props.cloud.poster = get_file(
project.extension_props.cloud.poster, api=api)
# Add convenience attribute that specifies the presence of the
# poster file
project.has_poster = True
# If there was a key error because one of the nested attributes is
# missing,
except KeyError:
continue
projects = get_projects('film')
return render_template(
'films.html',
title='films',
'projects_index_collection.html',
title='open-projects',
projects=projects._items,
api=system_util.pillar_api())
@@ -278,13 +261,12 @@ def get_random_featured_nodes() -> typing.List[dict]:
'summary': True,
'picture_square': True}},
{'$unwind': {'path': '$nodes_featured'}},
{'$sample': {'size': 6}},
{'$sample': {'size': 3}},
{'$lookup': {'from': 'nodes',
'localField': 'nodes_featured',
'foreignField': '_id',
'as': 'node'}},
{'$unwind': {'path': '$node'}},
{'$match': {'node._deleted': {'$ne': True}}},
{'$project': {'url': True,
'name': True,
'summary': True,
@@ -294,11 +276,7 @@ def get_random_featured_nodes() -> typing.List[dict]:
'node.permissions': True,
'node.picture': True,
'node.properties.content_type': True,
'node.properties.duration_seconds': True,
'node.properties.url': True,
'node._created': True,
}
},
'node.properties.url': True}},
])
featured_node_documents = []
@@ -307,10 +285,11 @@ def get_random_featured_nodes() -> typing.List[dict]:
# Turn the project-with-node doc into a node-with-project doc.
node_document = node_info.pop('node')
node_document['project'] = node_info
node_document['_id'] = str(node_document['_id'])
node = Node(node_document)
node.picture = get_file(node.picture, api=api)
node.url = url_for_node(node=node)
node.project.url = url_for('projects.view', project_url=node.project.url)
node.project.picture_square = get_file(node.project.picture_square, api=api)
featured_node_documents.append(node)
@@ -412,13 +391,6 @@ def privacy():
return render_template('privacy.html')
@blueprint.route('/production')
def production():
return render_template(
'production.html',
title='production')
@blueprint.route('/emails/welcome.send')
@login_required
def emails_welcome_send():
@@ -444,27 +416,32 @@ def emails_welcome_txt():
return flask.Response(txt, content_type='text/plain; charset=utf-8')
@blueprint.route('/p/<project_url>')
def project_landing(project_url):
"""Override Pillar project_view endpoint completely.
@blueprint.route('/nodes/<string(length=24):node_id>/comments')
def comments_for_node(node_id):
"""Overrides the default render_comments_for_node.
The first part of the function is identical to the one in Pillar, but the
second part (starting with 'Load custom project properties') extends the
behaviour to support film project landing pages.
This is done in order to extend can_post_comments by requiring the
subscriber capability.
"""
template_name = None
if request.args.get('format') == 'jstree':
log.warning('projects.view(%r) endpoint called with format=jstree, '
'redirecting to proper endpoint. URL is %s; referrer is %s',
project_url, request.url, request.referrer)
return redirect(url_for('projects.jstree', project_url=project_url))
api = system_util.pillar_api()
project = find_project_or_404(project_url,
node = Node.find(node_id, api=api)
project = Project({'_id': node.project})
can_post_comments = project.node_type_has_method('comment', 'POST', api=api)
can_comment_override = flask.request.args.get('can_comment', 'True') == 'True'
can_post_comments = can_post_comments and can_comment_override and current_user.has_cap(
'subscriber')
return render_comments_for_node(node_id, can_post_comments=can_post_comments)
@blueprint.route('/p/hero')
def project_hero():
api = system_util.pillar_api()
project = find_project_or_404('hero',
embedded={'header_node': 1},
api=api)
# Load the header video file, if there is any.
header_video_file = None
header_video_node = None
@@ -474,182 +451,15 @@ def project_landing(project_url):
header_video_file = get_file(project.header_node.properties.file)
header_video_node.picture = get_file(header_video_node.picture)
extra_context = {
'header_video_file': header_video_file,
'header_video_node': header_video_node}
# Load custom project properties. If the project has a 'cloud' extension prop,
# render it using the projects/landing.html template and try to attach a
# number of additional attributes (pages, images, etc.).
if 'extension_props' in project and EXTENSION_NAME in project['extension_props']:
extension_props = project['extension_props'][EXTENSION_NAME]
extension_props['logo'] = get_file(extension_props['logo'])
pages = Node.all({
'where': {
'project': project._id,
'node_type': 'page',
'_deleted': {'$ne': True}},
'projection': {'name': 1}}, api=api)
extra_context.update({'pages': pages._items})
template_name = 'projects/landing.html'
pages = Node.all({
'where': {'project': project._id, 'node_type': 'page'},
'projection': {'name': 1}}, api=api)
return render_project(project, api,
extra_context=extra_context,
template_name=template_name)
@blueprint.route('/p/<project_url>/browse')
@project_view()
def project_browse(project: pillarsdk.Project):
"""Project view displaying all top-level nodes.
We render a regular project view, but we introduce an additional template
variable: browse. By doing that we prevent the regular project view
from loading and fetch via AJAX a "group" node-like view instead (see
project_browse_view_nodes).
"""
return render_template(
'projects/view.html',
api=system_util.pillar_api(),
project=project,
node=None,
show_project=True,
browse=True,
og_picture=None,
navigation_links=project_navigation_links(project, system_util.pillar_api()),
extension_sidebar_links=current_app.extension_sidebar_links(project))
@blueprint.route('/p/<project_url>/browse/nodes')
@project_view()
def project_browse_view_nodes(project: pillarsdk.Project):
"""Display top-level nodes for a Project.
This view is always meant to be served embedded, as part of project_browse.
"""
api = system_util.pillar_api()
# Get top level nodes
projection = {
'project': 1,
'name': 1,
'picture': 1,
'node_type': 1,
'properties.order': 1,
'properties.status': 1,
'user': 1,
'properties.content_type': 1,
'permissions.world': 1}
where = {
'project': project['_id'],
'parent': {'$exists': False},
'properties.status': 'published',
'_deleted': {'$ne': True},
'node_type': {'$in': ['group', 'asset']},
}
try:
nodes = Node.all({
'projection': projection,
'where': where,
'sort': [('properties.order', 1), ('name', 1)]}, api=api)
except pillarsdk.exceptions.ForbiddenAccess:
return render_template('errors/403_embed.html')
nodes = nodes._items
for child in nodes:
child.picture = get_file(child.picture, api=api)
return render_template(
'projects/browse_embed.html',
nodes=nodes)
def project_settings(project: pillarsdk.Project, **template_args: dict):
"""Renders the project settings page for Blender Cloud projects.
If the project has been setup for Blender Cloud, check for the cloud.category
property, to render the proper form.
"""
# Based on the project state, we can render a different template.
if not current_cloud.is_cloud_project(project):
return render_template('project_settings/offer_setup.html',
project=project, **template_args)
cloud_props = project['extension_props'][EXTENSION_NAME]
category = cloud_props['category']
if category != 'film':
log.error('No interface available to edit %s projects, yet' % category)
form = FilmProjectForm()
# Iterate over the form fields and set the data if exists in the project document
for field_name in form.data:
if field_name not in cloud_props:
continue
# Skip csrf_token field
if field_name == 'csrf_token':
continue
form_field = getattr(form, field_name)
form_field.data = cloud_props[field_name]
return render_template('project_settings/settings.html',
project=project,
form=form,
**template_args)
@blueprint.route('/<project_url>/settings/film', methods=['POST'])
@authorization.require_login(require_cap='admin')
@project_view()
def save_film_settings(project: pillarsdk.Project):
# Ensure that the project is setup for Cloud (see @attract_project_view for example)
form = FilmProjectForm()
if not form.validate_on_submit():
log.debug('Form submission failed')
# Return list of validation errors
updated_extension_props = {}
for field_name in form.data:
# Skip csrf_token field
if field_name == 'csrf_token':
continue
form_field = getattr(form, field_name)
# TODO(fsiddi) if form_field type is FileSelectField, convert it to ObjectId
# Currently this raises TypeError: Object of type 'ObjectId' is not JSON serializable
if form_field.data == '':
form_field.data = None
updated_extension_props[field_name] = form_field.data
# Update extension props and save project
extension_props = project['extension_props'][EXTENSION_NAME]
# Project is a Resource, so we update properties iteratively
for k, v in updated_extension_props.items():
extension_props[k] = v
project.update(api=system_util.pillar_api())
return '', 204
@blueprint.route('/<project_url>/setup-for-film', methods=['POST'])
@login_required
@project_view()
def setup_for_film(project: pillarsdk.Project):
import cloud.setup
project_id = project._id
if not project.has_method('PUT'):
log.warning('User %s tries to set up project %s for Blender Cloud, but has no PUT rights.',
current_user, project_id)
raise wz_exceptions.Forbidden()
log.info('User %s sets up project %s for Blender Cloud', current_user, project_id)
cloud.setup.setup_for_film(project.url)
return '', 204
extra_context={'header_video_file': header_video_file,
'header_video_node': header_video_node,
'pages': pages._items,},
template_name='projects/landing.html')
def setup_app(app):

View File

@@ -1,54 +0,0 @@
"""Setting up projects for Blender Cloud."""
import logging
from bson import ObjectId
from eve.methods.put import put_internal
from flask import current_app
from pillar.api.utils import remove_private_keys
from . import EXTENSION_NAME
log = logging.getLogger(__name__)
def setup_for_film(project_url):
"""Add Blender Cloud extension_props specific for film projects.
Returns the updated project.
"""
projects_collection = current_app.data.driver.db['projects']
# Find the project in the database.
project = projects_collection.find_one({'url': project_url})
if not project:
raise RuntimeError('Project %s does not exist.' % project_url)
# Set default extension properties. Be careful not to overwrite any properties that
# are already there.
all_extension_props = project.setdefault('extension_props', {})
cloud_extension_props = {
'category': 'film',
'theme_css': '',
# The accent color (can be 'blue' or '#FFBBAA' or 'rgba(1, 1, 1, 1)
'theme_color': '',
'is_in_production': False,
'video_url': '', # Oembeddable url
'poster': None, # File ObjectId
'logo': None, # File ObjectId
# TODO(fsiddi) when we introduce other setup_for_* in Blender Cloud, make available
# at a higher scope
'is_featured': False,
}
all_extension_props.setdefault(EXTENSION_NAME, cloud_extension_props)
project_id = ObjectId(project['_id'])
project = remove_private_keys(project)
result, _, _, status_code = put_internal('projects', project, _id=project_id)
if status_code != 200:
raise RuntimeError("Can't update project %s, issues: %s", project_id, result)
log.info('Project %s was updated for Blender Cloud.', project_url)

55
cloud/static/assets/js/layout.min.js vendored Normal file
View File

@@ -0,0 +1,55 @@
/* Variables
================================================================== */
var searchToggleStatus = false
/* Selectors
================================================================== */
var searchButton = document.querySelector('#searchButton'),
searchCloseButton = document.querySelector('#searchCloseButton'),
searchNav = document.querySelector('#searchNav'),
searchNavInput = document.querySelector('#searchNav input'),
navbarNav = document.querySelector('#navbarNav'),
profileMenu = document.querySelector('#navbarNav .navbar-nav').lastElementChild
/* Interactions
================================================================== */
searchButton.addEventListener('click', showSearch)
searchCloseButton.addEventListener('click', closeSearch)
/* Functions
================================================================== */
function showSearch() {
searchToggleStatus = true
searchNav.classList.remove('hidden')
searchNavInput.focus()
navbarNav.classList.add('hidden')
profileMenu.classList.add('visible')
// $('#search-overlay').addClass('visible')
// if (searchToggleStatus == true) {
// // $(document.body).click(function() {
// // console.log('detect click outside');
// // searchToggleStatus = false
// // })
// console.log(searchToggleStatus);
// console.log('detect click outside');
// searchToggleStatus = false
// console.log(searchToggleStatus);
// } else {
// console.log(searchToggleStatus);
// console.log('not detecting clicks');
// }
// $(document.body).click(function () {
// console.log('detected click outside');
// searchToggleStatus == false
// }) else {
// }
}
function closeSearch() {
searchNav.classList.add('hidden')
navbarNav.classList.remove('hidden')
profileMenu.classList.remove('visible')
// $('#search-overlay').removeClass('active')
}

View File

@@ -1,3 +0,0 @@
"""Routes for fetching tagged assets."""

View File

@@ -1,16 +0,0 @@
import logging
import datetime
import functools
from flask import Blueprint, jsonify
blueprint = Blueprint('cloud.tagged', __name__, url_prefix='/tagged')
log = logging.getLogger(__name__)
@blueprint.route('/')
def index():
"""Return all tagged assets as JSON, grouped by tag."""

View File

@@ -7,7 +7,8 @@ import json
import logging
import typing
from flask import Blueprint, request
from flask_login import request
from flask import Blueprint
import werkzeug.exceptions as wz_exceptions
from pillar import current_app
@@ -101,8 +102,8 @@ def insert_or_fetch_user(wh_payload: dict) -> typing.Optional[dict]:
{'auth.provider': 'blender-id', 'auth.user_id': bid_str},
{'email': {'$in': [wh_payload['old_email'], email]}},
]}
db_users = list(users_coll.find(query))
user_count = len(db_users)
db_users = users_coll.find(query)
user_count = db_users.count()
if user_count > 1:
# Now we have to pay the price for finding users in one query; we
# have to prioritise them and return the one we think is most reliable.
@@ -117,10 +118,6 @@ def insert_or_fetch_user(wh_payload: dict) -> typing.Optional[dict]:
my_log.debug('found user %s', db_user['email'])
return db_user
if wh_payload.get('date_deletion_requested'):
my_log.info('Received update for a deleted user %s, not creating', bid_str)
return None
# Pretend to create the user, so that we can inspect the resulting
# capabilities. This is more future-proof than looking at the list
# of roles in the webhook payload.
@@ -168,7 +165,6 @@ def user_modified():
'old_email': 'old@example.com',
'full_name': 'Harry',
'email': 'new@example'com,
'avatar_changed': True,
'roles': ['role1', 'role2', …]}
"""
my_log = log.getChild('user_modified')
@@ -185,10 +181,6 @@ def user_modified():
my_log.info('Received update for unknown user %r', payload['old_email'])
return '', 204
if payload.get('date_deletion_requested'):
delete_user(db_user, payload)
return '', 204
# Use direct database updates to change the email and full name.
# Also updates the db_user dict so that local_user below will have
# the updated information.
@@ -208,11 +200,6 @@ def user_modified():
updates['full_name'] = db_user['username']
db_user['full_name'] = updates['full_name']
if payload.get('avatar_changed'):
import pillar.celery.avatar
my_log.info('User %s changed avatar, scheduling download', db_user['_id'])
pillar.celery.avatar.sync_avatar_for_user.delay(str(db_user['_id']))
if updates:
users_coll = current_app.db('users')
update_res = users_coll.update_one({'_id': db_user['_id']},
@@ -227,37 +214,3 @@ def user_modified():
subscription.do_update_subscription(local_user, payload)
return '', 204
def delete_user(db_user, payload):
"""Handle deletion request coming from BID."""
my_log = log.getChild('delete_user')
date_deletion_requested = payload['date_deletion_requested']
bid_str = str(payload['id'])
local_id = db_user['_id']
my_log.info(
'User %s with BID=%s requested deletion on %s, soft-deleting the user',
local_id, bid_str, date_deletion_requested,
)
# Delete all session tokens linked to this user
token_coll = current_app.db('tokens')
delete_res = token_coll.delete_many({'user': local_id})
my_log.info('Deleted %s session tokens of user %s', delete_res.deleted_count, local_id)
# Soft-delete the user and clear their PII
users_coll = current_app.db('users')
updates = {
'_deleted': True,
'email': None,
'full_name': None,
'username': None,
'auth': [],
}
update_res = users_coll.update_one({'_id': local_id}, {'$set': updates})
if update_res.matched_count != 1:
my_log.error(
'Soft-deleted %s users %s with BID=%s',
update_res.matched_count, local_id, bid_str,
)
else:
my_log.warning('Soft-deleted user %s with BID=%s', local_id, bid_str)

View File

@@ -2,7 +2,7 @@ import os
DEBUG = True
BLENDER_ID_ENDPOINT = 'http://id.local:8000/'
BLENDER_ID_ENDPOINT = 'http://id.local:8000'
SERVER_NAME = 'cloud.local:5001'
SCHEME = 'http'
@@ -30,14 +30,3 @@ URLER_SERVICE_AUTH_TOKEN = '##DEFINE##'
ZENCODER_API_KEY = '##DEFINE##'
ZENCODER_NOTIFICATIONS_SECRET = '##DEFINE##'
ZENCODER_NOTIFICATIONS_URL = 'http://zencoderfetcher/'
# Special announcement on top of every page, for non-subscribers.
# category: 'string', can be 'info', 'warning', 'danger', or 'success'.
# message: 'string', any text, it gets markdowned.
# icon: 'string', any icon in font-pillar. e.g. 'pi-heart-filled'
UI_ANNOUNCEMENT_NON_SUBSCRIBERS = {
'category': 'danger',
'message': 'Spring will swing away the gray clouds, until then, '
'[take cover under Blender Cloud](https://cloud.blender.org)!',
'icon': 'pi-heart-filled',
}

View File

@@ -1,6 +1,6 @@
#!/bin/bash -e
STAGING_BRANCH=${STAGING_BRANCH:-production}
DEPLOY_BRANCH=${DEPLOY_BRANCH:-production}
# macOS does not support readlink -f, so we use greadlink instead
if [[ `uname` == 'Darwin' ]]; then
@@ -11,34 +11,34 @@ else
fi
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
STAGINGDIR="$ROOT/docker/4_run/staging"
DEPLOYDIR="$ROOT/docker/4_run/deploy"
PROJECT_NAME="$(basename $ROOT)"
if [ -e $STAGINGDIR ]; then
echo "$STAGINGDIR already exists, press [ENTER] to destroy and re-install, Ctrl+C to abort."
if [ -e $DEPLOYDIR ]; then
echo "$DEPLOYDIR already exists, press [ENTER] to DESTROY AND DEPLOY, Ctrl+C to abort."
read dummy
rm -rf $STAGINGDIR
rm -rf $DEPLOYDIR
else
echo -n "Installing into $STAGINGDIR"
echo -n "Deploying to $DEPLOYDIR"
echo "press [ENTER] to continue, Ctrl+C to abort."
read dummy
fi
cd ${ROOT}
mkdir -p $STAGINGDIR
REMOTE_ROOT="$STAGINGDIR/$PROJECT_NAME"
mkdir -p $DEPLOYDIR
REMOTE_ROOT="$DEPLOYDIR/$PROJECT_NAME"
if [ -z "$SKIP_BRANCH_CHECK" ]; then
# Check that we're on production branch.
if [ $(git rev-parse --abbrev-ref HEAD) != "$STAGING_BRANCH" ]; then
echo "You are NOT on the $STAGING_BRANCH branch, refusing to stage." >&2
if [ $(git rev-parse --abbrev-ref HEAD) != "$DEPLOY_BRANCH" ]; then
echo "You are NOT on the $DEPLOY_BRANCH branch, refusing to deploy." >&2
exit 1
fi
# Check that production branch has been pushed.
if [ -n "$(git log origin/$STAGING_BRANCH..$STAGING_BRANCH --oneline)" ]; then
echo "WARNING: not all changes to the $STAGING_BRANCH branch have been pushed."
echo "Press [ENTER] to continue staging current origin/$STAGING_BRANCH, CTRL+C to abort."
if [ -n "$(git log origin/$DEPLOY_BRANCH..$DEPLOY_BRANCH --oneline)" ]; then
echo "WARNING: not all changes to the $DEPLOY_BRANCH branch have been pushed."
echo "Press [ENTER] to continue deploying current origin/$DEPLOY_BRANCH, CTRL+C to abort."
read dummy
fi
fi
@@ -88,21 +88,15 @@ function git_clone() {
echo "==================================================================="
echo "CLONING REPO ON $PROJECT_NAME @$BRANCH"
URL=$(git -C $LOCAL_ROOT remote get-url origin)
git -C $STAGINGDIR clone --depth 1 --branch $BRANCH $URL $PROJECT_NAME
git -C $DEPLOYDIR clone --depth 1 --branch $BRANCH $URL $PROJECT_NAME
}
if [ "$STAGING_BRANCH" == "production" ]; then
SDK_STAGING_BRANCH=master # SDK doesn't have a production branch
else
SDK_STAGING_BRANCH=$STAGING_BRANCH
fi
git_clone pillar-python-sdk $SDK_STAGING_BRANCH $SDK_DIR
git_clone pillar $STAGING_BRANCH $PILLAR_DIR
git_clone attract $STAGING_BRANCH $ATTRACT_DIR
git_clone flamenco $STAGING_BRANCH $FLAMENCO_DIR
git_clone pillar-svnman $STAGING_BRANCH $SVNMAN_DIR
git_clone blender-cloud $STAGING_BRANCH $ROOT
git_clone pillar-python-sdk master $SDK_DIR
git_clone pillar $DEPLOY_BRANCH $PILLAR_DIR
git_clone attract $DEPLOY_BRANCH $ATTRACT_DIR
git_clone flamenco $DEPLOY_BRANCH $FLAMENCO_DIR
git_clone pillar-svnman $DEPLOY_BRANCH $SVNMAN_DIR
git_clone blender-cloud $DEPLOY_BRANCH $ROOT
# Gulp everywhere
GULP=$ROOT/node_modules/.bin/gulp
@@ -110,21 +104,13 @@ if [ ! -e $GULP -o gulpfile.js -nt $GULP ]; then
npm install
touch $GULP # installer doesn't always touch this after a build, so we do.
fi
# List of projects
PROJECTS="pillar attract flamenco pillar-svnman blender-cloud"
# Run ./gulp for every project
for PROJECT in $PROJECTS; do
pushd $STAGINGDIR/$PROJECT; ./gulp --production; popd;
done
# Remove node_modules (only after all projects with interdependencies have been built)
for PROJECT in $PROJECTS; do
pushd $STAGINGDIR/$PROJECT; rm -r node_modules; popd;
done
$GULP --cwd $DEPLOYDIR/pillar --production
$GULP --cwd $DEPLOYDIR/attract --production
$GULP --cwd $DEPLOYDIR/flamenco --production
$GULP --cwd $DEPLOYDIR/pillar-svnman --production
$GULP --cwd $DEPLOYDIR/blender-cloud --production
echo
echo "==================================================================="
echo "Staging of ${PROJECT_NAME} is ready for dockerisation."
echo "Deploy of ${PROJECT_NAME} is ready for dockerisation."
echo "==================================================================="

View File

@@ -9,6 +9,7 @@ else
fi
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
PROJECT_NAME="$(basename $ROOT)"
DOCKER_DEPLOYDIR="$ROOT/docker/4_run/deploy"
DOCKER_IMAGE="armadillica/blender_cloud:latest"
REMOTE_SECRET_CONFIG_DIR="/data/config"
REMOTE_DOCKER_COMPOSE_DIR="/root/docker"

View File

@@ -1,39 +0,0 @@
# Deploying to Production
```
workon blender-cloud # activate your virtualenv
cd $projectdir/deploy
./full-pull.sh
```
## The Details
Deployment consists of a few steps:
1. Populate a staging directory with the files from the production branches of the various projects.
2. Create Docker images.
3. Push the docker images to Docker Hub.
4. Pull the docker images on the production server and rebuild+restart the containers.
The scripts involved are:
- `2docker.sh`: performs step 1. above.
- `build-{xxx}.sh`: performs steps 2. and 3. above.
- `2server.sh`: performs step 4. above.
The `full-{xxx}.sh` scripts perform all the steps, and call into `build-{xxx}.sh`.
For `xxx` there are:
- `all`: Rebuild all Docker images from scratch. This is good for getting the latest updates to the
base image.
- `pull`: Pull the base and intermediate images from Docker Hub so that they are the same as the
last time someone pushed to production, then rebuilds the final Docker image.
- `quick`: Just rebuild the final Docker image. Only use this if the last time a deployment to
the production server was done was by you, on the machine you're working on now.
## Hacking Stuff
To deploy another branch than `production`, do `export STAGING_BRANCH=otherbranch` before starting
the above commands.

View File

@@ -1,43 +0,0 @@
#!/bin/bash -e
# macOS does not support readlink -f, so we use greadlink instead
if [[ `uname` == 'Darwin' ]]; then
command -v greadlink 2>/dev/null 2>&1 || { echo >&2 "Install greadlink using brew."; exit 1; }
readlink='greadlink'
else
readlink='readlink'
fi
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
case "$(basename "$0")" in
build-pull.sh)
docker pull armadillica/pillar_py:3.6
docker pull armadillica/pillar_wheelbuilder:latest
pushd "$ROOT/docker/3_buildwheels"
./build.sh
popd
pushd "$ROOT/docker/4_run"
./build.sh
;;
build-quick.sh)
pushd "$ROOT/docker/4_run"
./build.sh
;;
build-all.sh)
pushd "$ROOT/docker"
./full_rebuild.sh
;;
*)
echo "Unknown script $0, aborting" >&2
exit 1
esac
popd
echo
echo "Press [ENTER] to push the new Docker images."
read dummy
docker push armadillica/pillar_py:3.6
docker push armadillica/pillar_wheelbuilder:latest
docker push armadillica/blender_cloud:latest
echo
echo "Build is done, ready to update the server."

1
deploy/build-all.sh Symbolic link
View File

@@ -0,0 +1 @@
build-quick.sh

View File

@@ -1 +0,0 @@
build-quick.sh

View File

@@ -1 +0,0 @@
build-all.sh

34
deploy/build-quick.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash -e
# macOS does not support readlink -f, so we use greadlink instead
if [[ `uname` == 'Darwin' ]]; then
command -v greadlink 2>/dev/null 2>&1 || { echo >&2 "Install greadlink using brew."; exit 1; }
readlink='greadlink'
else
readlink='readlink'
fi
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
DOCKERDIR="$ROOT/docker/4_run"
case "$(basename "$0")" in
build-quick.sh)
pushd "$ROOT/docker/4_run"
./build.sh
;;
build-all.sh)
pushd "$ROOT/docker"
./full_rebuild.sh
;;
*)
echo "Unknown script $0, aborting" >&2
exit 1
esac
popd
echo
echo "Press [ENTER] to push the new Docker image."
read dummy
docker push armadillica/blender_cloud:latest
echo
echo "Build is done, ready to update the server."

View File

@@ -1,9 +0,0 @@
#!/bin/bash
set -e
NAME="$(basename "$0")"
./2docker.sh
./${NAME/full-/build-}
./2server.sh cloud2

View File

@@ -1 +0,0 @@
full-all.sh

View File

@@ -1 +0,0 @@
full-all.sh

View File

@@ -1,10 +1,6 @@
FROM ubuntu:18.04
LABEL maintainer="Sybren A. Stüvel <sybren@blender.studio>"
FROM ubuntu:17.10
MAINTAINER Francesco Siddi <francesco@blender.org>
RUN set -ex; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install \
-qyy -o APT::Install-Recommends=false -o APT::Install-Suggests=false \
tzdata openssl ca-certificates locales; \
locale-gen en_US.UTF-8 en_GB.UTF-8 nl_NL.UTF-8
ENV LANG en_US.UTF-8
RUN apt-get update && apt-get install -qyy \
-o APT::Install-Recommends=false -o APT::Install-Suggests=false \
openssl ca-certificates

View File

@@ -0,0 +1 @@
1325134dd525b4a2c3272a1a0214dd54 Python-3.6.4.tar.xz

View File

@@ -1 +0,0 @@
c3f30a0aff425dda77d19e02f420d6ba Python-3.6.6.tar.xz

View File

@@ -34,9 +34,6 @@ make -j8 install
# Make sure we can run Python
ldconfig
# Upgrade pip
/opt/python/bin/python3 -m pip install -U pip
# Build mod-wsgi-py3 for Python 3.6
cd /dpkg/mod-wsgi-*
./configure --with-python=/opt/python/bin/python3

View File

@@ -1,9 +1,9 @@
FROM pillar_base
LABEL maintainer="Sybren A. Stüvel <sybren@blender.studio>"
LABEL maintainer Sybren A. Stüvel <sybren@blender.studio>
RUN sed -i 's/^# deb-src/deb-src/' /etc/apt/sources.list && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -qy \
apt-get install -qy \
build-essential \
apache2-dev \
checkinstall \
@@ -11,13 +11,13 @@ RUN sed -i 's/^# deb-src/deb-src/' /etc/apt/sources.list && \
RUN apt-get build-dep -y python3.6
ADD Python-3.6.6.tar.xz.md5 /Python-3.6.6.tar.xz.md5
ADD Python-3.6.4.tar.xz.md5 /Python-3.6.4.tar.xz.md5
# Install Python sources
RUN curl -O https://www.python.org/ftp/python/3.6.6/Python-3.6.6.tar.xz && \
md5sum -c Python-3.6.6.tar.xz.md5 && \
tar xf Python-3.6.6.tar.xz && \
rm -v Python-3.6.6.tar.xz
RUN curl -O https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tar.xz && \
md5sum -c Python-3.6.4.tar.xz.md5 && \
tar xf Python-3.6.4.tar.xz && \
rm -v Python-3.6.4.tar.xz
# Install mod-wsgi sources
RUN mkdir -p /dpkg && cd /dpkg && apt-get source libapache2-mod-wsgi-py3
@@ -32,4 +32,4 @@ RUN echo /opt/python/lib > /etc/ld.so.conf.d/python.conf
RUN ldconfig
ENV PATH=/opt/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV PYTHONSOURCE=/Python-3.6.6
ENV PYTHONSOURCE=/Python-3.6.4

View File

@@ -1,5 +1,5 @@
FROM pillar_base
LABEL maintainer="Sybren A. Stüvel <sybren@blender.studio>"
LABEL maintainer Sybren A. Stüvel <sybren@blender.studio>
ADD python /opt/python
@@ -10,4 +10,5 @@ RUN echo Python is installed in /opt/python/ > README.python
ENV PATH=/opt/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN cd /opt/python/bin && \
ln -s python3 python
ln -s python3 python && \
ln -s pip3 pip

View File

@@ -1,9 +1,7 @@
FROM armadillica/pillar_py:3.6
LABEL maintainer="Sybren A. Stüvel <sybren@blender.studio>"
LABEL maintainer Sybren A. Stüvel <sybren@blender.studio>
RUN set -ex; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -qy \
RUN apt-get update && apt-get install -qy \
git \
build-essential \
checkinstall \

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env bash
DOCKER_IMAGE_NAME=armadillica/pillar_wheelbuilder
set -e
# macOS does not support readlink -f, so we use greadlink instead
@@ -23,40 +21,25 @@ fi
echo "Wheelhouse is $WHEELHOUSE"
mkdir -p "$WHEELHOUSE"
rm -f "$WHEELHOUSE"/*
docker build -t $DOCKER_IMAGE_NAME:latest .
docker build -t pillar_wheelbuilder .
GID=$(id -g)
docker run --rm -i \
-v "$WHEELHOUSE:/data/wheelhouse" \
-v "$TOPDEVDIR:/data/topdev" \
$DOCKER_IMAGE_NAME <<EOT
pillar_wheelbuilder <<EOT
set -e
set -x
# Globally upgrade Pip, so that we can get a compatible version of the cryptography package.
# See https://github.com/pyca/cryptography/issues/5771
pip3 install --upgrade pip setuptools wheel
# Pin poetry to 1.0, as more recent version to not support nested filesystem package
# dependencies.
pip3 install wheel poetry==1.0 cryptography==2.7
# Build wheels for all dependencies.
cd /data/topdev/blender-cloud
pip3 install wheel
pip3 wheel --wheel-dir=/data/wheelhouse -r requirements.txt
chown -R $UID:$GID /data/wheelhouse
poetry install --no-dev
# Apparently pip doesn't like projects without setup.py, so it think we have 'pillar-svnman' as
# requirement (because that's the name of the directory). We have to grep that out.
poetry run pip3 freeze | grep -v '\(pillar\)\|\(^-[ef] \)' > \$WHEELHOUSE/requirements.txt
pip3 wheel --wheel-dir=\$WHEELHOUSE -r \$WHEELHOUSE/requirements.txt
chown -R $UID:$GID \$WHEELHOUSE
# Install the dependencies so that we can get a full freeze.
pip3 install --no-index --find-links=/data/wheelhouse -r requirements.txt
pip3 freeze | grep -v '^-[ef] ' > /data/wheelhouse/requirements.txt
EOT
# Remove our own projects, they shouldn't be installed as wheel (for now).
rm -f $WHEELHOUSE/{attract,flamenco,pillar,pillarsdk}*.whl
echo "Build of $DOCKER_IMAGE_NAME:latest is done."

View File

@@ -1,19 +1,17 @@
FROM armadillica/pillar_py:3.6
LABEL maintainer="Sybren A. Stüvel <sybren@blender.studio>"
LABEL maintainer Sybren A. Stüvel <sybren@blender.studio>
RUN set -ex; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -qy \
-o APT::Install-Recommends=false -o APT::Install-Suggests=false \
git \
apache2 \
libapache2-mod-xsendfile \
libjpeg8 \
libtiff5 \
ffmpeg \
rsyslog logrotate \
nano vim-tiny curl; \
rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -qyy \
-o APT::Install-Recommends=false -o APT::Install-Suggests=false \
git \
apache2 \
libapache2-mod-xsendfile \
libjpeg8 \
libtiff5 \
ffmpeg \
rsyslog logrotate \
nano vim-tiny curl \
&& rm -rf /var/lib/apt/lists/*
RUN ln -s /usr/bin/vim.tiny /usr/bin/vim
@@ -38,9 +36,8 @@ ENV USE_X_SENDFILE True
EXPOSE 80
EXPOSE 5000
ADD apache/remoteip.conf /etc/apache2/mods-available/
ADD apache/wsgi-py36.* /etc/apache2/mods-available/
RUN a2enmod remoteip && a2enmod rewrite && a2enmod wsgi-py36
RUN a2enmod rewrite && a2enmod wsgi-py36
ADD apache/apache2.conf /etc/apache2/apache2.conf
ADD apache/000-default.conf /etc/apache2/sites-available/000-default.conf
@@ -58,7 +55,7 @@ ENTRYPOINT /docker-entrypoint.sh
# Add the most-changing files as last step for faster rebuilds.
ADD config_local.py /data/git/blender-cloud/
ADD staging /data/git
ADD deploy /data/git
RUN python3 -c "import re, secrets; \
f = open('/data/git/blender-cloud/config_local.py', 'a'); \
h = re.sub(r'[_.~-]', '', secrets.token_urlsafe())[:8]; \

View File

@@ -40,7 +40,7 @@
# Redirects for blender-cloud projects
RewriteRule "^/p/blender-cloud/?$" "/blog" [R=301,L]
RewriteRule "^/agent327/?$" "/p/agent-327" [R=301,L]
RewriteRule "^/caminandes/?$" "/p/caminandes-3" [R=301,L]
RewriteRule "^/caminandes/?$" "/p/caminandes" [R=301,L]
RewriteRule "^/cf2/?$" "/p/creature-factory-2" [R=301,L]
RewriteRule "^/characters/?$" "/p/characters" [R=301,L]
RewriteRule "^/gallery/?$" "/p/gallery" [R=301,L]
@@ -49,8 +49,6 @@
RewriteRule "^/training/?$" "/courses" [R=301,L]
RewriteRule "^/spring/?$" "/p/spring" [R=301,L]
RewriteRule "^/hero/?$" "/p/hero" [R=301,L]
RewriteRule "^/coffee-run/?$" "/p/coffee-run" [R=301,L]
RewriteRule "^/settlers/?$" "/p/settlers" [R=301,L]
# Waking the forest was moved from the art gallery to its own workshop
RewriteRule "^/p/gallery/58cfec4f88ac8f1440aeb309/?$" "/p/waking-the-forest" [R=301,L]
</VirtualHost>

View File

@@ -133,9 +133,9 @@ AccessFileName .htaccess
# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended.
# Use mod_remoteip instead.
#
LogFormat "%v:%p %a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%a %l %u %t \"%r\" %>s %O" common
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

View File

@@ -1,2 +0,0 @@
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 172.16.0.0/12

View File

@@ -28,7 +28,7 @@ CACHE_REDIS_URL = 'redis://redis:6379'
PILLAR_SERVER_ENDPOINT = 'https://cloud.blender.org/api/'
BLENDER_ID_ENDPOINT = 'https://www.blender.org/id/'
BLENDER_ID_ENDPOINT = 'https://www.blender.org/id'
GCLOUD_APP_CREDENTIALS = '/data/config/google_app.json'
GCLOUD_PROJECT = 'blender-cloud'
@@ -83,22 +83,21 @@ LOGGING = {
}
}
# Latest version of the add-on.
BLENDER_CLOUD_ADDON_VERSION = '1.9.0'
REDIRECTS = {
# For old links, refer to the services page (hopefully it refreshes then)
'downloads/blender_cloud-latest-bundle.zip': 'https://cloud.blender.org/services#blender-addon',
# Latest Blender Cloud add-on.
# Latest Blender Cloud add-on; remember to update BLENDER_CLOUD_ADDON_VERSION.
'downloads/blender_cloud-latest-addon.zip':
f'https://storage.googleapis.com/institute-storage/addons/'
f'blender_cloud-{BLENDER_CLOUD_ADDON_VERSION}.addon.zip',
'https://storage.googleapis.com/institute-storage/addons/blender_cloud-1.8.0.addon.zip',
# Redirect old Grafista endpoint to /stats
'/stats/': '/stats',
}
# Latest version of the add-on; remember to update REDIRECTS.
BLENDER_CLOUD_ADDON_VERSION = '1.8.0'
UTM_LINKS = {
'cartoon_brew': {
'image': 'https://imgur.com/13nQTi3.png',
@@ -106,6 +105,15 @@ UTM_LINKS = {
}
}
# Disabled until we have regenerated the majority of the links.
CELERY_BEAT_SCHEDULE = {
'regenerate-expired-links': {
'task': 'pillar.celery.file_link_tasks.regenerate_all_expired_links',
'schedule': 600, # every N seconds
'args': ('gcs', 500)
},
}
SVNMAN_REPO_URL = 'https://svn.blender.cloud/repo/'
SVNMAN_API_URL = 'https://svn.blender.cloud/api/'

View File

@@ -1,4 +1,3 @@
#!/bin/sh
if [ -f /installed ]; then
return

View File

@@ -1,7 +1,7 @@
version: '3.4'
services:
mongo:
image: mongo:3.4
image: mongo:3.4.2
container_name: mongo
restart: always
volumes:
@@ -15,12 +15,8 @@ services:
max-size: "200k"
max-file: "20"
# Databases in use:
# 0: Flask Cache
# 1: Celery (backend)
# 2: Celery (broker)
redis:
image: redis:5.0
image: redis:3.2.8
container_name: redis
restart: always
ports:
@@ -31,8 +27,19 @@ services:
max-size: "200k"
max-file: "20"
rabbit:
image: rabbitmq:3.6.10
container_name: rabbit
restart: always
ports:
- "127.0.0.1:5672:5672"
logging:
driver: "json-file"
options:
max-size: "200k"
max-file: "20"
elastic:
# This image is defined in blender-cloud/docker/elastic
image: armadillica/elasticsearch:6.1.1
container_name: elastic
restart: always
@@ -63,7 +70,6 @@ services:
max-file: "20"
kibana:
# This image is defined in blender-cloud/docker/elastic
image: armadillica/kibana:6.1.1
container_name: kibana
restart: always
@@ -103,6 +109,7 @@ services:
depends_on:
- mongo
- redis
- rabbit
celery_worker:
image: armadillica/blender_cloud:latest
@@ -119,6 +126,7 @@ services:
depends_on:
- mongo
- redis
- rabbit
logging:
driver: "json-file"
options:
@@ -140,6 +148,7 @@ services:
depends_on:
- mongo
- redis
- rabbit
- celery_worker
logging:
driver: "json-file"
@@ -160,8 +169,7 @@ services:
- /data/letsencrypt:/data/letsencrypt
haproxy:
# This image is defined in blender-cloud/docker/haproxy
image: armadillica/haproxy:1.6.7
image: dockercloud/haproxy:1.5.3
container_name: haproxy
restart: always
ports:

View File

@@ -1,5 +0,0 @@
FROM dockercloud/haproxy:1.6.7
LABEL maintainer="Sybren A. Stüvel <sybren@blender.studio>"
# Fix https://talosintelligence.com/vulnerability_reports/TALOS-2019-0782
RUN sed 's/root::/root:!:/' -i /etc/shadow

View File

@@ -1,10 +0,0 @@
#!/bin/bash -e
# When updating this, also update the version in Dockerfile
VERSION=1.6.7
docker build -t armadillica/haproxy:${VERSION} .
docker tag armadillica/haproxy:${VERSION} armadillica/haproxy:latest
echo "Done, built armadillica/haproxy:${VERSION}"
echo "Also tagged as armadillica/haproxy:latest"

6
gulp
View File

@@ -15,8 +15,9 @@ if [ "$1" == "watch" ]; then
# Treat "gulp watch" as "gulp && gulp watch"
$GULP
elif [ "$1" == "all" ]; then
pushd .
# This is useful when building the Blender Cloud project for the first time.
# Run "gulp" once inside the repo
$GULP
# Run ./gulp in all depending projects (pillar, attract, flamenco, pillar-svnman)
declare -a repos=("pillar" "attract" "flamenco" "pillar-svnman")
for r in "${repos[@]}"
@@ -24,9 +25,6 @@ elif [ "$1" == "all" ]; then
cd ../$r
./gulp
done
popd
# Run "gulp" once inside the repo
$GULP
exit 1
fi

View File

@@ -1,130 +1,113 @@
let argv = require('minimist')(process.argv.slice(2));
let autoprefixer = require('gulp-autoprefixer');
let cache = require('gulp-cached');
let chmod = require('gulp-chmod');
let concat = require('gulp-concat');
let git = require('gulp-git');
let gulp = require('gulp');
let gulpif = require('gulp-if');
let pug = require('gulp-pug');
let livereload = require('gulp-livereload');
let plumber = require('gulp-plumber');
let rename = require('gulp-rename');
let sass = require('gulp-sass');
let sourcemaps = require('gulp-sourcemaps');
let uglify = require('gulp-uglify-es').default;
let uglify = require('gulp-uglify');
let enabled = {
uglify: argv.production,
maps: !argv.production,
failCheck: !argv.production,
prettyPug: !argv.production,
cachify: !argv.production,
cleanup: argv.production,
chmod: argv.production,
failCheck: !argv.production,
maps: argv.production,
prettyPug: !argv.production,
uglify: argv.production
};
let destination = {
css: 'cloud/static/assets/css',
pug: 'cloud/templates',
js: 'cloud/static/assets/js',
}
let source = {
pillar: '../pillar/'
}
css: 'cloud/static/assets/css',
pug: 'cloud/templates',
js: 'cloud/static/assets/js'
};
/* CSS */
gulp.task('styles', function(done) {
gulp.src('src/styles/**/*.sass')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.maps, sourcemaps.init()))
.pipe(sass({
outputStyle: 'compressed'}
))
.pipe(autoprefixer("last 3 versions"))
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(gulp.dest(destination.css));
done();
gulp
.src('src/styles/**/*.sass')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.maps, sourcemaps.init()))
.pipe(sass({outputStyle: 'compressed'}))
.pipe(autoprefixer("last 3 versions"))
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(gulp.dest(destination.css))
.pipe(gulpif(argv.livereload, livereload()));
done();
});
/* Templates - Pug */
gulp.task('templates', function(done) {
gulp.src('src/templates/**/*.pug')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.cachify, cache('templating')))
.pipe(pug({
pretty: enabled.prettyPug
}))
.pipe(gulp.dest(destination.pug));
// TODO(venomgfx): please check why 'gulp watch' doesn't pick up on .txt changes.
gulp.src('src/templates/**/*.txt')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.cachify, cache('templating')))
.pipe(gulp.dest(destination.pug));
done();
gulp.src('src/templates/**/*.pug')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(cache('templating'))
.pipe(pug({
pretty: enabled.prettyPug
}))
.pipe(gulp.dest(destination.pug))
.pipe(gulpif(argv.livereload, livereload()));
done();
});
/* Tutti gets built by Pillar. See gulpfile.js in pillar.*/
/* Individual Uglified Scripts */
/* Individually uglified scripts */
gulp.task('scripts', function(done) {
gulp.src('src/scripts/*.js')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.cachify, cache('scripting')))
.pipe(gulpif(enabled.maps, sourcemaps.init()))
.pipe(gulpif(enabled.uglify, uglify()))
.pipe(rename({suffix: '.min'}))
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(gulpif(enabled.chmod, chmod(0o644)))
.pipe(gulp.dest(destination.js));
done();
gulp.src('src/scripts/*.js')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(cache('scripting'))
.pipe(gulpif(enabled.maps, sourcemaps.init()))
.pipe(gulpif(enabled.uglify, uglify()))
.pipe(rename({suffix: '.min'}))
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(gulp.dest(destination.js))
.pipe(gulpif(argv.livereload, livereload()));
done();
});
/* Collection of scripts in src/scripts/tutti/ to merge into tutti.min.js */
/* Since it's always loaded, it's only for functions that we want site-wide */
gulp.task('scripts_tutti', function(done) {
gulp.src('src/scripts/tutti/**/*.js')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.maps, sourcemaps.init()))
.pipe(concat("tutti.min.js"))
.pipe(gulpif(enabled.uglify, uglify()))
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(gulp.dest(destination.js))
.pipe(gulpif(argv.livereload, livereload()));
done();
});
/* Simply move these vendor scripts from node_modules */
gulp.task('scripts_vendor', function(done) {
let toMove = [
'node_modules/photoswipe/dist/photoswipe.min.js'
];
gulp.src(toMove)
.pipe(gulp.dest(destination.js));
done();
});
// While developing, run 'gulp watch'
gulp.task('watch',function(done) {
let watchStyles = [
'src/styles/**/*.sass',
source.pillar + 'src/styles/**/*.sass',
];
gulp.task('watch', function(done) {
// Only reload the pages if we run with --livereload
if (argv.livereload){
livereload.listen();
}
let watchScripts = [
'src/scripts/**/*.js',
source.pillar + 'src/scripts/**/*.js',
];
let watchTemplates = [
'src/templates/**/*.pug',
source.pillar + 'src/templates/**/*.pug',
];
gulp.watch(watchStyles, gulp.series('styles'));
gulp.watch(watchScripts, gulp.series('scripts'));
gulp.watch(watchTemplates, gulp.series('templates'));
done();
});
// Erases all generated files in output directories.
gulp.task('cleanup', function(done) {
let paths = [];
for (attr in destination) {
paths.push(destination[attr]);
}
git.clean({ args: '-f -X ' + paths.join(' ') }, function (err) {
if(err) throw err;
});
done();
gulp.watch('src/styles/**/*.sass', gulp.series('styles'));
gulp.watch('src/templates/**/*.pug', gulp.series('templates'));
gulp.watch('src/scripts/*.js', gulp.series('scripts'));
gulp.watch('src/scripts/tutti/*.js', gulp.series('scripts_tutti'));
});
// Run 'gulp' to build everything at once
let tasks = [];
if (enabled.cleanup) tasks.push('cleanup');
gulp.task('default', gulp.parallel(tasks.concat(['styles', 'templates', 'scripts'])));
gulp.task('default', gulp.series('styles', 'templates', 'scripts', 'scripts_tutti', 'scripts_vendor'));

View File

@@ -1,6 +0,0 @@
# Flamenco Server JWT keys
To generate a keypair for `ES256`:
openssl ecparam -genkey -name prime256v1 -noout -out es256-private.pem
openssl ec -in es256-private.pem -pubout -out es256-public.pem

3684
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,26 +7,26 @@
"url": "git://git.blender.org/blender-cloud.git"
},
"devDependencies": {
"gulp": "~4.0",
"gulp-autoprefixer": "~6.0.0",
"gulp": "^4.0.0",
"gulp-autoprefixer": "~5.0.0",
"gulp-cached": "~1.1.1",
"gulp-chmod": "~2.0.0",
"gulp-concat": "~2.6.1",
"gulp-git": "~2.7.0",
"gulp-if": "^2.0.2",
"gulp-git": "~2.8.0",
"gulp-livereload": "~3.8.1",
"gulp-plumber": "~1.2.0",
"gulp-pug": "~4.0.1",
"gulp-rename": "~1.4.0",
"gulp-sass": "~4.1.0",
"gulp-rename": "~1.3.0",
"gulp-sass": "~4.0.1",
"gulp-sourcemaps": "~2.6.4",
"gulp-uglify-es": "^1.0.4",
"gulp-uglify": "~3.0.0",
"minimist": "^1.2.0"
},
"dependencies": {
"bootstrap": "^4.1.3",
"bootstrap": "^4.1.2",
"jquery": "^3.3.1",
"natives": "^1.1.6",
"popper.js": "^1.14.4",
"video.js": "^7.2.2"
"popper.js": "^1.14.3",
"photoswipe": "^4.1.2"
}
}

1917
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
[virtualenvs]
in-project = false

View File

@@ -1,75 +0,0 @@
[tool.poetry]
name = "blender-cloud"
version = "1.0"
description = ""
authors = [
"Francesco Siddi <francesco@blender.org>",
"Pablo Vazquez <pablo@blender.studio>",
"Sybren Stüvel <sybren@blender.studio>",
]
include = ["README.md", "LICENSE.txt"]
[tool.poetry.dependencies]
python = "~3.6"
pillar = {path = "../pillar"}
attract = {path = "../attract"}
flamenco = {path = "../flamenco"}
svnman = {path = "../pillar-svnman"}
amqp = "2.5.0"
asn1crypto = "0.24.0"
attrs = "19.1.0"
babel = "2.7.0"
bcrypt = "3.1.6"
billiard = "3.6.0.0"
bleach = "3.1.0"
celery = "4.3.0"
cerberus = "1.3.1"
certifi = "2019.3.9"
cffi = "1.12.3"
chardet = "3.0.4"
click = "7.0"
commonmark = "0.9.0"
cryptography = "2.7"
eve = "0.9.1"
fasteners = "0.15"
flask = "1.0.3"
flask-wtf = "0.14.2"
future = "0.17.1"
google-apitools = "0.5.28"
googleapis-common-protos = "1.6.0"
grpcio = "1.21.1"
httplib2 = "0.12.3"
ipaddress = "1.0.22"
jinja2 = "2.10.1"
kombu = "4.6.0"
protobuf = "3.8.0"
pyasn1 = "0.4.5"
pyasn1-modules = "0.2.5"
pycparser = "2.19"
pymongo = "3.8.0"
pyopenssl = "19.0.0"
pytz = "2019.1"
requests = "2.22.0"
rsa = "4.0"
setuptools = "51.0.0"
shortcodes = "2.5.0"
simplejson = "3.16.0"
six = "1.12.0"
wheel = "0.35.1"
wtforms = "2.2.1"
[tool.poetry.dev-dependencies]
pillar-devdeps = {path = "../pillar/devdeps"}
responses = "0.10.6"
zipp = "0.5.1"
py = "1.8.0"
colorama = "0.4.1"
importlib-metadata = "0.17"
more-itertools = "7.0.0"
coverage = "4.5.3"
pluggy = "0.12.0"
atomicwrites = "1.3.0"
[build-system]
requires = ["poetry==1.0","cryptography==2.7","setuptools==51.0.0","wheel==0.35.1"]
build-backend = "poetry.masonry.api"

11
requirements-dev.txt Normal file
View File

@@ -0,0 +1,11 @@
-r ../pillar-python-sdk/requirements-dev.txt
-r ../pillar/requirements-dev.txt
-r ../attract/requirements-dev.txt
-r ../flamenco/requirements-dev.txt
-r ../pillar-svnman/requirements-dev.txt
-e ../pillar-python-sdk
-e ../pillar
-e ../attract
-e ../flamenco
-e ../pillar-svnman

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
-r ../pillar/requirements.txt
-r ../attract/requirements.txt
-r ../flamenco/requirements.txt
-r ../pillar-svnman/requirements.txt

18
setup.py Normal file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python
"""Setup file for testing, not for packaging/distribution."""
import setuptools
setuptools.setup(
name='blender-cloud',
version='1.0',
packages=setuptools.find_packages('.', exclude=['tests']),
tests_require=[
'pytest>=2.9.1',
'responses>=0.5.1',
'pytest-cov>=2.2.1',
'mock>=2.0.0',
],
zip_safe=False,
)

42
src/scripts/layout.js Normal file
View File

@@ -0,0 +1,42 @@
/* Selectors
================================================================== */
var searchButton = document.querySelector('#searchButton'),
searchCloseButton = document.querySelector('#searchCloseButton'),
searchNav = document.querySelector('#searchNav'),
searchNavInput = document.querySelector('#searchNav input'),
navbarNav = document.querySelector('#navbarNav'),
profileMenu = document.querySelector('#navbarNav .navbar-nav').lastElementChild
/* Interactions
================================================================== */
searchButton.addEventListener('click', showSearch)
searchCloseButton.addEventListener('click', closeSearch)
searchNavInput.onblur = function() {
closeSearch()
}
/* Functions
================================================================== */
function showSearch() {
searchToggleStatus = true
searchNav.classList.remove('hidden')
searchNavInput.focus()
navbarNav.classList.add('hidden')
profileMenu.classList.add('visible')
document.addEventListener('keyup', onPressEscCloseSearch)
}
function closeSearch() {
searchNav.classList.add('hidden')
navbarNav.classList.remove('hidden')
profileMenu.classList.remove('visible')
document.removeEventListener('keyup', onPressEscCloseSearch)
}
function onPressEscCloseSearch(e) {
if (e.keyCode == 27) {
closeSearch()
}
}

View File

@@ -1,29 +0,0 @@
/**
* Support for fetching & rendering assets by tags.
*/
(function($) {
$.fn.loadTaggedAssets = function(load_initial_count, load_next_count, has_subscription) {
mark_if_public = !has_subscription;
this.each(function(index, each) {
let $card_deck_element = $(each)
$card_deck_element.trigger('pillar:workStart');
$.get('/api/nodes/tagged/' + $card_deck_element.data('assetTag'))
.fail(function(error) {
let msg = xhrErrorResponseMessage(error);
$card_deck_element
.append(
$('<p>').addClass('bg-danger').text(msg)
);
})
.done(function(resp) {
// 'resp' is a list of node documents.
$card_deck_element.append(
pillar.templates.Nodes.createListOf$nodeItems(resp, load_initial_count, load_next_count)
);
})
.always(function() {
$card_deck_element.trigger('pillar:workStop');
});
});
};
}(jQuery));

View File

@@ -0,0 +1,8 @@
// Bootstrap Breakpoints
// sm 576.98px
// md 576px - 757.98px
// lg 992px to 1199.98px
// xl 1200px and up
@import ../../static/assets/bootstrap/sass/functions
@import ../../static/assets/bootstrap/sass/variables
@import ../../static/assets/bootstrap/sass/mixins/breakpoints

36
src/styles/_cards.sass Normal file
View File

@@ -0,0 +1,36 @@
.container.card-container
max-width: 1200px
margin-top: 56px
.col-md-4
padding: 0 20px
&:focus
outline: none;
.card
border: none
margin-bottom: 64px
.card-image
background-color: #ccc
border-radius: 0
height: 160px
max-height: 160px
min-height: 160px
margin: 0 auto
overflow: hidden
width: 100%
+media-breakpoint-up(sm)
height: 203px
max-height: 203px
min-height: 203px
img
height: 100%
object-fit: cover
.card-title
margin: 11px 0 5px
font-size: 27px
a
color: #111
text-decoration: none
.card-text
font-size: 18px
.card-body
padding: 0

9
src/styles/_colors.sass Normal file
View File

@@ -0,0 +1,9 @@
$primary: #0A68FF
$grey: #EBEBEB
$gray: #EBEBEB
$dark: rgba(17,17,17,1)
$muted: rgba(17,17,17,0.75)
$lightgray: #F5F5F5
// $theme-colors:
// primary: red
//

54
src/styles/_footer.sass Normal file
View File

@@ -0,0 +1,54 @@
@import ../styles/colors
@import ../styles/breakpoints
footer.container-fluid
background: #F8F8FB
border-top: 1px solid #E3E3E6
.container
margin-top: 11px
max-width: 1200px
.row
max-width: 1200px
.col-4
flex: 0 0 100%
flex-basis: 100%
max-width: 100%
margin-bottom: 60px
padding: 0 20px 56px
+media-breakpoint-up(sm)
padding: 0 20px 32px
+media-breakpoint-up(md)
flex: 0 0 33%
padding-bottom: 0
max-width: 33%
margin-bottom: 0px
.col
flex-basis: 50%
padding: 0 20px 32px
+media-breakpoint-up(sm)
flex-basis: 0
padding-bottom: 0
h5
color: $dark
font-weight: 600
margin-bottom: 12px
text-transform: uppercase
p
color: #6d6d6e
font-size: 16px
line-height: 30px
.list-unstyled
li
line-height: 30px
a
color: #6d6d6e
font-size: 16px
&:hover
color: #3F3F40
.social-icons
.list-inline-item:not(:last-child)
margin-right: 22px
a
opacity: 1
&:hover
opacity: 0.8

56
src/styles/_gallery.sass Normal file
View File

@@ -0,0 +1,56 @@
@import ../styles/colors
@import ../styles/breakpoints
.gallery
.cta-arrow
margin-top: 20px
.container
max-width: 1190px
padding: 0
+media-breakpoint-up(sm)
padding-right: 15px
padding-left: 15px
.thumbnail
float: left
position: relative
width: 32.8%
padding-bottom: 32.9%
margin: 0.4%
overflow: hidden
// Fixes Thumbnail Spacing
&:nth-of-type(1),
&:nth-of-type(2),
&:nth-of-type(3)
margin-top: 0
&:nth-of-type(1),
&:nth-of-type(4),
&:nth-of-type(7)
margin-left: 0
&:nth-of-type(3),
&:nth-of-type(6)
margin-right: 0
+media-breakpoint-up(sm)
width: 22.4%
padding-bottom: 22.4%
margin: 1.73%
&:nth-of-type(4)
margin-top: 0
&:nth-of-type(1),
&:nth-of-type(5)
margin-left: 0
&:nth-of-type(4),
&:nth-of-type(7)
margin-left: 1.73%
&:nth-of-type(3),
&:nth-of-type(6)
margin-right: 1.73%
&:nth-of-type(4),
&:nth-of-type(8)
margin-right: 0
.thumbnail-container
position: absolute
width: 100%
height: 100%
img
width: 300%
transform: translate(-20%, -10%)

View File

@@ -1,60 +1,752 @@
.random-featured
// Hide irrelevant info from cards.
li
&.item-type,
&.item-date
@extend .d-none
.dashboard-container
+container-behavior
+media-xs
flex-direction: column
align-content: center
align-items: flex-start
display: flex
justify-content: space-around
word-break: break-word
// Fit 3 cards per row.
&.card-deck.card-deck-responsive
.card
+media-md
flex: 1 0 30%
max-width: 30%
flex: 1 0 31%
max-width: 31%
section.dashboard-main,
section.dashboard-secondary
+media-xs
width: 100%
margin: 20px auto
.homepage
.timeline
.card-text,
.card-title
margin-bottom: 0 !important
padding: 0 !important
img
max-width: 100%
section.dashboard-main
+container-box
width: 52%
section.dashboard-secondary
width: 46%
flex-direction: column
margin-right: auto
span.section-lead
display: block
padding: 10px 0
color: $color-text-dark-secondary
section.dashboard-main,
section.dashboard-secondary
h4
padding-bottom: 5px
margin-bottom: 20px
position: relative
&:before
position: absolute
width: 50px
height: 2px
top: 125%
content: ' '
display: block
background-color: $color-primary
// Hide project name, it's already in the timeline.
a
display: none
color: $color-text
// On blog posts, center text and title.
.h1.text-uppercase
+media-md
text-align: center
+media-xl
text-align: left
&:hover
color: $color-primary
cursor: pointer
.node-details-description
+media-md
margin-left: auto
margin-right: auto
+media-xl
margin-left: 0
margin-right: auto
nav#nav-tabs,
nav#sub-nav-tabs
ul#nav-tabs__list,
ul#sub-nav-tabs__list
margin: 0
padding: 0
list-style: none
border-bottom: thin solid $color-background
+clearfix
.featured-projects
+media-sm
padding-left: $spacer
padding-right: $spacer
li.nav-tabs__list-tab
float: left
border: none
border-bottom: 3px solid transparent
color: $color-text-dark-primary
user-select: none
.featured-project-card
+media-xl
.card-thumbnail
height: 100%
border-radius: $border-radius
&:hover
border-color: rgba($color-secondary, .3)
cursor: pointer
color: $color-text-dark
a
color: $color-text-dark
.card-body
padding-left: 15px !important
padding-top: 0 !important
a
display: block
text-decoration: none
padding: 10px 15px 5px
color: $color-text-dark-primary
.homepage
.title-underline
padding-bottom: 2px
i
margin-right: 5px
color: $color-text-dark-secondary
font-size: .9em
&.pi-blender
margin-right: 10px
span
color: $color-text-dark-hint
margin-left: 5px
&.active
border-color: $color-secondary
color: $color-secondary-dark
a, i
color: $color-secondary-dark
&.disabled
border-color: $color-background-light
color: $color-text-dark-hint
cursor: default
a, i
color: $color-text-dark-hint
&:hover
border-color: $color-background-light
pointer-events: none
li.create
cursor: pointer
display: inline-block
float: right
font:
size: 1.2em
weight: 400
padding: 5px 10px
margin-top: 3px
a
color: $color-success
text-decoration: none
&.disabled
cursor: wait
border-color: $color-success
opacity: .8
a
cursor: wait
section.stream
background-color: white
border-bottom: thin solid $color-background-dark
ul.activity-stream__list
list-style: none
margin: 0
padding: 0
$activity-stream-thumbnail-size: 110px
> li
position: relative
display: flex
padding: 10px 0
overflow: hidden
border-top: thin solid $color-background-dark
&:first-child
border: none
&.active .activity-stream__list-details .title
color: $color-primary
&:hover
.title
text-decoration: underline
&.video
a.image
&:hover
i
font-size: 3.5em
img
opacity: .9
img
opacity: .7
z-index: 0
transition: opacity 150ms ease-in-out
i
+position-center-translate
z-index: 1
color: rgba(white, .6)
font-size: 3em
transition: font-size 100ms ease-in-out
&.comment
.activity-stream__list-thumbnail
background: transparent
color: $node-type-comment
font-size: 1.2em
box-shadow: none
i
+position-center-translate
left: 22px
top: 19px
.activity-stream__list-details
padding: 0
.title
color: $color-text-dark
padding: 7px 10px 2px 10px
font-size: 1em
margin: 0
ul.meta
padding: 0 10px 7px 10px
li
&.where-parent:before
content: '\e83a'
font-family: 'pillar-font'
&.what:before
display: none
&.post
.activity-stream__list-thumbnail
border-color: $node-type-post
background-color: $node-type-post
.activity-stream__list-details .title
color: darken($node-type-post, 15%)
font:
size: 1.3em
weight: 500
&.asset, &.comment, &.post
&:hover
cursor: pointer
&.empty
display: none
color: $color-text-dark-primary
padding: 20px
text-align: center
span
color: $color-primary
&:hover
text-decoration: underline
cursor: pointer
&.with-picture
min-height: $activity-stream-thumbnail-size
.activity-stream__list-thumbnail
background-color: black
width: $activity-stream-thumbnail-size * 1.69
min-width: $activity-stream-thumbnail-size * 1.69
.activity-stream__list-thumbnail-icon
position: absolute
top: 0
left: 0
right: 0
bottom: 0
font-size: 1.3em
text-shadow: 1px 1px 0 rgba(black, .2)
background-image: linear-gradient(10deg, rgba(black, .5) 0%, transparent 40%)
i
position: absolute
bottom: -8px
left: 20px
top: initial
right: initial
color: white
.activity-stream__list-thumbnail
position: relative
display: flex
justify-content: center
align-items: center
overflow: hidden
width: 35px
height: auto
min-width: 35px
min-height: auto
+media-xs
display: none
&.image i
color: $node-type-asset_image
&.file i
color: $node-type-asset_file
&.video i
color: $node-type-asset_video
i
+position-center-translate
left: 23px
top: 21px
font-size: 1.1em
img
max-height: $activity-stream-thumbnail-size
+position-center-translate
.activity-stream__list-details
display: flex
flex-direction: column
justify-content: space-around
flex: 1
overflow: hidden
position: relative
max-width: 100%
margin-right: auto
padding: 10px 0
+media-xs
margin-left: 0
.ribbon
+ribbon
right: -47px
top: 5px
font:
size: 12px
weight: 500
span
padding: 1px 50px
.title
display: inline-block
padding: 0 10px
color: $color-text-dark
font-size: 1.1em
span
@include badge(hsl(hue($color-success), 60%, 45%), 3px)
font-size: .7em
padding: 1px 5px
margin-right: 5px
ul.meta
+list-meta
padding: 5px 10px 0 10px
font-size: .85em
color: $color-text-dark-secondary
display: flex
white-space: nowrap
&.extra
margin-top: auto
li
padding-left: 10px
&:before
left: -5px
&.where-project
+text-overflow-ellipsis
section.comments
padding: 0 15px 5px
ul
padding: 0
> ul
list-style-type: none
margin: 10px 0 0
> li
+text-overflow-ellipsis
border-top: thin solid $color-background-dark
padding: 10px 0
&:first-child
border: none
> a
+text-overflow-ellipsis
color: $color-text
display: block
padding-bottom: 5px
section.blog-stream
+media-md
padding-left: 10px
+media-sm
padding-left: 10px
position: relative
.feed
position: absolute
top: 10px
right: 10px
font-size: 1.4em
color: lighten($color-text-dark-hint, 10%)
&:hover
color: $color-primary
> ul
margin: 0
padding: 0
list-style: none
border-top: thin solid $color-background
.blog_index-item
+container-box
display: flex
flex-direction: column
margin-bottom: 50px
&:before
height: 1px
background-color: $color-background-dark
position: absolute
bottom: -26px
left: 25px
right: 25px
content: ' '
&:last-child
margin-bottom: 0
&:before
display: none
video
max-width: 100%
a.item-title
font-size: 1.6em
padding: 5px 15px
display: block
color: $color-text
&:hover
color: $color-primary
ul.meta
+list-meta
font-size: .9em
padding: 15px 15px 5px
&.blog-non-featured
border-radius: 0
margin: 0
.item-content
+node-details-description
padding: 10px 15px
.blog-stream__list-details
.title
color: $color-text-dark-primary
display: block
font-size: 1.3em
&:hover
color: $color-primary
ul.meta
+list-meta
padding-top: 5px
font-size: .9em
color: $color-text-dark-secondary
li
padding-left: 10px
&:before
left: -5px
.blog_index-header
display: block
position: relative
img
border-top-left-radius: 3px
border-top-right-radius: 3px
width: 100%
.more
text-align: center
a
color: $color-text
display: block
padding: 25px 0
text-decoration: underline
width: 100%
&:hover
color: $color-primary
section.random-asset
border-bottom: thin solid $color-background-dark
ul.random-asset__list
list-style: none
padding: 0
> li
align-items: center
border-top: thin solid $color-background
display: flex
padding: 7px 0
position: relative
overflow: hidden
&:first-child
border-top: none
.ribbon
+ribbon
right: -47px
top: 5px
font:
size: 12px
weight: 500
z-index: 1
span
padding: 1px 50px
.random-asset__list-thumbnail
background-color: $color-background
display: block
height: 50px
margin-right: 15px
min-height: 50px
min-width: 50px
overflow: hidden
position: relative
width: 50px
img
width: 100%
i
+position-center-translate
font-size: 1.6em
color: $color-text-light
&.image
background-color: $node-type-asset_image
&.file
background-color: $node-type-asset_file
font-size: .8em
&.video
background-color: $node-type-asset_video
font-size: .8em
&.None
background-color: $node-type-group
.random-asset__list-details
.title
display: block
font-size: 1em
color: $color-text-dark-primary
&:hover
color: $color-primary
ul.meta
+list-meta
padding-top: 5px
font-size: .9em
li
&:before
left: -5px
&.what
text-transform: capitalize
&.featured
align-items: flex-start
flex-direction: column
padding: 0
a.title
font-size: 1.1em
padding: 10px 0 5px
display: block
color: $color-text
&:hover
color: $color-primary
a.random-asset__thumbnail
display: block
position: relative
&.video
background-color: black
img
opacity: .7
img
transition: opacity 150ms ease-in-out
width: 100%
max-width: 100%
i
+position-center-translate
color: white
font-size: 3em
text-shadow: 0 0 25px black
transition: font-size 150ms ease-in-out
&:hover
i
font-size: 3.5em
img
opacity: .85
ul.meta
+list-meta
padding-bottom: 10px
section.announcement
+container-box
margin-left: 15px
margin-right: 15px
.header-icons
display: flex
align-items: center
justify-content: center
padding: 20px 0 5px 0
i
font-size: 2.5em
color: $color-info
&.pi-heart-filled
color: $color-danger
margin-left: 5px
img.header
width: 100%
margin: 0 auto
border-top-left-radius: 3px
border-top-right-radius: 3px
iframe
width: 100%
position: relative
left: 15px
margin: 25px auto
+media-sm
height: 500px
+media-md
height: 520px
+media-lg
height: 580px
.text
padding: 15px
.title
padding-bottom: 10px
font:
family: $font-body
size: 1.4em
weight: 300
+media-xs
font-size: 1.4em
strong
color: $color-primary-dark
a
color: $color-text-dark-primary
.lead
font-size: 1em
+list-bullets
ul
margin-top: 10px
padding-left: 10px
hr
border: none
height: 1px
width: 100%
margin: 10px 0
background-color: $color-background
clear: both
+media-xs
padding-left: 10px
.buttons
margin: 15px auto 0 auto
display: flex
align-items: center
justify-content: space-around
flex-wrap: wrap
a
+button($color-text-light, 3px)
padding: 5px 0
margin:
bottom: 5px
right: auto
left: auto
font-size: .9em
opacity: 1
flex: 1
+media-xs
margin: 10px auto
width: 100%
&:first-child
margin-right: 15px
&.blue
+button(hsl(hue($color-info), 60%, 45%), 3px)
&.orange
+button(hsl(hue($color-secondary), 50%, 50%), 3px)
padding: 5px 15px
&.green
+button(hsl(hue($color-success), 60%, 40%), 3px, true)
section.dashboard-in-production
.in-production-project
border-bottom: thin solid $color-background-dark
color: $color-text-dark-primary
display: block
font-size: 1.1em
margin-bottom: 15px
> img
margin-bottom: 15px
body.homepage
.dashboard-container
.dashboard-main
+media-xs
width: 100%
background-color: transparent
box-shadow: none
width: 60%
.dashboard-secondary
+container-box
+media-xs
width: 100%
width: 38%
> section
padding: 15px

2
src/styles/_iframe.sass Normal file
View File

@@ -0,0 +1,2 @@
.embed-responsive
margin: 48px auto

View File

@@ -0,0 +1,32 @@
@import ../styles/colors
@import ../styles/breakpoints
.jumbotron
background-color: #fff
background-repeat: no-repeat
background-position: left top
background-size: cover
height: 360px
width: 100%
margin-bottom: 0px
+media-breakpoint-up(md)
height: 500px
+media-breakpoint-up(xl)
height: 600px
.container
max-width: 1160px
display: flex
height: 100%
flex-direction: column
justify-content: center
h1.display-4
color: white
font-weight: 400
font-size: 48px
margin: 0
text-shadow: 0 2px 2px rgba(0, 0, 0, 0.30)
.lead
max-width: 722px
text-shadow: 0 2px 2px rgba(0, 0, 0, 0.80)
p
color: white

View File

@@ -1,37 +0,0 @@
body.films
.page-content
@extend .text-white
background-color: $color-bg-dark-pages
h1
@extend .text-white
hr
background-color: lighten($color-bg-dark-pages, 20%)
.films-item
animation: fade-in-down .33s ease-out
animation-fill-mode: both
img
box-shadow: 0 10px 25px rgba($black, .5)
transition: box-shadow 800ms ease-in-out, transform 200ms ease-in-out
&:hover
box-shadow: 0 0 25px rgba($primary, .1), 0 0 50px rgba(white, .1)
transform: scale(1.05)
@for $i from 1 through 20
.films-item:nth-child(#{$i}n)
animation-delay: #{$i * 0.125}s
@keyframes fade-in-down
0%
opacity: 0
transform: translateY(-25px)
80%
opacity: 1
100%
transform: translateY(0)

View File

@@ -0,0 +1,41 @@
@import ../styles/colors
@import ../styles/breakpoints
.nav-scroller
position: relative
z-index: 2
height: 2.75rem
overflow-y: hidden
.navbar-secondary
max-width: 700px
margin-bottom: 40px
padding-left: 0
border-bottom: 1px solid $gray
+media-breakpoint-up(sm)
margin-bottom: 56px
.nav
display: flex
flex-wrap: nowrap
overflow-x: auto
text-align: center
white-space: nowrap
-webkit-overflow-scrolling: touch
.nav-link
font-size: 18px
color: $dark
padding: 13px 4px
margin-right: 16px
margin-left: 16px
&:hover
color: $muted
.nav-title
font-weight: 600
@include media-breakpoint-up(md)
padding-left: 0
margin-left: 0
&:hover
color: $dark
.active
border-bottom: 2px solid $primary
&:hover
color: $dark

220
src/styles/_navbar.sass Normal file
View File

@@ -0,0 +1,220 @@
@import ../styles/colors
@import ../styles/breakpoints
nav.navbar
background: #fff
height: auto
min-height: 48px
padding: 2px 20px
position: sticky
+media-breakpoint-up(md)
box-shadow: 0 2px 2px -2px rgba(0,0,0,.15)
height: 56px
padding: 10px 5px 10px 20px
&.sticky-top
+media-breakpoint-down(sm)
position: static
.navbar-brand
padding: .3125rem 0
z-index: 1002
.navbar-toggler
padding: 0
border-radius: 0
&:hover
background: none
.navbar-nav
margin-bottom: 15px
margin-top: 15px
+media-breakpoint-up(md)
margin: 0
li
line-height: 2.5
.special
top: 10px
left: 35px
a.navbar-item.active,
a.navbar-item.active:hover,
a.navbar-item:hover
box-shadow: none
li:last-of-type
// profile menu
z-index: 1002
.dropdown-menu
right: 0
left: auto
top: 54px
padding: .5rem 0
margin: .125rem 0 0
&.visible
visibility: visible
li a
padding: 0 15px 0 12px
li:hover
background-color: #f8f9fa
.dropdown ul.dropdown-menu li a
color: #111
font-family: 'Source Sans Pro', sans-serif
&:hover
color: #111
text-decoration: none
.nav-item .nav-link
font-size: 18px
color: $dark
padding: 10px
+media-breakpoint-up(lg)
padding: 6px 18px
&:hover
color: $primary
.navbar-toggler
border: 0
display: block
outline: none
+media-breakpoint-up(md)
display: none
span
width: 20px
height: 2px
background: $dark
display: block
margin-bottom: 4px
&:last-of-type
margin-bottom: 0
.collapsing
transition: none
display: none
#navbarNav
animation-timing-function: cubic-bezier(0.4,0,0.2,1)
animation-fill-mode: backwards
opacity: 1
transition: opacity .15s ease-in
visibility: visible
&.hidden
animation-fill-mode: backwards
animation-timing-function: cubic-bezier(0.4,0,0.2,1)
opacity: 0
transition: visibility 0s .15s, opacity .15s ease-in
visibility: hidden
.dropdown-toggle::after
display:none !important
&.show
padding-bottom: 75vh
.search-icon
display: flex
align-items: center
justify-content: center
padding: 0 8px
+media-breakpoint-up(lg)
padding: 0 16px
.pi-search
&::before
color: rgba(17,17,17,0.75)
font-size: 16px
&:hover
color: $primary
.nav-notifications
display: flex
align-items: center
justify-content: center
padding: 0 8px
+media-breakpoint-up(lg)
padding: 0 16px
.navbar-item
height: auto
padding: 0
.active
box-shadow: 0
.flyout
background-color: #fff
background-clip: padding-box
border: 1px solid rgba(0,0,0,.15)
border-radius: .25rem
box-shadow: 0
font-size: 16px
.flyout.notifications
right: 65px
top: 57px
.nav-notifications:hover>#notifications-toggle
display: block
.nav-notifications-icon
color: rgba(17,17,17,0.75)
&:hover
color: $primary
.gravatar
width: 32px
border-radius: 50%
.dropdown-menu
margin-top: -1px
border: 0
border-radius: 0 0 4px 4px
box-shadow: none
font-size: 1rem
color: #111
text-align: left
list-style: none
background-color: #fff
background-clip: padding-box
box-shadow: 0px 4px 4px 0px hsla(0, 0%, 80%, 0.25)
.dropdown:hover
background: none
&>.dropdown-menu
display: block
.dropdown-item
color: #111
font-size: 18px
padding: 0px 15px
i
padding-right: 8px
#userDropdown
.nav-link
padding-top: 4px
padding-bottom: 0
padding-right: 0
outline: none
#searchNav
display: flex
align-items: center
justify-content: center
background-color: #fff
max-height: 56px
position: absolute
width: calc(100% - 40px)
z-index: 1001
animation-timing-function: cubic-bezier(0.4,0,0.2,1)
animation-fill-mode: backwards
opacity: 1
transition: opacity .15s ease-in
visibility: visible
+media-breakpoint-up(md)
width: calc(100% - 25px)
&.hidden
animation-fill-mode: backwards
animation-timing-function: cubic-bezier(0.4,0,0.2,1)
opacity: 0
transition: visibility 0s .15s, opacity .15s ease-in
visibility: hidden
.search-container
position: relative
max-width: 560px
width: 100%
input
padding: 11px 20px 11px 46px
color: #4d4e53
box-shadow: none
border: 1px solid #e5e5e5
border-radius: 4px
border-bottom-color: none
background-color: #fcfcfc
font-size: 18px
transition: border-color 150ms ease-in-out, box-shadow 150ms ease-in-out
width: 100%
max-width: 560px
.search-icon
cursor: pointer
position: absolute
top: 14px
left: 11px
#searchCloseButton
cursor: pointer
position: absolute
right: 16px
top: 12px

View File

@@ -1,85 +0,0 @@
.landing-home
.page-content
@extend .text-white
background-color: $color-bg-dark-pages
.jumbotron
+media-xs
background-position: top right
background-position: top center
padding-bottom: 16em
padding-top: 8em
.card
@extend .bg-transparent
@extend .text-white
.card-text
@extend .text-secondary
.node-details-description
@extend .mx-auto
color: #ddd
font-size: 1.3em
a
color: $color-primary
.btn-outline-primary
border-color: $color-text-light
color: $color-text-light
&:hover
border-color: transparent
.gallery
max-width: 1024px
.thumbnail
float: left
position: relative
width: 23%
padding-bottom: 23%
margin: 0.83%
overflow: hidden
transition: box-shadow 150ms ease-in-out
&:hover
box-shadow: 2px 6px 50px 0 rgba(black, .2)
.img-container
position: absolute
width: 100%
height: 100%
img
width: 300%
transform: translate(-20%,-10%)
@media screen and (max-width: 992px)
.thumbnail
width: 22%
padding-bottom: 22%
margin: 1.5%
@media screen and (max-width: 720px)
.thumbnail
width: 29%
padding-bottom: 29%
margin: 2.16%
@media screen and (max-width: 470px)
.thumbnail
width: 44%
padding-bottom: 44%
margin: 3%
.jumbotron
&.jumbotron-overlay-gradient-fade-to-gray
*
z-index: 1
&:after
background-color: transparent
background-image: linear-gradient(transparent 60%, $color-bg-dark-pages 100%)
display: block
visibility: visible

View File

@@ -0,0 +1,43 @@
html
// changing font-size here, will change all bootstrap font-sizes
font-size: 20px
body
font-family: 'Source Sans Pro', sans-serif
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
color: #212529
h1, h2, h3, h4, h5
font-family: 'Source Sans Pro', sans-serif
margin-top: 0
margin-bottom: .5rem
h1
font-size: 48px
line-height: 57px
font-weight: 500
h2
font-size: 36px
line-height: 47px
font-weight: 500
h3
font-size: 27px
line-height: 38px
font-weight: 500
h4
font-size 21px
font-weight: 500
line-height: 34px
h5
font-size: 15px
letter-spacing: 0.6px
p
font-size: 20px
line-height: 1.5
margin-bottom: 1rem
.lead
font-size: 20px
letter-spacing: 0.4px
a
color: #007bff
&:hover
color: #007bff
text-decoration: underline

View File

@@ -1,4 +0,0 @@
.list-first-new
li:first-child
span
@extend .new

View File

@@ -1,5 +0,0 @@
$color-bg-dark-pages: #151515
// Alias for Blender Cloud logo used in project edit.
.pi-cloud
@extend .pi-blender-cloud

View File

@@ -10,6 +10,9 @@
li a.navbar-item
color: $color-text
.navbar-container
+container-behavior
.navbar-toggle
border: 2px solid $color-text-dark-primary
color: $color-text
@@ -264,6 +267,22 @@
+media-xs
margin-top: 20px
.navbar
.nav-item-sign-in
a.navbar-item
background-color: $color-primary
border: none
border-radius: 3px
color: white
height: auto
font-weight: bold
margin-top: 5px
margin-left: 10px
padding: 10px 20px
&:hover
background-color: lighten($color-primary, 10%)
box-shadow: none
.container.wide-on-sm
+media-sm
@@ -312,10 +331,12 @@ section.pricing
transform: scale(1)
a.sign-up-now
+button($color-primary, $btn-border-radius, true)
+button($color-primary, 3px, true)
h3
font-size: 1.8em
font:
size: 1.8em
family: $font-body
padding-bottom: 0
margin: 25px 0 0 10px
@@ -361,7 +382,7 @@ section.pricing
transform: translateX(-50%)
font-size: 1.2em
+button($color-primary, $btn-border-radius)
+button($color-primary, 3px)
padding: 5px 25px
white-space: nowrap
text-align: center

17
src/styles/base.sass Normal file
View File

@@ -0,0 +1,17 @@
/* mixins reused on every page
================================================================== */
@import ../styles/typography
@import ../styles/navbar
@import ../styles/footer
/* Undo Old Base.css, main.css
================================================================== */
.container
width: 100%
padding-right: 15px
padding-left: 15px
margin-right: auto
margin-left: auto
.navbar+.page-content
padding-top: 0

View File

@@ -1,108 +1,26 @@
// Bootstrap variables and utilities.
@import "../../../pillar/node_modules/bootstrap/scss/functions"
@import "../../../pillar/node_modules/bootstrap/scss/variables"
@import "../../../pillar/node_modules/bootstrap/scss/mixins"
@import ../../../pillar/src/styles/_normalize
@import ../../../pillar/src/styles/_config
@import ../../../pillar/src/styles/_utils
// Pillar variables and utilities.
@import "../../../pillar/src/styles/config"
@import "../../../pillar/src/styles/utils"
$pillar-font-path: "../../../../static/pillar/assets/font"
@import "../../../pillar/src/styles/font-pillar"
// Bootstrap components.
@import "../../../pillar/node_modules/bootstrap/scss/root"
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
@import "../../../pillar/node_modules/bootstrap/scss/type"
@import "../../../pillar/node_modules/bootstrap/scss/images"
@import "../../../pillar/node_modules/bootstrap/scss/code"
@import "../../../pillar/node_modules/bootstrap/scss/grid"
@import "../../../pillar/node_modules/bootstrap/scss/tables"
@import "../../../pillar/node_modules/bootstrap/scss/forms"
@import "../../../pillar/node_modules/bootstrap/scss/buttons"
@import "../../../pillar/node_modules/bootstrap/scss/transitions"
@import "../../../pillar/node_modules/bootstrap/scss/dropdown"
@import "../../../pillar/node_modules/bootstrap/scss/button-group"
@import "../../../pillar/node_modules/bootstrap/scss/input-group"
@import "../../../pillar/node_modules/bootstrap/scss/custom-forms"
@import "../../../pillar/node_modules/bootstrap/scss/nav"
@import "../../../pillar/node_modules/bootstrap/scss/navbar"
@import "../../../pillar/node_modules/bootstrap/scss/card"
@import "../../../pillar/node_modules/bootstrap/scss/breadcrumb"
@import "../../../pillar/node_modules/bootstrap/scss/pagination"
@import "../../../pillar/node_modules/bootstrap/scss/badge"
@import "../../../pillar/node_modules/bootstrap/scss/jumbotron"
@import "../../../pillar/node_modules/bootstrap/scss/alert"
@import "../../../pillar/node_modules/bootstrap/scss/progress"
@import "../../../pillar/node_modules/bootstrap/scss/media"
@import "../../../pillar/node_modules/bootstrap/scss/list-group"
@import "../../../pillar/node_modules/bootstrap/scss/close"
@import "../../../pillar/node_modules/bootstrap/scss/modal"
@import "../../../pillar/node_modules/bootstrap/scss/tooltip"
@import "../../../pillar/node_modules/bootstrap/scss/popover"
@import "../../../pillar/node_modules/bootstrap/scss/carousel"
@import "../../../pillar/node_modules/bootstrap/scss/utilities"
@import "../../../pillar/node_modules/bootstrap/scss/print"
// Pillar components.
@import "../../../pillar/src/styles/apps_base"
@import "../../../pillar/src/styles/error"
@import "../../../pillar/src/styles/components/base"
@import "../../../pillar/src/styles/components/jumbotron"
@import "../../../pillar/src/styles/components/alerts"
@import "../../../pillar/src/styles/components/navbar"
@import "../../../pillar/src/styles/components/dropdown"
@import "../../../pillar/src/styles/components/footer"
@import "../../../pillar/src/styles/components/shortcode"
@import "../../../pillar/src/styles/components/statusbar"
@import "../../../pillar/src/styles/components/search"
@import "../../../pillar/src/styles/components/flyout"
@import "../../../pillar/src/styles/components/forms"
@import "../../../pillar/src/styles/components/inputs"
@import "../../../pillar/src/styles/components/buttons"
@import "../../../pillar/src/styles/components/popover"
@import "../../../pillar/src/styles/components/tooltip"
@import "../../../pillar/src/styles/components/checkbox"
@import "../../../pillar/src/styles/components/overlay"
@import "../../../pillar/src/styles/components/card"
@import "../../../pillar/src/styles/components/placeholder"
@import "../../../pillar/src/styles/components/timeline"
@import "../../../pillar/src/styles/comments"
@import "../../../pillar/src/styles/notifications"
/* Generic styles (comments, notifications, etc) come from base.css */
/* Blender Cloud specific styles */
@import "../../../pillar/src/styles/_project"
@import "../../../pillar/src/styles/_project-sharing"
@import "../../../pillar/src/styles/_project-dashboard"
@import "../../../pillar/src/styles/_user"
@import "../../../pillar/src/styles/_search"
@import "../../../pillar/src/styles/_organizations"
@import ../../../pillar/src/styles/_project
@import ../../../pillar/src/styles/_project-sharing
@import ../../../pillar/src/styles/_project-dashboard
@import ../../../pillar/src/styles/_user
@import _welcome
@import _homepage
@import _services
@import _about
@import ../../../pillar/src/styles/_search
@import ../../../pillar/src/styles/_organizations
/* services, about, etc */
@import "../../../pillar/src/styles/_pages"
@import ../../../pillar/src/styles/_pages
/* plugins are included here, don't include in base unless needed by other pillar apps */
@import "../../../pillar/src/styles/plugins/_jstree"
@import "../../../pillar/src/styles/plugins/_js_select2"
@import ../../../pillar/src/styles/plugins/_jstree
@import ../../../pillar/src/styles/plugins/_js_select2
/* CSS for pillar-font comes from fontello.com using static/assets/font/config.json */
@import variables
@import utils
@import welcome
@import services
@import about
@import homepage
@import list_films

View File

@@ -0,0 +1,60 @@
@import ../styles/colors
@import ../styles/breakpoints
@import ../styles/jumbotron
@import ../styles/navbar-secondary
@import ../styles/iframe
@import ../styles/gallery
@import ../styles/cards
/* Undo main.css padding
================================================================== */
body,
.container-page
background: #fff
section.node-details-container.project
padding-bottom: 0
.node-details-title
padding-top: 0
padding-bottom: 0
/* Override jumbotron to make it shorter than the default */
.jumbotron
height: 400px
/* Landing Page Sass
================================================================== */
.cta-arrow
img
padding-left: 11px
.node-details-title
&.container
max-width: 1190px
h1,
h2,
h3,
ul,
p
max-width: 700px
margin-left: auto
margin-right: auto
section
margin-top: 104px
+media-breakpoint-up(sm)
margin-top: 152px
&:first-child
margin-top: 0
h2
margin-bottom: 24px
+media-breakpoint-up(sm)
margin-bottom: 40px
.container.card-container
margin-top: 0
.card
.card-image
margin-top: 0
footer.container-fluid
margin-top: 72px
+media-breakpoint-up(sm)
margin-top: 112px
+media-breakpoint-up(lg)
margin-top: 136px

View File

@@ -1,93 +0,0 @@
// Bootstrap variables and utilities.
@import "../../../pillar/node_modules/bootstrap/scss/functions"
@import "../../../pillar/node_modules/bootstrap/scss/variables"
@import "../../../pillar/node_modules/bootstrap/scss/mixins"
// Pillar variables and utilities.
@import "../../../pillar/src/styles/_config"
@import "../../../pillar/src/styles/_utils"
$pillar-font-path: "../../../../static/pillar/assets/font"
@import "../../../pillar/src/styles/font-pillar"
// Bootstrap components.
@import "../../../pillar/node_modules/bootstrap/scss/root"
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
@import "../../../pillar/node_modules/bootstrap/scss/type"
@import "../../../pillar/node_modules/bootstrap/scss/images"
@import "../../../pillar/node_modules/bootstrap/scss/code"
@import "../../../pillar/node_modules/bootstrap/scss/grid"
@import "../../../pillar/node_modules/bootstrap/scss/tables"
@import "../../../pillar/node_modules/bootstrap/scss/forms"
@import "../../../pillar/node_modules/bootstrap/scss/buttons"
@import "../../../pillar/node_modules/bootstrap/scss/transitions"
@import "../../../pillar/node_modules/bootstrap/scss/dropdown"
@import "../../../pillar/node_modules/bootstrap/scss/button-group"
@import "../../../pillar/node_modules/bootstrap/scss/input-group"
@import "../../../pillar/node_modules/bootstrap/scss/custom-forms"
@import "../../../pillar/node_modules/bootstrap/scss/nav"
@import "../../../pillar/node_modules/bootstrap/scss/navbar"
@import "../../../pillar/node_modules/bootstrap/scss/card"
@import "../../../pillar/node_modules/bootstrap/scss/breadcrumb"
@import "../../../pillar/node_modules/bootstrap/scss/pagination"
@import "../../../pillar/node_modules/bootstrap/scss/badge"
@import "../../../pillar/node_modules/bootstrap/scss/jumbotron"
@import "../../../pillar/node_modules/bootstrap/scss/alert"
@import "../../../pillar/node_modules/bootstrap/scss/progress"
@import "../../../pillar/node_modules/bootstrap/scss/media"
@import "../../../pillar/node_modules/bootstrap/scss/list-group"
@import "../../../pillar/node_modules/bootstrap/scss/close"
@import "../../../pillar/node_modules/bootstrap/scss/modal"
@import "../../../pillar/node_modules/bootstrap/scss/tooltip"
@import "../../../pillar/node_modules/bootstrap/scss/popover"
@import "../../../pillar/node_modules/bootstrap/scss/carousel"
@import "../../../pillar/node_modules/bootstrap/scss/utilities"
@import "../../../pillar/node_modules/bootstrap/scss/print"
// Pillar components.
@import "../../../pillar/src/styles/apps_base"
@import "../../../pillar/src/styles/components/base"
@import "../../../pillar/src/styles/components/jumbotron"
@import "../../../pillar/src/styles/components/alerts"
@import "../../../pillar/src/styles/components/navbar"
@import "../../../pillar/src/styles/components/dropdown"
@import "../../../pillar/src/styles/components/footer"
@import "../../../pillar/src/styles/components/shortcode"
@import "../../../pillar/src/styles/components/statusbar"
@import "../../../pillar/src/styles/components/search"
@import "../../../pillar/src/styles/components/placeholder"
@import "../../../pillar/src/styles/components/timeline"
@import "../../../pillar/src/styles/components/flyout"
@import "../../../pillar/src/styles/components/forms"
@import "../../../pillar/src/styles/components/inputs"
@import "../../../pillar/src/styles/components/buttons"
@import "../../../pillar/src/styles/components/popover"
@import "../../../pillar/src/styles/components/tooltip"
@import "../../../pillar/src/styles/components/checkbox"
@import "../../../pillar/src/styles/components/overlay"
@import "../../../pillar/src/styles/components/card"
@import "../../../pillar/src/styles/components/breadcrumbs"
@import "../../../pillar/src/styles/_notifications"
@import "../../../pillar/src/styles/_comments"
@import "../../../pillar/src/styles/_project"
@import "../../../pillar/src/styles/_project-sharing"
@import "../../../pillar/src/styles/_project-dashboard"
@import "../../../pillar/src/styles/_error"
@import "../../../pillar/src/styles/_search"
@import "../../../pillar/src/styles/plugins/_jstree"
@import "../../../pillar/src/styles/plugins/_js_select2"
// Cloud components.
@import variables
@import "_project-landing"

View File

@@ -0,0 +1,15 @@
@import ../styles/colors
@import ../styles/breakpoints
@import ../styles/jumbotron
@import ../styles/navbar-secondary
@import ../styles/cards
body,
.container-page
background: #fff
footer.container-fluid
margin-top: 72px
+media-breakpoint-up(sm)
margin-top: 112px
+media-breakpoint-up(lg)
margin-top: 136px

29
src/styles/welcome.sass Normal file
View File

@@ -0,0 +1,29 @@
@import ../styles/colors
@import ../styles/breakpoints
nav.navbar
+media-breakpoint-up(md)
padding: 10px 10px 10px 20px
.nav-item-sign-in
line-height: 1.5
display: flex
align-items: center
justify-content: center
.navbar-item
.join .navbar .nav-item-sign-in a.navbar-item
background: $primary
font-size: 16px
font-weight: 600
margin: 0
padding: 8px 16px
width: 100%
&:hover
background: $primary
+media-breakpoint-up(md)
width: auto
// temporarily restore old primary color
h2,
h3
color: #68B3C8
footer.container-fluid
margin-top: 0

View File

@@ -1,124 +0,0 @@
.footer-wrapper
| {% block footer_navigation %}
.footer-navigation
.container
.row
.col-md-4.col-xs-6
h4
a(href="{{ url_for('main.homepage') }}")
i.pi-blender-cloud-logo
p.pl-2.
Blender Cloud is the creative hub for your projects,
powered by Free and Open Source Software.
h5.d-flex
a.px-2(href="https://www.youtube.com/BlenderCloudOfficial",
title="Blender Cloud YouTube Channel")
i.pi-social-youtube
a.px-2(href="https://twitter.com/Blender_Cloud",
title="Follow us on Twitter")
i.pi-social-twitter
a.px-2(href="https://www.facebook.com/BlenderCloudOfficial/",
title="Follow us on Facebook")
i.pi-social-facebook
.col-md-2.col-xs-6
h7.font-weight-bold
a.d-block.pb-2(href="{{ url_for('cloud.learn') }}")
| TRAINING
ul.list-unstyled.mb-3
li
a(href="{{ url_for('cloud.courses') }}")
| Courses
li
a(href="{{ url_for('cloud.workshops') }}")
| Workshops
li
a(href="{{ url_for('cloud.production') }}")
span.new Production Lessons
h7.font-weight-bold
a.d-block.pb-2(href="{{ url_for('cloud.open_projects') }}")
| FILMS
.col-md-2.col-xs-6
h7.font-weight-bold
a.d-block.pb-2(href="{{ url_for('cloud.libraries') }}")
| LIBRARIES
ul.list-unstyled
li
a(href="{{ url_for('projects.view', project_url='hdri') }}",
title="HDRI Library")
| HDRIs
li
a(href="{{ url_for('projects.view', project_url='textures') }}",
title="Texture Library")
| Textures
li
a(href="{{ url_for('projects.view', project_url='characters') }}",
title="Characters")
| Characters
li
a(href="{{ url_for('projects.view', project_url='gallery') }}")
| Art Gallery
.col-md-2.col-xs-6
h7.font-weight-bold
a.d-block.pb-2(href="{{ url_for('cloud.services') }}")
| SERVICES
ul.list-unstyled
li
a(href="{{ url_for('cloud.services') }}#blender-cloud-add-on",
title="Blender Cloud add-on")
| Add-on
li
a(href="{{ url_for('projects.home_project') }}",
title="Your synced Blender settings")
| Blender Sync
li
a(href="/attract",
title="Production management")
| Attract
li
a(href="/flamenco",
title="Render management")
| Flamenco
li
a(href="{{ url_for('projects.home_project_shared_images')}}",
title="Share your images from within Blender")
| Image Sharing
.col-md-2.col-xs-6
h7.font-weight-bold
a.d-block.pb-2(href="{{ url_for('main.homepage') }}")
| CLOUD
ul.list-unstyled
li
a(href="{{ url_for('main.main_blog') }}",
title="Blender Cloud Blog")
| Blog
li
a(href="{{ url_for('cloud.terms_and_conditions') }}",
title="Terms and Conditions")
| Terms and Conditions
li
a(href="{{ url_for('cloud.privacy') }}",
title="Privacy")
| Privacy Policy
li.dropdown-divider
li
a(href="https://www.blender.org",
title="Home of Blender, the Free and Open Source creative suite")
| blender.org
| {% endblock footer_navigation %}
#hop(title="Be awesome in space")
i.pi-angle-up

View File

@@ -1,174 +0,0 @@
include ../../../../pillar/src/templates/mixins/components
| {#
| Secondary Navigation Bars.
| #}
| {% macro navigation_homepage(title) %}
button.navbar-toggler(
type="button",
data-toggle="collapse",
data-target="#navigationLinks",
aria-controls="navigationLinks",
aria-expanded="false",
aria-label="Toggle navigation"
)
i.pi-blender-cloud
i.pi-angle-down
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
+nav-secondary-link(
href="{{ url_for('main.homepage') }}")
i.pi-blender-cloud-logo
+nav-secondary-link(
href="{{ url_for('cloud.open_projects') }}",
class="{% if title == 'films' %}active{% endif %}")
span Films
+nav-secondary-link(
href="{{ url_for('cloud.learn') }}",
class="{% if title in ('learn', 'courses', 'workshops') %}active{% endif %}")
span Training
+nav-secondary-link(
href="{{ url_for('cloud.libraries') }}",
class="{% if title == 'libraries' %}active{% endif %}")
span Libraries
+nav-secondary-link(
href="{{ url_for('cloud.services') }}",
class="{% if title == 'services' %}active{% endif %}")
span Services
| {% endmacro %}
| {% macro navigation_home_project(title) %}
button.navbar-toggler(
type="button",
data-toggle="collapse",
data-target="#navigationLinks",
aria-controls="navigationLinks",
aria-expanded="false",
aria-label="Toggle navigation"
)
i.pi-blender-cloud
i.pi-angle-down
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
+nav-secondary-link(
href="{{ url_for('main.homepage') }}")
i.pi-blender-cloud
+nav-secondary-link(
href="{{ url_for('projects.index') }}",
class="{% if title == 'dashboard' %}active{% endif %}")
i.pi-star.pr-2
span My Projects
| {% if current_user.has_organizations() %}
+nav-secondary-link(
href="{{ url_for('pillar.web.organizations.index') }}",
class="{% if title == 'organizations' %}active{% endif %}")
i.pi-users.pr-2
span My Organizations
| {% endif %}
+nav-secondary-link(
href="{{ url_for('projects.home_project_shared_images')}}",
class="{% if title == 'images' %}active{% endif %}")
i.pi-picture.pr-2
span Image Sharing
+nav-secondary-link(
href="{{ url_for('projects.home_project') }}",
class="{% if title == 'blender-sync' %}active{% endif %}")
i.pi-blender.pr-2
span Blender Sync
| {% endmacro %}
| {% macro navigation_project(project, navigation_links, extension_sidebar_links, title) %}
| {% if project.category == 'course' %}
| {% set category_url = url_for('cloud.courses') %}
| {% set category_title = 'Courses' %}
| {% elif project.category == 'workshop' %}
| {% set category_url = url_for('cloud.workshops') %}
| {% set category_title = 'Workshops' %}
| {% elif project.category == 'film' %}
| {% set category_url = url_for('cloud.open_projects') %}
| {% set category_title = 'Films' %}
| {% elif project.category == 'assets' %}
| {% set category_url = url_for('cloud.libraries') %}
| {% set category_title = 'Libraries' %}
| {% else %}
| {% set category_url = url_for('main.homepage') %}
| {% set category_title = project.category %}
| {% endif %}
button.navbar-toggler(
type="button",
data-toggle="collapse",
data-target="#navigationLinks",
aria-controls="navigationLinks",
aria-expanded="false",
aria-label="Toggle navigation"
)
i.pi-blender-cloud
i.pi-angle-down
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
//- Blender Cloud logo.
+nav-secondary-link(
href="{{ url_for('main.homepage') }}")
i.pi-blender-cloud
//- Category (Films, Courses, etc).
+nav-secondary-link(
href="{{ category_url }}",
class="px-0")
span {{ category_title }}
li(class="nav-item px-1")
i.pi-angle-right
//- Project Name.
| {% if project.url != 'blender-cloud' %}
+nav-secondary-link(
class="font-weight-bold{% if title == 'default' %} active{% endif %} px-0",
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
span {{ project.name }}
| {% endif %}
//- Pages (Blog, Team, Awards, etc).
| {% for link in navigation_links %}
+nav-secondary-link(
href="{{ link['url'] }}",
class="{% if link['slug'] == title %}active{% endif %}")
span {{ link['label'] }}
| {% endfor %}
+nav-secondary-link(
href="{{ url_for('cloud.project_browse', project_url=project.url) }}",
title="Browse {{ project.name }}",
class="{% if title == 'project' %}active{% endif %}")
span Browse
//- Link to Production Tools (Attract, Flamenco, SVN, etc).
| {% if extension_sidebar_links %}
+nav-secondary()
li.nav-item.dropdown
a.nav-link.dropdown-toggle(
class="{% if title == 'production-tools' %}active{% endif %}"
href="#"
data-toggle="dropdown")
span Production Tools
i.pi-angle-down
ul.dropdown-menu
| {{ extension_sidebar_links }}
| {% endif %}
| {% endmacro %}

View File

@@ -1,17 +0,0 @@
//- Opengraph/Twitter cards for social media.
| {% macro opengraph(title, description, url_image, url_page) %}
meta(property="og:type", content="website")
meta(property="og:url", content="{{ url_page }}")
meta(property="og:title", content="{{ title }} on Blender Cloud")
meta(name="twitter:title", content="{{ title }} on Blender Cloud")
meta(property="og:description", content="{{ description }}")
meta(name="twitter:description", content="{{ description }}")
| {% if url_image %}
meta(property="og:image", content="{{ url_image }}")
meta(name="twitter:image", content="{{ url_image }}")
| {% endif %}
| {% endmacro %}

View File

@@ -193,16 +193,16 @@ style.
small October 30th, 2015
.page-card-summary
| Introducing integrated blogs in Blender Cloud projects. Glass Half is the first project fully developed on the new Blender Cloud. It's also the first and only project to have share its
a(href='/p/glass-half/5627bb22f0e7220061109c9f') animation dailies
a(href='https://cloud.blender.org/p/glass-half/5627bb22f0e7220061109c9f') animation dailies
| ! But the biggest outcome from Glass Half was definitely
a(href='/p/glass-half/569d6044c379cf445461293e') Flexirig
a(href='https://cloud.blender.org/p/glass-half/569d6044c379cf445461293e') Flexirig
| .
.page-card-side
a(href='/p/glass-half/blog/glass-half-premiere')
a(href='https://cloud.blender.org/p/glass-half/blog/glass-half-premiere')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_10_30_glass.jpg') }}", alt="Glass Half")
section.page-card
.page-card-side
a(href='/blog/new-art-gallery-with-gleb-alexandrov')
a(href='https://cloud.blender.org/blog/new-art-gallery-with-gleb-alexandrov')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_19_art.jpg') }}", alt="Art Gallery")
.page-card-side
h2.page-card-title
@@ -218,11 +218,11 @@ style.
.page-card-summary
| With so much going on in the Cloud at at the studio. The Blender Institute Podcast was born! Sharing our daily studio work, Blender community news, and interacting with the awesome Blender Cloud subscribers.
.page-card-side
a(href='/blog/introducing-blender-institute-podcast')
a(href='https://cloud.blender.org/blog/introducing-blender-institute-podcast')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_24_bip.jpg') }}", alt="Blender Institute Podcast")
section.page-card
.page-card-side
a(href='/p/blenrig/blog/welcome-to-the-blenrig-project')
a(href='https://cloud.blender.org/p/blenrig/blog/welcome-to-the-blenrig-project')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_01_blenrig.jpg') }}", alt="Blenrig")
.page-card-side
h2.page-card-title
@@ -238,11 +238,11 @@ style.
.page-card-summary
| The biggest source for CC0/Public Domain textures on the interwebs goes live. First as beta, as a quick gift right before Xmas 2015!
.page-card-side
a(href='/blog/new-texture-library')
a(href='https://cloud.blender.org/blog/new-texture-library')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_23_textures.jpg') }}", alt="Texture Library")
section.page-card
.page-card-side
a(href='/blog/nraryew-the-character-lib')
a(href='https://cloud.blender.org/blog/nraryew-the-character-lib')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_05_charlib.jpg') }}", alt="Character Library")
.page-card-side
h2.page-card-title
@@ -262,11 +262,11 @@ style.
a(href='https://www.youtube.com/watch?v=kQH897V9bDg&list=PLI2TkLMzCSr_H6ppmzDtU0ut0RwxGvXjv') nicely edited Weekly video reports
| .
.page-card-side
a(href='/p/caminandes-3/blog/caminandes-llamigos')
a(href='https://cloud.blender.org/p/caminandes-3/blog/caminandes-llamigos')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_30_llamigos.jpg') }}", alt="Caminandes: Llamigos")
section.page-card
.page-card-side
a(href='/blog/welcome-sybren')
a(href='https://cloud.blender.org/blog/welcome-sybren')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_03_01_sybren.jpg') }}", alt="Dr. Sybren!")
.page-card-side
h2.page-card-title
@@ -282,11 +282,11 @@ style.
.page-card-summary
| Create your own private projects on Blender Cloud.
.page-card-side
a(href='/blog/welcome-sybren')
a(href='https://cloud.blender.org/blog/welcome-sybren')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_03_projects.jpg') }}", alt="Projects")
section.page-card
.page-card-side
a(href='/blog/introducing-project-sharing')
a(href='https://cloud.blender.org/blog/introducing-project-sharing')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_09_projectsharing.jpg') }}", alt="Sharing")
.page-card-side
h2.page-card-title
@@ -302,11 +302,11 @@ style.
.page-card-summary
| Browse the textures from within Blender!
.page-card-side
a(href='/blog/introducing-project-sharing')
a(href='https://cloud.blender.org/blog/introducing-project-sharing')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_11_addon.jpg') }}", alt="Blender Cloud Add-on")
section.page-card
.page-card-side
a(href='/blog/introducing-private-texture-libraries')
a(href='https://cloud.blender.org/blog/introducing-private-texture-libraries')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_23_privtextures.jpg') }}", alt="Texture Libraries")
.page-card-side
h2.page-card-title
@@ -322,11 +322,11 @@ style.
.page-card-summary
| Sync your Blender preferences across multiple devices.
.page-card-side
a(href='/blog/introducing-blender-sync')
a(href='https://cloud.blender.org/blog/introducing-blender-sync')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_06_30_sync.jpg') }}", alt="Blender Sync")
section.page-card
.page-card-side
a(href='/blog/introducing-image-sharing')
a(href='https://cloud.blender.org/blog/introducing-image-sharing')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_14_image.jpg') }}", alt="Image Sharing")
.page-card-side
h2.page-card-title
@@ -337,21 +337,21 @@ style.
section.page-card
.page-card-side
h2.page-card-title
a(href='/blog/introducing-the-hdri-library')
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
| HDRI Library
small July 27th, 2016
.page-card-summary
| High-dynamic range images are now available on Blender Cloud! With their own special viewer. Also available via the Blender Cloud add-on.
.page-card-side
a(href='/blog/introducing-the-hdri-library')
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_27_hdri.jpg') }}", alt="HDRI Library")
section.page-card
.page-card-side
a(href='/blog/introducing-the-hdri-library')
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_12_06_toon.jpg') }}", alt="Hdri Library")
.page-card-side
h2.page-card-title
a(href='/blog/new-training-toon-character-workflow')
a(href='https://cloud.blender.org/blog/new-training-toon-character-workflow')
| Toon Character Workflow
small December 6th, 2016
.page-card-summary
@@ -366,7 +366,7 @@ style.
| to all resources and training produced so far!
a.page-card-cta(href='https://store.blender.org/product/membership/') Subscribe
.page-card-side
a(href='/p/agent-327')
a(href='https://cloud.blender.org/p/agent-327')
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2017_03_10_agent.jpg') }}", alt="Agent 327")

View File

@@ -1,7 +0,0 @@
| {% extends 'layout.html' %}
| {% block body %}
.row.py-2
.col.text-center
h1 Design System goes here
| {% endblock %}

View File

@@ -11,7 +11,7 @@ section
our team to create more Open Projects, training, services and of course to make Blender the best
CG pipeline in the world. You rock!
p.buttons
a.button(href="{{ abs_url('cloud.login', next='/') }}", target='_blank') Browse Now >
a.button(href="{{ abs_url('cloud.login', next='/') }}", target='_blank') Explore Now >
p.
Here is a quick guide to help you get started with Blender Cloud.

View File

@@ -1,61 +0,0 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %}
| {% from '_macros/_opengraph.html' import opengraph %}
include mixins/components
| {% set page_title = 'Films' %}
| {% set page_description = 'The iconic Blender Open Movies. Featuring all the production files, assets, artwork, and never-seen-before content.' %}
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_01.jpg', _external=true) %}
| {% block page_title %}{{ page_title }}{% endblock %}
| {% block og %}
| {{ opengraph(page_title, page_description, page_header_image, request.url) }}
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_homepage(title) }}
| {% endblock navigation_tabs %}
| {% block body %}
.container.py-4
+category_list_header('{{ page_title }}', '{{ page_description }}')
.row.films-list
| {% for project in projects %}
| {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %}
| {% set project_url = url_for('projects.view', project_url=project.url) %}
.films-item.col-md-4.col-sm-6.col-lg-3.my-5
.d-flex.flex-column.h-100
| {% if project.has_poster %} {# Check convenience attribute set in open_projects() #}
a.mx-auto(
href="{{ project_url }}",
tabindex='{{ loop.index }}')
img.rounded.w-100(
alt="{{ project.name }}",
src="{{ project.extension_props.cloud.poster.thumbnail('l', api=api) }}")
| {% endif %}
h4.pt-5.pb-3.text-center
a.text-white(href="{{ project_url }}")
| {{ project.name }}
| {% if project.summary %}
a.lead.text-secondary(href="{{ project_url }}")
small {{ project | markdowned('summary') }}
.d-flex.align-items-center.mt-auto
a.btn-link.mr-auto.my-3(href="{{ project_url }}")
| Browse #[i.pi-angle-right]
| {% endif %}
| {% if project.status == 'pending' and current_user.is_authenticated and current_user.has_role('admin') %}
p.text-danger
small Project Not Published
| {% endif %}
| {% endif %}
| {% endfor %}
| {% endblock body %}

View File

@@ -1,15 +1,12 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %}
| {% from '_macros/_asset_list_item.html' import asset_list_item %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
| {% from 'nodes/custom/blog/_macros.html' import render_blog_post %}
include ../../../pillar/src/templates/mixins/components
| {% set title = 'homepage' %}
| {% block og %}
meta(property="og:type", content="website")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:url", content="https://cloud.blender.org/")
meta(property="og:title", content="Blender Cloud")
meta(name="twitter:title", content="Blender Cloud")
@@ -21,125 +18,238 @@ meta(property="og:image", content="{% if main_project.picture_header %}{{ main_p
meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_project.picture_header.thumbnail('l', api=api) }}{% else %}{{ url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg')}}{% endif %}")
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_homepage(title) }}
| {% endblock navigation_tabs %}
mixin featured_project_card(title, description, url, image)
a.featured-project-card.card.asset.my-2(href=url)
img.card-thumbnail(alt=title, src=image)
.card-body.py-2()
.card-title.mb-1.font-weight-bold
=title
.card-text
=description
.btn-link
| See more
i.pi-angle-right
mixin featured_projects()
section.py-2.mb-3
h6.title-underline
| Featured Projects
.featured-projects
+card-deck(3)&attributes(attributes)
+featured_project_card(
"MASTER SHADING NODES",
"Dive into a sea of nodes with this training by Simon Thommes.",
"/p/procedural-shading",
"{{ url_for('static', filename='assets/img/features/training_procedural_shading_01.jpg')}}")
+featured_project_card(
"IMPROVE YOUR RIGS",
"The ultimate guide to learn about this crucial step while rigging.",
"/p/weight-painting",
"{{ url_for('static', filename='assets/img/features/training_weight_painting_01.jpg')}}")
+featured_project_card(
"COFFEE RUN",
"This 2d-scroller-inspired short film will take you on the journey of a lifetime.",
"/p/coffee-run",
"{{ url_for('static', filename='assets/img/features/coffee_run_02.jpg')}}")
| {% block body %}
.container-fluid.dashboard-container.imgs-fluid
.row.mt-3
.col-md-10.col-lg-9.col-xl-8.mx-auto
.d-xl-none
+featured_projects()
.dashboard-container
section.dashboard-main
+timeline()
.d-block.text-center
a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}")
| See All Blog Posts
a.d-inline-block.p-3.text-muted(
href="{{ url_for('main.feeds_blogs') }}",
title="Blogs Feed",
data-toggle="tooltip",
data-placement="left")
i.pi-rss
| RSS Feed
.col-md-10.col-lg-9.col-xl-4.mx-auto
.d-lg-none.d-xl-block
+featured_projects()(class="card-deck-vertical border-bottom pb-3")
section.py-2.border-bottom.mb-3
h6.title-underline
a.text-muted(href="{{ url_for('main.nodes_search_index') }}")
| Random Awesome
| {% if random_featured %}
+card-deck()(class='pl-3 random-featured')
| {% for child in random_featured %}
| {% if child.node_type not in ['comment'] %}
| {{ asset_list_item(child, current_user) }}
| {% endif %}
| {% endfor %}
section.blog-stream
ul.blog-stream__list
| {% if latest_posts %}
| {% for node in latest_posts %}
| {{ render_blog_post(node) }}
| {% endfor %}
| {% else %}
.card
.card-body
h6.card-title
| No random featured.
li
.blog-stream__list-details
ul.meta
li.when No blog entries... yet!
| {% endif %}
section.py-3
h6.title-underline Latest Comments
.more
a(href="{{ url_for('main.main_blog') }}")
| See All Blog Posts
ul.list-unstyled.pt-2
| {% if latest_comments %}
| {% for n in latest_comments %}
li.pb-2.mb-2.border-bottom.text-truncate
a.feed(
href="{{ url_for('main.feeds_blogs') }}",
title="Blogs Feed",
data-toggle="tooltip",
data-placement="left")
i.pi-rss
a.js-comment-content.text-muted(href="{{ n.url }}")
| {{ n.properties.content | striptags | truncate(200) }}
| {% if n.attached_to %}
.d-flex.align-items-baseline
a.text-muted.text-truncate(href="{{ n.attached_to.url }}")
small.pr-2.font-weight-bold {{ n.project.name }}
small {{ n.attached_to.name }}
section.dashboard-secondary
| {{ navigation_tabs(title) }}
section.dashboard-in-production
h4 In Production
span.section-lead.
Check out these projects currently in production!
a.in-production-project(href="https://cloud.blender.org/p/spring/")
img(src="{{ url_for('static', filename='assets/img/projects/spring_450x150.jpg')}}")
p.
#[strong Spring] - A poetic short film about a mountain spirit and her wise little dog.
a.in-production-project(href="https://cloud.blender.org/p/hero/")
img(src="{{ url_for('static', filename='assets/img/projects/hero_450x150.jpg')}}")
p.
#[strong Hero] - A '2D' trailer-style movie focused on getting grease pencil
production ready for Blender 2.8.
section.stream
h4 Latest Assets
ul.activity-stream__list
| {% for n in activity_stream %}
li(
class="{{ n.node_type }} {{ n.properties.content_type }} {% if n.picture %}with-picture{% endif %}",
data-url="{{ n.url }}")
a.activity-stream__list-thumbnail(
class="{{ n.properties.content_type }}",
href="{{ n.url }}")
| {% if n.picture %}
img(src="{{ n.picture.thumbnail('m', api=api) }}")
| {% endif %}
.d-flex.align-items-baseline
small.pr-2.font-weight-bold {{ n.user.full_name }}
.activity-stream__list-thumbnail-icon
| {% if n.node_type == 'asset' %}
| {% if n.properties.content_type == 'video' %}
i.pi-play
| {% elif n.properties.content_type == 'image' %}
i.pi-picture
| {% elif n.properties.content_type == 'file' %}
i.pi-file-archive
| {% else %}
i.pi-folder
| {% endif %}
| {% endif %}
a.text-muted(href="{{ n.url }}", title="{{ n._created }}")
small {{ n._created | pretty_date }}
| {% endfor %}
| {% else %}
span
| No comments... yet!
.activity-stream__list-details
a.title(href="{{ n.url }}")
| {{ n.name }}
| {% if n.permissions.world %}
.ribbon
span free
| {% endif %}
ul.meta
| {% if not n.picture %}
li.when
a(href="{{ n.url }}", title="{{ n._created }}") {{ n._created | pretty_date_time }}
li.who {{ n.user.full_name }}
| {% endif %}
| {% if n.attached_to %}
li.where-parent
a(href="{{ n.attached_to.url }}") {{ n.attached_to.name }}
| {% endif %}
li.where-project
a.project(href="{{ url_for('projects.view', project_url=n.project.url) }}") {{ n.project.name }}
li.what
| {% if n.node_type == 'asset' %}
| {{ n.properties.content_type | undertitle }}
| {% endif %}
| {% if n.picture %}
ul.meta.extra
li.when
a(href="{{ n.url }}", title="{{ n._created }}") {{ n._created | pretty_date_time }}
li.who {{ n.user.full_name }}
| {% endif %}
| {% endfor %}
li.activity-stream__list-item.empty#activity-stream__empty
| No items to list.
section.random-asset
h4
a(href="/search") Explore the Cloud
span.section-lead Random selection of the best assets &amp; tutorials
ul.random-asset__list
| {% for n in random_featured %}
| {% if n.picture and loop.first %}
li.random-asset__list-item.project
| {% if n.project.picture_square %}
a.random-asset__list-thumbnail(
href="{{ n.project.url }}")
img.image(src="{{ n.project.picture_square.thumbnail('s', api=api) }}")
| {% endif %}
.random-asset__list-details
a.title(href="{{ n.project.url }}") {{ n.project.name }}
| {% if n.project.summary %}
ul.meta
li.what
a(href="{{ n.project.url }}") {{ n.project.summary }}
| {% endif %}
li.random-asset__list-item.featured
| {% if n.permissions.world %}
.ribbon
span free
| {% endif %}
a.random-asset__thumbnail(
href="{{ n.url }}",
class="{{ n.properties.content_type }}")
| {% if n.picture %}
img(src="{{ n.picture.thumbnail('l', api=api) }}")
| {% if n.properties.content_type == 'video' %}
i.pi-play
| {% endif %}
| {% endif %}
a.title(href="{{ n.url }}")
| {{ n.name }}
ul.meta
li.what
a(href="{{ n.url }}")
| {% if n.properties.content_type %}{{ n.properties.content_type | undertitle }}{% else %}Folder{% endif %}
li.where
a(href="{{ n.project.url }}")
| {{ n.project.name }}
| {% else %}
li
| {% if n.permissions.world %}
.ribbon
span free
| {% endif %}
a.random-asset__list-thumbnail(
href="{{ n.url }}",
class="{{ n.properties.content_type }}")
| {% if n.picture %}
img.image(src="{{ n.picture.thumbnail('s', api=api) }}")
| {% else %}
| {% if n.properties.content_type == 'video' %}
i.pi-film-thick
| {% elif n.properties.content_type == 'image' %}
i.pi-picture
| {% elif n.properties.content_type == 'file' %}
i.pi-file-archive
| {% else %}
i.pi-folder
| {% endif %}
| {% endif %}
.random-asset__list-details
a.title(href="{{ n.url }}") {{ n.name }}
ul.meta
li.what
a(href="{{ n.url }}")
| {% if n.properties.content_type %}{{ n.properties.content_type }}{% else %}Folder{% endif %}
li.where
a(href="{{ n.project.url }}") {{ n.project.name }}
| {% endif %}
| {% endfor %}
section.comments
h4 Latest Comments
ul
| {% if latest_comments %}
| {% for n in latest_comments %}
li(
class="{{ n.node_type }}",
data-url="{{ n.url }}")
a.comment-content(href="{{ n.url }}")
| {{ n.properties.content | striptags | truncate(200) }}
ul.meta
li.who {{ n.user.full_name }}
| {% if n.attached_to %}
li.where-parent
a(href="{{ n.attached_to.url }}") {{ n.attached_to.name }}
| {% endif %}
li.when
a(href="{{ n.url }}", title="{{ n._created }}")
| {{ n._created | pretty_date_time }}
| {% endfor %}
| {% else %}
li.activity-stream__list-item.empty#activity-stream__empty
| No comments... yet!
| {% endif %}
| {% endblock %}
@@ -149,10 +259,16 @@ script.
$(function () {
/* cleanup mentions in comments */
$('.js-comment-content').each(function(){
$('.comment-content').each(function(){
$(this).text($(this).text().replace(/\*|\@|\<(.*?)\>/g, ''));
});
/* Click on the whole asset/comment row to go */
$('.activity-stream__list li, .comments ul li').click(function(e){
window.location.href = $(this).data('url');
$(this).addClass('active');
});
hopToTop(); // Display jump to top button
});
| {% endblock %}

View File

@@ -1,11 +1,9 @@
include ../../../pillar/src/templates/mixins/components
doctype
html(lang="en")
head
meta(charset="utf-8")
title {% if self.page_title() %}{% block page_title %}{% endblock %} — {% endif %}Blender Cloud
meta(name="viewport", content="width=device-width, initial-scale=1, shrink-to-fit=no")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
meta(name="description", content="Blender Cloud is a web based service developed by Blender Institute that allows people to access the training videos and all the data from the open projects.")
meta(name="author", content="Blender Institute")
meta(name="theme-color", content="#3e92aa")
@@ -21,7 +19,7 @@ html(lang="en")
| {% block og %}
meta(property="og:title", content="Blender Cloud")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:url", content="https://cloud.blender.org")
meta(property="og:type", content="website")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_gleb_locomotive.jpg')}}")
meta(property="og:description", content="Blender Cloud is a web based service developed by Blender Institute that allows people to access the training videos and all the data from the open projects.")
@@ -31,103 +29,223 @@ html(lang="en")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_gleb_locomotive.jpg')}}")
| {% endblock og %}
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}")
script.
pillar.utils.initCurrentUser({{ current_user | json | safe }});
script(src="{{ url_for('static_pillar', filename='assets/js/timeline.min.js') }}")
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery-3.1.0.min.js')}}")
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js')}}")
| {% if current_user.is_authenticated %}
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
| {% endif %}
| {% if current_user.has_cap('subscriber') %}
| {# Only load if we can comment (for converting markdown as-we-type) #}
script(src="{{ url_for('static_pillar', filename='assets/js/markdown.min.js') }}")
| {% endif %}
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}")
link(href="{{ url_for('static', filename='assets/img/favicon.png') }}", rel="shortcut icon")
link(href="{{ url_for('static', filename='assets/img/apple-touch-icon-precomposed.png') }}", rel="icon apple-touch-icon-precomposed", sizes="192x192")
link(rel="stylesheet", href="{{ url_for('static', filename='assets/bootstrap/css/bootstrap.min.css') }}")
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
link(rel="stylesheet", href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600")
link(href="{{ url_for('static', filename='assets/google-font-roboto/roboto.css') }}", rel="stylesheet")
| {% block head %}{% endblock %}
| {% block css %}
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
link(href="{{ url_for('static_pillar', filename='assets/css/base.css') }}", rel="stylesheet")
| {% if title == 'blog' %}
link(href="{{ url_for('static_pillar', filename='assets/css/blog.css') }}", rel="stylesheet")
| {% else %}
link(href="{{ url_for('static_cloud', filename='assets/css/main.css') }}", rel="stylesheet")
link(href="{{ url_for('static_cloud', filename='assets/css/base.css') }}", rel="stylesheet")
| {% endif %}
| {% endblock css %}
| {% if not title %}{% set title="default" %}{% endif %}
body(class="{{ title }} {{'project' if project and project.url != 'blender-cloud'}} {% block bodyclasses %}{% endblock %}"
"{% block bodyattrs %}{% endblock %}"
)
| {% with messages = get_flashed_messages(with_categories=True) %}
| {% if messages or (config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS and not current_user.has_cap('subscriber')) %}
| {% if config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS %}
.alert.d-flex.justify-content-center(
role="alert",
class="alert-{{ config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS['category'] }}")
i.pr-2(class="{{ config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS['icon'] }}")
| {{ config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS['message'] | markdown }}
| {% endif %}
| {% for (category, message) in messages %}
.alert.d-flex.justify-content-center(
role="alert",
class="alert-{{ category }}")
i.alert-icon(class="{{ category }}")
span {{ message }}
button.close(type="button", data-dismiss="alert")
i.pi-cancel
| {% endfor %}
| {% endif %}
| {% endwith %}
| {% if not title %}{% set title="default" %}{% endif %}
nav.navbar.navbar-expand-md.fixed-top.bg-white
| {% block navigation_tabs %}
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
+nav-secondary-link(
href="{{ url_for('main.homepage') }}")
i.pi-blender-cloud-logo
| {% endblock navigation_tabs %}
+nav-secondary()(class="m-auto keep-when-overlay")
div.nav-item.quick-search.qs-input#qs-input
body(class="{{ title }}")
.container-page
| {% with messages = get_flashed_messages(with_categories=True) %}
| {% if messages %}
+nav-secondary()(class="ml-auto")
| {% if node and node.properties and node.properties.category %}
| {% set category = node.properties.category %}
| {% else %}
| {% set category = title %}
| {% endif %}
li.nav-item.quick-search.cursor-pointer.px-3.pi-search#qs-toggle
| {% for (category, message) in messages %}
.alert(role="alert", class="alert-{{ category }}")
i.alert-icon(class="{{ category }}")
span {{ message }}
button.close(type="button", data-dismiss="alert")
i.pi-cancel
| {% endfor %}
| {% block navigation_sections %}
| {% endblock navigation_sections %}
| {% endif %}
| {% endwith %}
| {% block navigation_user %}
| {% include 'menus/notifications.html' %}
| {% include 'menus/user.html' %}
| {% endblock navigation_user %}
//- Navigation Bar
nav.navbar.navbar-expand-md.navbar-light.sticky-top
//- Logo
a.navbar-brand(href="{{ url_for('main.homepage') }}")
img(src="{{ url_for('static', filename='assets/img/logo-blender-cloud-text.svg') }}", alt="alt")
//- Mobile Nav Button
button.navbar-toggler(type='button', data-toggle='collapse', data-target='#navbarNav', aria-controls="navbarNav", aria-expanded="false", aria-label="Toggle navigation")
span
span
span
//- Search Bar
#searchNav.hidden
span.search-container
input#cloud-search(type="text", placeholder="Search Blender Cloud")
i.search-icon.pi-search
a#searchCloseButton(ahref="#")
img(src="{{ url_for('static', filename='assets/img/icons/icon-close-button.svg') }}", alt="alt")
//- Links
#navbarNav.collapse.navbar-collapse.justify-content-end
ul.navbar-nav
| {% if node and node.properties and node.properties.category %}
| {% set category = node.properties.category %}
| {% else %}
| {% set category = title %}
| {% endif %}
| {% if current_user.is_anonymous %}
li
a.btn.btn-sm.btn-primary.px-4.mx-1(
href="https://store.blender.org/product/membership/",
title="Sign up") Sign up
| {% endif %}
| {% block navigation_sections %}
li.nav-item
a.nav-link(href="{{ url_for('main.main_blog') }}", class="{% if category == 'blog' %}active{% endif %}") Blog
li.nav-item.dropdown
a.nav-link.dropdown-toggle(href="#", data-toggle="dropdown") Libraries
.dropdown-menu
a.dropdown-item(
href="{{ url_for('projects.view', project_url='hdri') }}")
i.pi-globe
| HDRI
a.dropdown-item(
href="{{ url_for('projects.view', project_url='textures') }}")
i.pi-folder-texture
| Textures
a.dropdown-item(
href="{{ url_for('projects.view', project_url='characters') }}")
i.pi-character
| Characters
li.nav-item.dropdown
a.nav-link.dropdown-toggle(href="#", data-toggle="dropdown") Training
.dropdown-menu
a.dropdown-item(href="{{ url_for('cloud.courses') }}")
i.pi-graduation-cap
| Courses
a.dropdown-item(href="{{ url_for('cloud.workshops') }}")
i.pi-lightbulb
| Workshops
a.dropdown-item(href="{{ url_for('projects.view', project_url='gallery') }}")
i.pi-image
| Art Gallery
li.nav-item
a.nav-link(href="{{ url_for('cloud.open_projects') }}",
class="{% if category in ['open-projects', 'film'] %}active{% endif %}") Open Projects
li.nav-item
a.nav-link(href="{{ url_for('cloud.services') }}",
class="{% if category == 'services' %}active{% endif %}") Services
li.nav-item.search-icon
a#searchButton.navbar-item(data-toggle='collpase', data-target='#searchbarNav', href="#")
i.pi-search
| {% endblock navigation_sections %}
.loading-bar
| {% if current_user.is_anonymous %}
li.nav-item
a.nav-link(
href="https://store.blender.org/product/membership/",
title="Sign up") Sign up
| {% endif %}
.page-content
.quick-search.container-fluid.m-auto.p-5#search-overlay
ul.qs-loading.text-center
i.h1.pi-spin.spinner
h2 Loading
| {% block page_overlay %}
#page-overlay
| {% endblock page_overlay %}
.page-body
| {% block body %}{% endblock %}
| {% block navigation_user %}
| {% include 'menus/notifications.html' %}
| {% include 'menus/user.html' %}
| {% endblock navigation_user %}
.page-content
#search-overlay
| {% block page_overlay %}
#page-overlay
| {% endblock page_overlay %}
.page-body
| {% block body %}{% endblock %}
| {% block footer_container %}
| {% include '_footer.html' %}
footer.container-fluid
| {% block footer_navigation %}
.container.py-5
.row
.col-4
h5 Blender Cloud
p A creative hub for your projects, powered by free and open source software.
ul.list-inline.social-icons
li.list-inline-item
a(href="https://www.facebook.com/BlenderCloudOfficial/")
img(src="../../static/assets/img/icons/icon-social-facebook.svg", alt="alt")
li.list-inline-item
a(href="https://twitter.com/Blender_Cloud")
img(src="../../static/assets/img/icons/icon-social-twitter.svg", alt="alt")
li.list-inline-item
a(href="https://www.youtube.com/channel/UC5qvW9fotdsSJkCguB_t-kQ")
img(src="../../static/assets/img/icons/icon-social-youtube.svg", alt="alt")
.col
h5 Libraries
ul.list-unstyled.text-small
li
a(href="{{ url_for('projects.view', project_url='hdri') }}") HDRI
li
a(href="{{ url_for('projects.view', project_url='textures') }}") Textures
li
a(href="{{ url_for('projects.view', project_url='characters') }}") Characters
.col
h5 Training
ul.list-unstyled.text-small
li
a(href="{{ url_for('projects.view', project_url='gallery') }}") Art Gallery
li
a(href="{{ url_for('cloud.courses') }}") Courses
li
a(href="{{ url_for('cloud.workshops') }}") Workshops
.col
h5 Resources
ul.list-unstyled.text-small
li
a(href="https://www.blender.org") Blender
li
a(href="https://store.blender.org/") Blender Store
li
a(href="https://www.blender.org/foundation/") Contact Us
li
a(href="{{ url_for('cloud.terms_and_conditions') }}") Terms and Conditions
li
a(href="{{ url_for('cloud.privacy') }}") Privacy
.col
h5 Services
ul.list-unstyled.text-small
li
a(href="https://cloud.blender.org/services#attract") Attract
li
a(href="https://cloud.blender.org/services#blender-cloud-add-on") Blender Cloud Add-on
li
a(href="https://cloud.blender.org/services#blender-sync") Blender Sync
li
a(href="https://cloud.blender.org/services#flamenco") Flamenco
li
a(href="https://cloud.blender.org/services#image-sharing") Image Sharing
li
a(href="https://cloud.blender.org/services#projects") Private Projects
li
a(href="https://cloud.blender.org/services#texture-browser") Texture Browser
| {% endblock footer_navigation %}
//- Scroll Up Arrow. Not sure if it's necessary
//- | {% block footer %}
//- footer.container
//- #hop(title="Be awesome in space")
//- i.pi-angle-up
//- | {% endblock footer %}
| {% endblock footer_container %}
#notification-pop(data-url="", data-read-toggle="")
@@ -139,6 +257,21 @@ html(lang="en")
.nc-text
span.nc-date
a(href="")
noscript
link(href='//fonts.googleapis.com/css?family=Roboto:300,400', rel='stylesheet', type='text/css')
//- script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.bootstrap-3.3.7.min.js') }}")
//- local bootstrap js returns error message, investigate later
//- script(src="{{ url_for('static', filename='assets/jquery/jquery.slim.min.js') }}")
//- script(src="{{ url_for('static', filename='assets/popper.js/popper.min.js') }}")
//- script(src="{{ url_for('static', filename='assets/bootstrap/js/bootstrap.min.js') }}")
script(src="https://code.jquery.com/jquery-3.3.1.min.js", integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=", crossorigin="anonymous")
script(src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js", integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49", crossorigin="anonymous")
script(src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js", integrity="sha384-o+RDsa0aLu++PJvFqy8fFScvbHFLtbvScb8AjopnFD+iEQ7wo/CG0xlczd+2O/em", crossorigin="anonymous")
script(src="{{ url_for('static_cloud', filename='assets/js/layout.min.js') }}")
| {% if current_user.is_authenticated %}
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typewatch-3.0.0.min.js') }}")
script.
@@ -163,43 +296,13 @@ html(lang="en")
{% endif %}
});
// Enable all tooltips.
if (typeof $().tooltip != 'undefined'){
$('[data-toggle="tooltip"]').tooltip({'delay' : {'show': 0, 'hide': 0}});
}
// Enable Quick Search
let searches = {
{% if project and not project.is_private %}
project: {
name: 'Project',
uiUrl: '{{ url_for("projects.search", project_url=project.url)}}',
apiUrl: '/api/newsearch/multisearch',
searchParams: [
{name: 'Assets', params: {project: '{{ project._id }}', node_type: 'asset'}},
{name: 'Blog', params: {project: '{{ project._id }}', node_type: 'post'}},
{name: 'Groups', params: {project: '{{ project._id }}', node_type: 'group'}},
]
},
{% endif %}
cloud: {
name: 'Cloud',
uiUrl: '/search',
apiUrl: '/api/newsearch/multisearch',
searchParams: [
{name: 'Assets', params: {node_type: 'asset'}},
{name: 'Blog', params: {node_type: 'post'}},
{name: 'Groups', params: {node_type: 'group'}},
]
},
if(typeof($.fn.popover) != 'undefined'){
$('[data-toggle="popover"]').popover();
}
$('#qs-toggle').quickSearch({
resultTarget: '#search-overlay',
inputTarget: '#qs-input',
searches: searches,
});
| {% block footer_scripts_pre %}{% endblock %}
| {% block footer_scripts %}{% endblock %}

View File

@@ -1,103 +0,0 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %}
| {% from '_macros/_opengraph.html' import opengraph %}
include mixins/components
| {% set title = 'learn' %}
| {% set page_title = 'Learn' %}
| {% set page_description = 'Production quality training by Blender professionals.' %}
| {% set page_header_image = url_for('static', filename='assets/img/features/training_minecraft_animation.jpg', _external=true) %}
| {% block page_title %}{{ page_title }}{% endblock %}
| {% block og %}
| {{ opengraph(page_title, page_description, page_header_image, request.url) }}
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_homepage(title) }}
| {% endblock navigation_tabs %}
| {% block body %}
.container.py-4
+category_list_header('{{ page_title }}', '{{ page_description }}')
+category_list_item(
'COURSES',
'In-depth training for mastering every corner in Blender.',
"{{ url_for('cloud.courses') }}",
"{{ url_for('static', filename='assets/img/features/training_animation_fundamentals_01.jpg')}}",
'/p/animation-fundamentals')
ul.list-unstyled.mt-3.mb-0.column-count-2.list-first-new
-
var projects = {
'Procedural Shading': '/p/procedural-shading',
'Weight Painting':'/p/weight-painting',
'Animation Fundamentals':'/p/animation-fundamentals',
'Stylized Character Workflow':'/p/stylized-character-workflow',
'Scripting for Artists':'/p/scripting-for-artists'
};
each url, title in projects
li
a.d-block.py-1.text-primary(href=url)
span=title
li
a.d-block.py-1.text-primary(href="{{ url_for('cloud.courses') }}")
| See all Courses
i.pi-angle-right
hr.mb-4
+category_list_item(
'WORKSHOPS',
'Enter the artist workshop and learn by example.',
"{{ url_for('cloud.workshops') }}",
"{{ url_for('static', filename='assets/img/features/training_anglerfish_01.jpg')}}",
'/p/anglerfish')
ul.list-unstyled.mt-3.mb-0.column-count-2
-
var projects = {
'Anglerfish':'/p/anglerfish',
'Speed Sculpting':'/p/speed-sculpting',
'Minecraft Animation':'/p/minecraft-animation-workshop',
};
each url, title in projects
li
a.d-block.py-1.text-primary(href=url)
span=title
li
a.d-block.py-1.text-primary(href="{{ url_for('cloud.workshops') }}")
| See all Workshops
i.pi-angle-right
hr.mb-4
+category_list_item(
'PRODUCTION LESSONS',
'Tips and tricks by the Blender Open Movies crew.',
"{{ url_for('cloud.production') }}",
"{{ url_for('static', filename='assets/img/features/open_movies_spring_03.jpg')}}")
ul.list-unstyled.mt-3.mb-0.column-count-2
-
var projects = {
'Walk-throughs':'/production#walk-through',
'Animation Tips':'/production#animation',
'Character Pipeline':'/production#character-pipeline'
};
each url, title in projects
li
a.d-block.py-1.text-primary(href=url)
span=title
li
a.d-block.py-1.text-primary(href="{{ url_for('cloud.production') }}")
| See all Production Lessons
i.pi-angle-right
| {% endblock body %}

View File

@@ -1,128 +0,0 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %}
| {% from '_macros/_opengraph.html' import opengraph %}
include mixins/components
| {% set title = 'libraries' %}
| {% set page_title = 'Libraries' %}
| {% set page_description = 'Download 1000s of files and assets.' %}
| {% set page_header_image = url_for('static', filename='assets/img/features/characters_01.jpg', _external=true) %}
| {% block page_title %}{{ page_title }}{% endblock %}
| {% block og %}
| {{ opengraph(page_title, page_description, page_header_image, request.url) }}
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_homepage(title) }}
| {% endblock navigation_tabs %}
| {% block body %}
.container.py-4
+category_list_header('LIBRARIES', null)
.lead
| Browse Textures and HDRI within Blender with our #[a(href="/services#blender-cloud-add-on") Blender Cloud add-on!]
+category_list_item(
'HDR IMAGES',
'Up to 16K and 24 EVs HDRI to enhance your renders.',
"{{ url_for('projects.view', project_url='hdri') }}",
"{{ url_for('static', filename='assets/img/features/hdri_02.jpg')}}")
ul.list-unstyled.mt-3.mb-0.column-count-2
-
var projects = {
'Myanmar' : '/p/hdri/58d824e588ac8f2107b314e1',
'Indoor' : '/p/hdri/57976801c379cf39de54cc5d',
'Skies' : '/p/hdri/585ab5521f47427f2f7b3604'
};
each url, title in projects
li
a.d-block.py-1.text-primary(href=url)
span=title
li
a.d-block.py-1.text-primary(href="{{ url_for('projects.view', project_url='hdri') }}")
| See all HDRIs
i.pi-angle-right
hr.mb-4
+category_list_item(
'TEXTURES',
'More than 1500 public-domain textures.',
"{{ url_for('projects.view', project_url='textures') }}",
"{{ url_for('static', filename='assets/img/features/textures_02.jpg')}}")
ul.list-unstyled.mt-3.mb-0.column-count-2
-
var projects = {
'Metal' : '/p/textures/5677e2f4c379cf0007b31fe1',
'Wood' : '/p/textures/567800d4c379cf211051a439',
'Bricks': '/p/textures/5672df9fc379cf0007b3198b'
};
each url, title in projects
li
a.d-block.py-1.text-primary(href=url)
span=title
li
a.d-block.py-1.text-primary(href="{{ url_for('projects.view', project_url='textures') }}")
| See all Textures
i.pi-angle-right
hr.mb-4
+category_list_item(
'ART GALLERY',
'Dive into the most interesting .blend files from the community.',
"{{ url_for('projects.view', project_url='gallery') }}",
"{{ url_for('static', filename='assets/img/features/gallery_01.jpg')}}",
'/p/gallery/564a15bec379cf089a7ad514')
ul.list-unstyled.mt-3.mb-0.column-count-2
-
var projects = {
'Grease Pencil Files' : '/p/gallery/5b642e25bf419c1042056fc6',
'Gleb Alexandrov' : '/p/gallery/57907fb8c379cf33d47a098d',
'Midge "Mantissa" Sinnaeve' : '/p/gallery/5800d64ee5e20f084523a059'
};
each url, title in projects
li
a.d-block.py-1.text-primary(href=url)
span=title
li
a.d-block.py-1.text-primary(href="{{ url_for('projects.view', project_url='gallery') }}")
| Visit the Gallery
i.pi-angle-right
hr.mb-4
+category_list_item(
'CHARACTERS',
'Production quality characters ready to animate and render.',
"{{ url_for('projects.view', project_url='characters') }}",
"{{ url_for('static', filename='assets/img/features/characters_01.jpg')}}")
ul.list-unstyled.mt-3.mb-0.column-count-2
-
var projects = {
'Vincent' : '/p/characters/5718a967c379cf04929a4247',
'Big Buck Bunny' : '/p/characters/56cb2785c379cf0079716c19',
'Min (Glass Half)' : '/p/characters/5672d39bc379cf0007b31911'
};
each url, title in projects
li
a.d-block.py-1.text-primary(href=url)
span=title
li
a.d-block.py-1.text-primary(href="{{ url_for('projects.view', project_url='characters') }}")
| See all Characters
i.pi-angle-right
| {% endblock body %}

View File

@@ -9,8 +9,11 @@
| {% endif %}
| {% block menu_avatar %}
a.navbar-item.dropdown-toggle(href="{{ url_for('settings.profile') }}", data-toggle="dropdown")
current-user-avatar
a.nav-link.dropdown-toggle(href="#", data-toggle="dropdown", title="{{ current_user.email }}", aria-haspopup="true", aria-expanded="false")
img.gravatar(
src="{{ current_user.gravatar }}",
class="{{ subscription }}",
alt="Avatar")
.special(class="{{ subscription }}")
| {% if subscription == 'subscriber' %}
i.pi-check
@@ -19,34 +22,32 @@ a.navbar-item.dropdown-toggle(href="{{ url_for('settings.profile') }}", data-tog
| {% else %}
i.pi-attention
| {% endif %}
script.
new Vue({el: 'current-user-avatar'})
| {% endblock menu_avatar %}
| {% block menu_list %}
li.subscription-status(class="{{ subscription }}")
.dropdown-menu.subscription-status(class="{{ subscription }}", aria-labelledby="dropdownMenuLink")
| {% if subscription == 'subscriber' %}
a.navbar-item.pt-2.pl-2.pr-3(
href="{{ url_for('settings.billing') }}"
title="View subscription info")
a.dropdown-item(
href="{{url_for('settings.billing')}}"
title="View subscription info")
i.pi-grin
span.subitem Your subscription is active!
span Your subscription is active!
| {% elif subscription == 'demo' %}
a.navbar-item.pt-2.pl-2.pr-3(
href="{{url_for('settings.billing')}}"
title="View subscription info")
a.dropdown-item(
href="{{url_for('settings.billing')}}"
title="View subscription info")
i.pi-heart-filled
span.subitem You have a free account.
span You have a free account.
| {% elif current_user.has_cap('can-renew-subscription') %}
a.navbar-item.pt-2.pl-2.pr-3(target='_blank', href="/renew", title="Renew subscription")
a.dropdown-item(target='_blank', href="/renew", title="Renew subscription")
i.pi-heart
span.info Your subscription is not active.
span.renew Click here to renew.
| {% else %}
a.navbar-item(
href="https://store.blender.org/product/membership/"
title="Renew subscription")
a.dropdown-item(
href="https://store.blender.org/product/membership/"
title="Renew subscription")
i.pi-unhappy
span.info Your subscription is not active.
span.renew Click here to renew.
@@ -55,9 +56,9 @@ li.subscription-status(class="{{ subscription }}")
| {{ super() }}
li
a.navbar-item.px-2(
href="{{ url_for('settings.billing') }}"
title="Billing")
a.dropdown-item(
href="{{ url_for('settings.billing') }}"
title="Billing")
i.pi-credit-card
| Subscription
| {% endblock menu_list %}

View File

@@ -1,33 +0,0 @@
//- Category listing (Learn, Libraries, etc).
//- Header
mixin category_list_header(title, text)
.row.pt-2.pb-3.mb-4.border-bottom&attributes(attributes)
.col-md-9
h1.py-2.font-weight-bold
=title
.lead
=text
if block
block
//- List Item
mixin category_list_item(title, text, url, image, image_link_url)
.row.pb-2.my-2&attributes(attributes)
.col-md-8
a(href=url, title=title)
h3.font-weight-bold.text-muted
=title
.lead
=text
if block
block
.col-md-4
if image_link_url
- var url = image_link_url
a(href=url, title=title)
img.img-fluid.rounded(alt=title, src=image)

View File

@@ -1,171 +0,0 @@
include ../../../../../../pillar/src/templates/mixins/components
| {% macro render_blog_post(node, project=None, pages=None) %}
.expand-image-links.imgs-fluid
| {% if node.picture %}
+jumbotron(
"{{ node.name }}",
"{{ node._created | pretty_date }}{% if node.user.full_name %} · {{ node.user.full_name }}{% endif %}{% if node.properties.status != 'published' %} · {{ node.properties.status }}{% endif %}",
"{{ node.picture.thumbnail('h', api=api) }}",
"{{ node.url }}")(
class="jumbotron-overlay")
| {% else %}
.pt-5.text-center.text-muted
h2.pb-2
a.text-muted(href="{{ node.url }}")
| {{ node.name }}
ul.d-flex.list-unstyled.justify-content-center
| {% if node.properties.status != 'published' %}
li.mr-3(title="Status {{ node.properties.status }}")
span.badge.badge-danger {{ node.properties.status | undertitle }}
| {% endif %}
| {% if node.project.name %}
li.pr-2 {{ node.project.name }}
| {% endif %}
| {% if node.user.full_name %}
li.pr-2
| {{ node.user.full_name }}
| {% endif %}
li
a.px-2.text-muted(href="{{ node.url }}",
title="Updated {{ node._updated | pretty_date }}")
| {{ node._created | pretty_date }}
li
a.px-2(href="{{ node.url }}#comments")
| Leave a comment
| {% endif %}
.node-details-description.mx-auto.py-5
| {{ node.properties | markdowned('content') }}
hr.my-4
comments-tree#comments-embed.justify-content-center.mx-auto(
parent-id="{{ node._id }}"
read-only=false
)
| {% endmacro %}
//- ******************************************************* -//
| {% macro render_blog_list_item(node) %}
a.card.asset.card-image-fade(
href="{{ node.url }}")
.card-thumbnail
| {% if node.picture %}
img.card-img-top(src="{{ node.picture.thumbnail('m', api=api) }}", alt="{{ node.name }}")
| {% else %}
.card-img-top
i.pi-document-text
| {% endif %}
.card-body.py-2.d-flex.flex-column
.card-title.mb-1.font-weight-bold
| {{ node.name }}
ul.card-text.list-unstyled.d-flex.text-black-50.mt-auto
li.pr-2 {{ node.user.full_name }}
li {{ node._created | pretty_date }}
| {% if node.properties.status != 'published' %}
li.text-info.font-weight-bold {{ node.properties.status}}
| {% endif %}
| {% endmacro %}
//- ******************************************************* -//
| {% macro render_blog_index(current_post, project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %}
| {% if can_create_blog_posts or (current_post and current_post.has_method('PUT')) %}
+nav-secondary(class="bg-light border-bottom")
| {% if can_create_blog_posts %}
+nav-secondary-link(href="{{url_for('nodes.posts_create', project_id=project._id)}}")
i.pi-plus.pr-2
span Create New Blog Post
| {% endif %}
| {% if (current_post and current_post.has_method('PUT')) %}
+nav-secondary-link(href="{{url_for('nodes.edit', node_id=current_post._id)}}")
i.pi-edit.pr-2
span Edit Post
| {% endif %}
| {% endif %}
| {% if posts %}
| {{ render_blog_post(current_post, project=project, pages=pages) }}
.container
.pt-4.text-center
h5
| {% if more_posts_available %}
a.text-muted.py-3.d-block(href="{{ project.blog_archive_url }}")
| More from {{ project.name }} blog
| {% else %}
| More from {{ project.name }} blog
| {% endif %}
+card-deck(class="px-2")
| {% for node in posts %}
| {# Skip listing the current post #}
| {% if node._id != current_post._id %}
| {{ render_blog_list_item(node) }}
| {% endif %}
| {% endfor %}
| {% if more_posts_available %}
a.d-block.pb-4.text-center(href="{{ project.blog_archive_url }}")
| {{posts_meta.total - posts|length}} more blog posts over here
i.pi-angle-right
| {% endif %}
| {% else %}
.text-center
p No posts... yet!
| {% endif %} {# posts #}
| {% endmacro %}
//- Macro for rendering the navigation buttons for prev/next pages -//
| {% macro render_archive_pagination(project) %}
.d-flex.justify-content-center
| {% if project.blog_archive_prev %}
a.px-5.py-3(
href="{{ project.blog_archive_prev }}", rel="prev")
i.pi-angle-left
| Previous page
| {% else %}
span.px-5.py-3.text-black-50
i.pi-angle-left
| Previous page
| {% endif %}
a.px-5.py-3(
href="{{ url_for('main.project_blog', project_url=project.url) }}")
| Blog Index
| {% if project.blog_archive_next %}
a.px-5.py-3(
href="{{ project.blog_archive_next }}", rel="next")
| Next page
i.pi-angle-right
| {% else %}
span.px-5.py-3.text-black-50
| Next page
i.pi-angle-right
| {% endif %}
| {% endmacro %}
| {% macro render_archive(project, posts, posts_meta) %}
| {{ render_archive_pagination(project) }}
+card-deck(class="px-2")
| {% for node in posts %}
| {{ render_blog_list_item(node) }}
| {% endfor %}
| {{ render_archive_pagination(project) }}
| {% endmacro %}

View File

@@ -1,212 +0,0 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_home_project %}
include ../../../../pillar/src/templates/mixins/components
| {% set title = 'organizations' %}
| {% block page_title %}Organizations{% endblock %}
| {% block og %}
meta(property="og:title", content="Dashboard")
meta(name="twitter:title", content="Blender Cloud")
meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}")
meta(property="og:type", content="website")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_home_project(title) }}
| {% endblock navigation_tabs %}
| {% block body %}
+nav-secondary
| {% if can_create_organization %}
+nav-secondary-link(
class="create",
onclick='createNewOrganization(this)')
i.pi-plus.text-success
span.text-success
| Create Organization
| {% endif %}
li#create_organization_result_panel.result
.container-fluid.dashboard-container
.row
.col-md-6
ul.projects__list
| {% if organizations %}
| {% for organization in organizations['_items'] %}
| {% set link_url = url_for('pillar.web.organizations.view_embed', organization_id=organization._id) %}
li.projects__list-item(
data-url="{{ link_url }}",
id="organization-{{ organization._id }}")
a.projects__list-thumbnail(
href="{{ link_url }}")
i.pi-users
.projects__list-details
a.title(href="{{ link_url }}")
| {{ organization.name }}
ul.meta
li(title="Members")
| {{ organization.members|hide_none|count }} Member{{ organization.members|hide_none|count|pluralize }}
| {% if (organization.unknown_members|hide_none|count) != 0 %}
| ({{ organization.unknown_members|hide_none|count }} pending)
| {% endif %}
li(title="Seats")
| {{ organization.seat_count }} Seat{{ organization.seat_count|pluralize }}
| {% endfor %}
| {% else %}
li.projects__list-item
a.projects__list-thumbnail
i.pi-blender-cloud
.projects__list-details
span Create an Organization to get started!
| {% endif %}
.col-md-6.py-1.pb-3
#item-details
| {% endblock %}
| {% block footer_scripts %}
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.autocomplete-0.22.0.min.js') }}", async=true)
script.
/* Returns a more-or-less reasonable message given an error response object. */
function xhrErrorResponseMessage(err) {
if (typeof err.responseJSON == 'undefined')
return err.statusText;
if (typeof err.responseJSON._error != 'undefined' && typeof err.responseJSON._error.message != 'undefined')
return err.responseJSON._error.message;
if (typeof err.responseJSON._message != 'undefined')
return err.responseJSON._message
return err.statusText;
}
/**
* Open an organization in the #item-details div.
*/
function item_open(item_id, pushState)
{
if (item_id === undefined ) {
throw new ReferenceError("item_open(" + item_id + ") called.");
}
// Style elements starting with item_type and dash, e.g. "#job-uuid"
var clean_classes = 'active processing';
var current_item = $('#organization-' + item_id);
$('[id^="organization-"]').removeClass(clean_classes);
current_item
.removeClass(clean_classes)
.addClass('processing');
var item_url = '/o/' + item_id;
$.get(item_url, function(item_data) {
$('#item-details').html(item_data);
current_item
.removeClass(clean_classes)
.addClass('active');
}).fail(function(xhr) {
if (console) {
console.log('Error fetching organization', item_id, 'from', item_url);
console.log('XHR:', xhr);
}
current_item.removeClass(clean_classes);
toastr.error('Failed to open organization');
if (xhr.status) {
$('#item-details').html(xhr.responseText);
} else {
$('#item-details').html('<p class="text-danger">Opening ' + item_type + ' failed. There possibly was ' +
'an error connecting to the server. Please check your network connection and ' +
'try again.</p>');
}
});
// Determine whether we should push the new state or not.
pushState = (typeof pushState !== 'undefined') ? pushState : true;
if (!pushState) return;
// Push the correct URL onto the history.
var push_state = {itemId: item_id};
window.history.pushState(
push_state,
'Organization: ' + item_id,
item_url
);
}
$('li.projects__list-item').click(function(e){
url = $(this).data('url');
if (typeof url === 'undefined') return;
window.location.href = url;
if (console) console.log(url);
$(this).addClass('active');
$(this).find('.projects__list-thumbnail i')
.removeAttr('class')
.addClass('pi-spin spin');
});
{% if open_organization_id %}
$(function() { item_open('{{ open_organization_id }}', false); });
{% endif %}
{% if can_create_organization %}
function createNewOrganization(button) {
$(button)
.attr('disabled', 'disabled')
.fadeTo(200, 0.1);
$('#create_organization_result_panel').html('');
// TODO: create a form to get the initial info from the user.
$.post(
'{{ url_for('pillar.web.organizations.create_new') }}',
{
name: 'New Organization',
seat_count: 1,
}
)
.done(function(result) {
var $p = $('<p>').text('organization created, reloading list.')
$('#create_organization_result_panel').html($p);
window.location.href = result.location;
})
.fail(function(err) {
var msg = xhrErrorResponseMessage(err);
$('#create_organization_result_panel').html('Error creating organization: ' + msg);
$(button)
.fadeTo(1000, 1.0)
.queue(function() {
$(this)
.removeAttr('disabled')
.dequeue()
;
})
})
;
return false;
}
{% endif %}
| {% endblock %}

View File

@@ -15,7 +15,7 @@ style.
This Application collects some Personal Data from its Users.
h3 Data Controller and Owner
p.
Blender Institute B.V. - Buikslotermeerplein 161 - 1025 ET Amsterdam - the Netherlands,
Blender Institute B.V. - Entrepotdok 57A - 1018 AD Amsterdam - the Netherlands,
institute@blender.org
p.
Blender Institute has been authorised by Stichting Blender Foundation to conduct these
@@ -121,8 +121,7 @@ style.
Data Controller to erase the Personal Data. Unless stated otherwise, the then-current privacy
policy applies to all Personal Data the Data Controller has about Users.
h4 Definitions and legal references
p Original issue: February 27, 2014
p Latest update: June 10, 2019 (Updated Blender Institute address)
p Latest update: February 27, 2014
| {% endblock body%}

View File

@@ -1,64 +0,0 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %}
| {% from '_macros/_opengraph.html' import opengraph %}
include mixins/components
include ../../../pillar/src/templates/mixins/components
mixin group(title, tag)
.row(id=tag)
section.py-4.my-3.border-bottom.col-12
h4.title-underline.mt-2.mb-4
a.text-muted(href="#" + tag)= title
+card-deck(data-asset-tag=tag, class="js-asset-list p-3")
| {% set title = 'learn' %}
| {% set page_title = 'Production Lessons' %}
| {% set page_description = 'Tips and tricks by the Blender Open Movies crew.' %}
| {% set page_header_image = url_for('static', filename='assets/img/features/open_movies_02.jpg', _external=true) %}
| {% block page_title %}{{ page_title }}{% endblock %}
| {% block og %}
| {{ opengraph(page_title, page_description, page_header_image, request.url) }}
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_homepage(title) }}
| {% endblock navigation_tabs %}
| {% block head %}
script(src="{{ url_for('static_cloud', filename='assets/js/tagged_assets.min.js') }}")
script.
$(function() {
let is_subscriber = {{ current_user.has_cap('subscriber')|string|lower }};
$('.js-asset-list').loadTaggedAssets(8, 8, is_subscriber);
});
| {% endblock %}
| {% block body %}
.container.py-4
+category_list_header('{{ page_title }}', '{{ page_description }}')
.row
.col-12
+group('Walk-through', 'walk-through')
+group('Modeling', 'modeling')
+group('Sculpting', 'sculpting')
+group('Animation', 'animation')
+group('Shading', 'shading')
+group('Texturing', 'texturing')
+group('Character Pipeline', 'character-pipeline')
+group('Rigging', 'rigging')
+group('Lighting & Rendering', 'lighting')
+group('Simulation & Effects', 'effects')
+group('Video Editing', 'video-editing')
+group('Digital Painting', 'digital-painting')
+group('Production Design', 'production-design')
a.d-block.py-5.text-center.text-muted(
href="{{ url_for('main.nodes_search_index') }}")
| Search Blender Cloud to find even more content
i.pi-angle-right.pl-1
| {% endblock body%}

View File

@@ -1,21 +0,0 @@
| {% extends 'projects/edit_layout.html' %}
| {% set title = 'cloud' %}
| {% block page_title %}Blender Cloud settings for {{ project.name }}{% endblock %}
| {% block head %}
script(src="{{ url_for('static_attract', filename='assets/js/generated/tutti.min.js') }}")
| {% endblock %}
| {% block project_context %}
.container-fluid
.row
.col-md-12
h5.pt-3 {{ self.page_title() }}
hr
#node-edit-container
| {% block cloud_container %}
| {% endblock cloud_container %}
| {% endblock project_context %}

View File

@@ -1,28 +0,0 @@
| {% extends 'project_settings/cloud_layout.html' %}
| {% block cloud_container %}
#node-edit-form
p This project is not setup for Blender Cloud #[span.text-muted (yet!)]
p
button.btn.btn-outline-primary.px-3(onclick='setupForFilm()')
i.pr-2.pi-blender-cloud
| Setup Project for Film
| {% endblock cloud_container %}
| {% block footer_scripts %}
script.
function setupForFilm() {
$.ajax({
url: '{{ url_for( "cloud.setup_for_film", project_url=project.url) }}',
method: 'POST',
})
.done(function() {
window.location.reload();
})
.fail(function(err) {
var err_elt = xhrErrorResponseElement(err, 'Error setting up your project: ');
toastr.error(err_elt);
});
}
| {% endblock %}

View File

@@ -1,66 +0,0 @@
| {% extends 'project_settings/cloud_layout.html' %}
| {% block cloud_container %}
#node-edit-form
form(onsubmit="save(this, '{{ url_for('cloud.save_film_settings', project_url=project['url']) }}'); return false;")
| {% for field in form %}
| {% if field.name == 'csrf_token' %}
| {{ field }}
| {% else %}
| {% if field.type == 'HiddenField' %}
| {{ field }}
| {% else %}
.form-group(class="{{field.name}}{% if field.errors %} error{% endif %}")
| {{ field.label }}
| {% if field.name == 'picture' %}
| {% if post.picture %}
img.node-preview-thumbnail(src="{{ post.picture.thumbnail('m', api=api) }}")
a(href="#", class="file_delete", data-field-name="picture", data-file_id="{{post.picture._id}}") Delete
| {% endif %}
| {% endif %}
| {{ field(class='form-control') }}
| {% if field.description %}
small.form-text.text-muted
| {{ field.description }}
| {% endif %}
| {% if field.errors %}
ul.error
| {% for error in field.errors %}
li {{ error }}
| {% endfor %}
| {% endif %}
| {% endif %}
| {% endif %}
| {% endfor %}
button.btn.btn-outline-success.btn-block(type='submit')
i.pi-check
| Save
| {% endblock cloud_container %}
| {% block footer_scripts %}
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.ui.widget.min.js') }}")
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.iframe-transport.min.js') }}")
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.fileupload.min.js') }}")
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/file_upload.min.js') }}")
script.
ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''});
function save(form, url) {
let serializedData = $(form).serializeArray()
$.post(url, serializedData)
.done(function(xhr) {
toastr.success('Properties saved');
})
.fail(function(err) {
toastr.error(xhrErrorResponseElement(err, 'Error saving properties: '));
});
}
| {% endblock %}

View File

@@ -1,23 +0,0 @@
| {% from '_macros/_asset_list_item.html' import asset_list_item %}
include ../../../../pillar/src/templates/mixins/components
| {% block body %}
#node-container
section.d-flex
h4.p-4 Browse
section.container-fluid
| {% if nodes %}
+card-deck(id="asset_list_explore", class="pl-4")
| {% for node in nodes %}
| {{ asset_list_item(node, current_user) }}
| {% endfor %}
| {% else %}
.list-node-children-container
.list-node-children-empty No items... yet!
| {% endif %}
script.
// Generate GA pageview
ga('send', 'pageview', location.pathname);
| {% endblock %}

View File

@@ -1,61 +0,0 @@
| {% extends 'projects/home_layout.html' %}
| {% set title = 'blender-sync' %}
| {% set subtab = 'blender_sync' %}
| {% set learn_more_btn_url = '/blog/introducing-blender-sync' %}
| {% block currenttab %}
.container-fluid
section.nav-tabs__tab.active#tab-blender_sync
.tab_header-container
.tab_header-intro(
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/pattern_01.jpg')}})")
.tab_header-intro_text
h2 Connect Blender with the Cloud
p
| Save your Blender preferences and keymaps once, load them anywhere.
<br/>
| Use the
=' '
a(href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip') Blender Cloud add-on
=' '
| to synchronise your settings from within Blender.
| {% if show_addon_download_buttons %}
.row
.col-md-6
a.btn.btn-block.btn-outline-success(
href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip")
i.pi-download
| Download <small>v</small>{{ config.BLENDER_CLOUD_ADDON_VERSION }}
.col-md-6
a.btn.btn-link(
href="{{ learn_more_btn_url }}")
| Learn More
i.pi-angle-right
| {% endif %}
.tab_header-intro_icons
i.pi-blender
i.pi-heart-filled
i.pi-blender-cloud
| {% for version in synced_versions %}
.blender_sync-main
.blender_sync-main-header
h5.blender_sync-main-title
i.pi-blender
| Blender {{ version.version }}
.blender_sync-main-last
| Last synced on: {{ version.date|pretty_date }}
| {% else %}
.blender_sync-main.empty
.blender_sync-main-header
span.blender_sync-main-title
| No settings synced yet
<hr/>
a.download(
href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip')
| Download add-on
| {% endfor %}
| {% endblock %}

View File

@@ -1,42 +0,0 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_home_project %}
include ../../../../pillar/src/templates/mixins/components
| {% block og %}
meta(property="og:type", content="website")
meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
meta(property="og:title", content="Blender Cloud - Home")
meta(name="twitter:title", content="Blender Cloud")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
| {% endblock %}
| {% block page_title %}
| {{current_user.full_name}}
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_home_project(title) }}
| {% endblock navigation_tabs %}
| {% block body %}
.dashboard-container
section#projects.bg-white
| {% block currenttab %}{% endblock %}
| {% endblock %}
| {% block footer_scripts %}
script.
$(document).ready(function () {
$('#subtab-{{ subtab }}').addClass('active');
var $nav_tabs = $('#sub-nav-tabs__list').find('a.nav-link');
$nav_tabs.on('click', function (e) {
console.log($(this));
window.location = $(this).attr('data-tab-url');
});
});
| {% endblock %}

View File

@@ -1,304 +0,0 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_home_project %}
include ../../../../pillar/src/templates/mixins/components
| {% set title = 'dashboard' %}
| {% block og %}
meta(property="og:title", content="Dashboard")
meta(name="twitter:title", content="Blender Cloud")
meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}")
meta(property="og:type", content="website")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
| {% endblock %}
| {% block page_title %}
| {{current_user.full_name}}
| {% endblock %}
| {% block css %}
| {{ super() }}
style.
.deleted-projects-toggle {
z-index: 10;
position: absolute;
right: 0;
font-size: 20px;
padding: 3px;
text-shadow: 0 0 2px white;
}
.deleted-projects-toggle .show-deleted {
color: #aaa;
}
.deleted-projects-toggle .hide-deleted {
color: #bbb;
}
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_home_project(title) }}
| {% endblock navigation_tabs %}
| {% block body %}
.dashboard-container
section.dashboard-main
section#projects.bg-white
.d-flex
+nav-secondary()(id='sub-nav-tabs__list')
+nav-secondary-link(data-tab-toggle='own_projects', class="active")
span
| Own Projects
| {% if projects_user | length != 0 %}
.d-inline.text-muted.pl-1 ({{ projects_user|length }})
| {% endif %}
+nav-secondary-link(data-tab-toggle='shared')
span
| Shared with me
| {% if projects_shared | length != 0 %}
.d-inline.text-muted.pl-1 ({{ projects_shared|length }})
| {% endif %}
+nav-secondary()()
| {% if current_user.has_cap('subscriber') %}
+nav-secondary-link(
id="project-create",
data-url="{{ url_for('projects.create') }}",
href="{{ url_for('projects.create') }}")
span.text-success Create New Project...
| {% elif current_user.has_cap('can-renew-subscription') %}
+nav-secondary-link(
id="project-create",
data-url="{{ url_for('projects.create') }}",
href="/renew",
target="_blank")
i.pi-heart-filled.text-danger.pr-1
span Resubscribe to Create a Project
| {% endif %}
nav.nav-tabs__tab.active#own_projects
.deleted-projects-toggle
| {% if show_deleted_projects %}
a.hide-deleted(href="{{ request.base_url }}", title='Hide deleted projects')
i.pi-trash
| {% else %}
a.show-deleted(href="{{ request.base_url }}?deleted=1", title='Show deleted projects')
i.pi-trash
| {% endif %}
ul.projects__list
| {% for project in projects_deleted %}
li.projects__list-item.deleted
span.projects__list-thumbnail
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
| {% else %}
i.pi-blender-cloud
| {% endif %}
.projects__list-details
span.title {{ project.name }}
ul.meta
li.status.deleted Deleted
li.edit
a(href="javascript:undelete_project('{{ project._id }}')") Restore project
| {% else %}
| {% if show_deleted_projects %}
li.projects__list-item.deleted You have no recenly deleted projects. Deleted projects can be restored within a month after deletion.
| {% endif %}
| {% endfor %}
| {% for project in projects_user %}
li.projects__list-item(
data-url="{{ url_for('projects.view', project_url=project.url) }}")
a.projects__list-thumbnail(
href="{{ url_for('projects.view', project_url=project.url) }}")
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
| {% else %}
i.pi-blender-cloud
| {% endif %}
.projects__list-details
a.title(href="{{ url_for('projects.view', project_url=project.url) }}")
| {{ project.name }}
ul.meta
li.status(
class="{{ project.is_private | yesno('private,public,') }}",
title="{{ project.is_private | yesno('Private Project,Public Project,') }}")
| {{ project.is_private | yesno('Private,Public,') }}
li.when(title="{{ project._created }}") {{ project._created | pretty_date }}
li.edit
a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit
| {% if project.status == 'pending' and current_user.has_cap('view-pending-nodes') %}
li.pending Not Published
| {% endif %}
| {% else %}
| {% if current_user.has_cap('subscriber') %}
li.projects__list-item(data-url="{{ url_for('projects.create') }}")
a.projects__list-thumbnail
i.pi-plus
.projects__list-details
a.title(href="{{ url_for('projects.create') }}")
| Create a project to get started!
| {% elif current_user.has_cap('can-renew-subscription') %}
li.projects__list-item(data-url="https://store.blender.org/renew-my-subscription.php")
a.projects__list-thumbnail
i.pi-plus
.projects__list-details
a.title(href="https://store.blender.org/renew-my-subscription.php")
| Renew your Blender Cloud subscription to create your own projects!
| {% else %}
li.projects__list-item(data-url="/join")
a.projects__list-thumbnail
i.pi-plus
.projects__list-details
a.title(href="/join")
| Join Blender Cloud to create your own projects!
| {% endif %}
| {% endfor %}
section.nav-tabs__tab#shared(style='display: none')
ul.projects__list
| {% if projects_shared %}
| {% for project in projects_shared %}
li.projects__list-item(
data-url="{{ url_for('projects.view', project_url=project.url) }}")
a.projects__list-thumbnail(
href="{{ url_for('projects.view', project_url=project.url) }}")
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
| {% else %}
i.pi-blender-cloud
| {% endif %}
.projects__list-details
a.title(href="{{ url_for('projects.view', project_url=project.url) }}")
| {{ project.name }}
ul.meta
li.status(
class="{{ project.is_private | yesno('private,public,') }}",
title="{{ project.is_private | yesno('Private Project,Public Project,') }}")
| {{ project.is_private | yesno('Private,Public,') }}
li.when {{ project._created | pretty_date }}
li.who by {{ project.user.full_name }}
li.edit
a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit
| {% if project.status == 'pending' and current_user.has_cap('view-pending-nodes') %}
li.pending Not Published
| {% endif %}
li.leave
span.user-remove-prompt
| Leave Project
span.user-remove
| Are you sure?
span.user-remove-confirm(
user-id="{{ current_user.objectid }}",
project-url="{{url_for('projects.sharing', project_url=project.url)}}")
i.pi-check
| Yes, leave
span.user-remove-cancel
i.pi-cancel
| No, cancel
| {% endfor %}
| {% else %}
li.projects__list-item
a.projects__list-thumbnail
i.pi-heart-broken
.projects__list-details
.title
| No projects shared with you... yet!
| {% endif %}
| {% endblock %}
| {% block footer_scripts %}
script.
$(document).ready(function() {
$('li.projects__list-item').click(function(e){
url = $(this).data('url');
if (typeof url === 'undefined') return;
window.location.href = url;
if (console) console.log(url);
$(this).addClass('active');
$(this).find('.projects__list-thumbnail i')
.removeAttr('class')
.addClass('pi-spin spin');
});
// Tabs behavior
var $nav_tabs_list = $('#sub-nav-tabs__list');
var $nav_tabs = $nav_tabs_list.find('a.nav-link');
$nav_tabs.on('click', function(e){
e.preventDefault();
$nav_tabs.removeClass('active');
$(this).addClass('active');
$('.nav-tabs__tab').hide();
$('#' + $(this).attr('data-tab-toggle')).show();
});
// Leave project
var $projects_list = $('ul.projects__list');
$projects_list.find('span.user-remove-prompt').on('click', function(e){
e.stopPropagation();
e.preventDefault();
$(this).next().show();
$(this).hide();
});
$projects_list.find('span.user-remove-cancel').on('click', function(e){
e.stopPropagation();
e.preventDefault();
$(this).parent().prev().show();
$(this).parent().hide();
});
$projects_list.find('span.user-remove-confirm').on('click', function(e){
e.stopPropagation();
e.preventDefault();
var parent = $(this).closest('.projects__list-item');
function removeUser(userId, projectUrl){
$.post(projectUrl, {user_id: userId, action: 'remove'})
.done(function (data) {
parent.remove();
});
}
removeUser($(this).attr('user-id'), $(this).attr('project-url'));
});
hopToTop(); // Display jump to top button
});
var patch_url = '{{ url_for('projects.patch.patch_project', project_id='PROJECTID') }}';
function undelete_project(project_id) {
console.log('undeleting project', project_id);
$.ajax({
url: patch_url.replace('PROJECTID', project_id),
method: 'PATCH',
data: JSON.stringify({'op': 'undelete'}),
contentType: 'application/json'
})
.done(function(data, textStatus, jqXHR) {
location.href = jqXHR.getResponseHeader('Location');
})
.fail(function(err) {
toastr.error(xhrErrorResponseMessage(err), 'Undeletion failed');
})
}
| {% endblock %}

View File

@@ -1,25 +1,68 @@
| {% import 'projects/_macros.html' as projectmacros %}
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_project %}
| {% from '_macros/_opengraph.html' import opengraph %}
include ../../../../pillar/src/templates/mixins/components
| {% block bodyclasses %}{{ super() }} landing-home{% endblock %}
| {% from '_macros/_asset_list_item.html' import asset_list_item %}
| {% block page_title %}{{ project.name }}{% endblock%}
| {% block og %}
meta(property="og:type", content="website")
| {% if og_picture %}
| {% set og_picture_url = og_picture.thumbnail('l', api=api) %}
| {% else %}
| {% set og_picture_url = None %}
meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
| {% elif node and node.picture %}
meta(property="og:image", content="{{ node.picture.thumbnail('l', api=api) }}")
meta(name="twitter:image", content="{{ node.picture.thumbnail('l', api=api) }}")
| {% elif project.picture_header %}
meta(property="og:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
meta(name="twitter:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
| {% endif %}
| {% block og %}
| {{ opengraph(project.name, project.summary, og_picture_url, url_for('cloud.project_landing', project_url=project.url, _external=True)) }}
| {% endblock %}
| {% if show_project %}
meta(property="og:title", content="{{ project.name }} - Blender Cloud")
meta(name="twitter:title", content="{{ project.name }} - Blender Cloud")
meta(property="og:description", content="{{ project.summary }}")
meta(name="twitter:description", content="{{ project.summary }}")
meta(property="og:url", content="{{ url_for('projects.view', project_url=project.url, _external=True) }}")
| {% else %}
| {% if node %}
meta(property="og:title", content="{{ node.name }} - Blender Cloud")
meta(name="twitter:title", content="{{ node.name }} on Blender Cloud")
| {% if node.node_type == 'post' %}
| {% if node.properties.content %}
meta(property="og:description", content="{{ node.properties.content | truncate(180) }}")
meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}")
| {% else %}
meta(property="og:description", content="Blender Cloud, your source for open content and training")
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
| {% endif %}
| {% else %}
| {% if node.description %}
meta(property="og:description", content="{{ node.description | truncate(180) }}")
meta(name="twitter:description", content="{{ node.description | truncate(180) }}")
| {% else %}
meta(property="og:description", content="Blender Cloud, your source for open content and training")
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
| {% endif %}
| {% endif %}
meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}")
| {% else %}
meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud")
meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud")
meta(property="og:description", content="{{ project.summary }}")
meta(name="twitter:description", content="{{ project.summary }}")
meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {% endif %}
| {% endif %}
| {% endblock og %}
| {% block page_overlay %}
#page-overlay.video
@@ -27,156 +70,102 @@ include ../../../../pillar/src/templates/mixins/components
#others
| {% endblock %}
| {% block css %}
link(href="{{ url_for('static_cloud', filename='assets/css/project-main.css') }}", rel="stylesheet")
| {% block head %}
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}")
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}")
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}")
script(src="{{ url_for('static_cloud', filename='assets/js/photoswipe.min.js') }}")
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_project(project, navigation_links, extension_sidebar_links, title) }}
| {% endblock navigation_tabs %}
| {% block css %}
| {{ super() }}
link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css') }}", rel="stylesheet")
| {% endblock %}
| {% block body %}
header
.jumbotron.jumbotron-fluid(
style="background-image: url('{{ project.picture_header.thumbnail('h', api=api) }}'); background-position: 50% 50%;")
| {% if project.has_method('PUT') %}
+nav-secondary
+nav-secondary-link(
href="{{ url_for('projects.edit', project_url=project.url) }}",
class="text-white")
i.pi-edit.pr-2
span Edit Project
| {% endif %}
| {# Secondary Navigation #}
| {% block navbar_secondary %}
| {{ projectmacros.render_secondary_navigation(project, pages=pages) }}
| {% endblock navbar_secondary %}
| {% if project.picture_header %}
| {% set project_header = project.picture_header.thumbnail('h', api=api) %}
| {% endif %}
.container.landing
section.node-details-container.project
.node-details-title.container
h1 {{ project.name }}
| {% set project_browse_url = url_for('cloud.project_browse', project_url=project.url) %}
.jumbotron.text-white.jumbotron-overlay-gradient-fade-to-gray(
style="background-image: url(\'{{ project_header }}\');")
.container
.row
.col-md-6
.display-4.text-uppercase.font-weight-bold
| {% if project.extension_props.cloud.logo %}
a(href="{{ project_browse_url }}")
img.img-fluid(
alt="{{ project.name }}",
src="{{ project.extension_props.cloud.logo.thumbnail('m', api=api) }}")
| {% else %}
a.text-white(href="{{ project_browse_url }}")
| {{ project.name }}
| {% endif %}
.lead.pt-3
| {% if project.summary %}
| {{ project | markdowned('summary') }}
| {% endif %}
.d-flex.pt-4
| {% if project.extension_props.cloud.video_url %}
a.btn.btn-primary.px-5(
class="js-open-overlay-video",
href="{{ project.extension_props.cloud.video_url }}",
target="_blank")
i.pi-play.pr-2
| WATCH
| {% endif %}
a.btn.btn-link.px-4.text-white(href="{{ project_browse_url }}")
| Browse
i.pi-angle-right.pl-2
.container-fluid.landing
.row
.col-md-8.mx-auto.mt-5
.node-details-description
| {% if project.description %}
| {{ project | markdowned('description') }}
| {% endif %}
.row
.col-md-10.mx-auto
section.py-5
.gallery.mx-auto
| {% for n in activity_stream %}
| {% if n.node_type not in ['comment', 'post'] and n.picture %}
.thumbnail.expand-image-links
.img-container
a.js-open-overlay-image(
title="{{ n.name }}",
href="{{ n.picture.thumbnail('l', api=api) }}")
img(
alt="{{ n.name }}",
src="{{ n.picture.thumbnail('l', api=api) }}")
| {% endif %}
| {% endfor %}
| {% if project.description %}
| {{ project | markdowned('description') }}
| {% endif %}
section.gallery
h2.text-center Gallery
.container
| {% for n in activity_stream %}
| {% if n.node_type not in ['comment', 'post'] and n.picture %}
.thumbnail.expand-image-links
.thumbnail-container
a(href="{{ n.picture.thumbnail('l', api=api) }}", data-node_id="{{ n._id }}")
img(src="{{ n.picture.thumbnail('l', api=api) }}", alt="{{ n.name }}")
| {% endif %}
| {% endfor %}
.clearfix
.text-center.mx-auto.py-3
a.btn.btn-outline-primary.px-5(
href="{{ project_browse_url }}")
| See More Artwork
| {% if project.nodes_featured %}
| {# In some cases featured_nodes might might be embedded #}
| {% if '_id' in project.nodes_featured[0] %}
| {% set featured_node_id=project.nodes_featured[0]._id %}
| {% else %}
| {% set featured_node_id=project.nodes_featured[0] %}
| {% endif %}
p.cta-arrow.text-center
a(href="{{ url_for('projects.view_node', project_url=project.url, node_id=featured_node_id) }}")
| See more
i.pi-angle-right
| {% endif %}
.row.mt-5
.col-md-10.mx-auto
h2.pb-3 Project Timeline
.timeline-dark
+timeline("{{ project._id }}")
section.node-extra
h2.text-center Latest Updates
| {% if activity_stream %}
.container.card-container
.row
| {% for n in activity_stream %}
| {% if n.node_type == 'post' %}
.col-md-4
.card
a.card-image(href="{{ url_for_node(node=n) }}")
| {% if n.picture %}
img.card-img-top(src="{{ n.picture.thumbnail('l', api=api) }}")
| {% endif %}
.card-body
h4.card-title
a(href="{{ url_for_node(node=n) }}") {{ n.name }}
p.card-text {{ n.properties | markdowned('content') | striptags | truncate(140, end="... <small>read more</small>") | safe | hide_none }}
| {% endif %}
| {% endfor %}
| {% endif %}
.clearfix
p.cta-arrow.text-center
a(href="{{ url_for('main.project_blog', project_url=project.url) }}")
| See all updates
i.pi-angle-right
| {% endblock body %}
| {% block footer_scripts %}
script.
function showOverlay(html_content) {
$('#page-overlay')
.addClass('active')
.html(html_content);
}
// Click anywhere in the page to hide the overlay
function hideOverlay() {
$('#page-overlay')
.removeClass('active')
.html('');
$('#page-overlay.video').removeClass('active');
$('#page-overlay.video .video-embed').html('');
}
$("a.js-open-overlay-image").on( "click", function(e) {
e.preventDefault();
e.stopPropagation();
var url = $(this).attr('href');
showOverlay('<img src="' + url + '"/>');
});
{% if project.extension_props.cloud.video_url %}
//- By isherwood - http://jsfiddle.net/isherwood/cH6e8/
function getYoutubeId(url) {
var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
var match = url.match(regExp);
if (match && match[2].length == 11) {
return match[2];
} else {
return 'error';
}
}
var videoId = getYoutubeId('{{ project.extension_props.cloud.video_url }}');
var iframeMarkup = '<iframe width="960" height="540" src="//www.youtube.com/embed/'
+ videoId + '" frameborder="0" allowfullscreen></iframe>';
$("a.js-open-overlay-video").on( "click", function(e) {
e.preventDefault();
e.stopPropagation();
showOverlay(iframeMarkup);
});
{% endif %}
//- Click anywhere on the page or hit Escape to hide the overlay.
$(document).click(function () {
hideOverlay();
});
@@ -186,4 +175,16 @@ script.
hideOverlay();
}
});
$("a[data-node_id]").on( "click", function(e) {
// var nodeId = $(this).data('node_id');
// displayNode(nodeId);
e.preventDefault();
e.stopPropagation();
$('#page-overlay').addClass('active');
var url = $(this).attr('href');
$('#page-overlay').html('<img src="' + url + '"/>')
});
| {% endblock %}

Some files were not shown because too many files have changed in this diff Show More